* 'i2c-for-linus' of git://jdelvare.pck.nerim.net/jdelvare-2.6: (26 commits) i2c-rpx: Remove i2c-mpc: work around missing-9th-clock-pulse bug i2c: New PMC MSP71xx TWI bus driver i2c-savage4: Delete many unused defines i2c/tsl2550: Speed up initialization i2c: New bus driver for the TAOS evaluation modules i2c-i801: Use the internal 32-byte buffer on ICH4+ i2c-i801: Various cleanups i2c: Add support for the TSL2550 i2c-pxa: Support new-style I2C drivers i2c-gpio: Make some internal functions static i2c-gpio: Add support for new-style clients i2c-iop3xx: Switch to static adapter numbering i2c-sis5595: Resolve resource conflict with sis5595 matroxfb: Clean-up i2c header inclusions i2c-nforce2: Add support for SMBus block transactions i2c-mpc: Use i2c_add_numbered_adapter i2c-mv64xxx: Use i2c_add_numbered_adapter i2c-piix4: Add support for the ATI SB700 i2c: New DS1682 chip driver ...tirimbino
commit
068345f4a8
@ -0,0 +1,46 @@ |
||||
Kernel driver i2c-taos-evm |
||||
|
||||
Author: Jean Delvare <khali@linux-fr.org> |
||||
|
||||
This is a driver for the evaluation modules for TAOS I2C/SMBus chips. |
||||
The modules include an SMBus master with limited capabilities, which can |
||||
be controlled over the serial port. Virtually all evaluation modules |
||||
are supported, but a few lines of code need to be added for each new |
||||
module to instantiate the right I2C chip on the bus. Obviously, a driver |
||||
for the chip in question is also needed. |
||||
|
||||
Currently supported devices are: |
||||
|
||||
* TAOS TSL2550 EVM |
||||
|
||||
For addtional information on TAOS products, please see |
||||
http://www.taosinc.com/ |
||||
|
||||
|
||||
Using this driver |
||||
----------------- |
||||
|
||||
In order to use this driver, you'll need the serport driver, and the |
||||
inputattach tool, which is part of the input-utils package. The following |
||||
commands will tell the kernel that you have a TAOS EVM on the first |
||||
serial port: |
||||
|
||||
# modprobe serport |
||||
# inputattach --taos-evm /dev/ttyS0 |
||||
|
||||
|
||||
Technical details |
||||
----------------- |
||||
|
||||
Only 4 SMBus transaction types are supported by the TAOS evaluation |
||||
modules: |
||||
* Receive Byte |
||||
* Send Byte |
||||
* Read Byte |
||||
* Write Byte |
||||
|
||||
The communication protocol is text-based and pretty simple. It is |
||||
described in a PDF document on the CD which comes with the evaluation |
||||
module. The communication is rather slow, because the serial port has |
||||
to operate at 1200 bps. However, I don't think this is a big concern in |
||||
practice, as these modules are meant for evaluation and testing only. |
@ -1,38 +0,0 @@ |
||||
Kernel driver x1205 |
||||
=================== |
||||
|
||||
Supported chips: |
||||
* Xicor X1205 RTC |
||||
Prefix: 'x1205' |
||||
Addresses scanned: none |
||||
Datasheet: http://www.intersil.com/cda/deviceinfo/0,1477,X1205,00.html |
||||
|
||||
Authors: |
||||
Karen Spearel <kas11@tampabay.rr.com>, |
||||
Alessandro Zummo <a.zummo@towertech.it> |
||||
|
||||
Description |
||||
----------- |
||||
|
||||
This module aims to provide complete access to the Xicor X1205 RTC. |
||||
Recently Xicor has merged with Intersil, but the chip is |
||||
still sold under the Xicor brand. |
||||
|
||||
This chip is located at address 0x6f and uses a 2-byte register addressing. |
||||
Two bytes need to be written to read a single register, while most |
||||
other chips just require one and take the second one as the data |
||||
to be written. To prevent corrupting unknown chips, the user must |
||||
explicitely set the probe parameter. |
||||
|
||||
example: |
||||
|
||||
modprobe x1205 probe=0,0x6f |
||||
|
||||
The module supports one more option, hctosys, which is used to set the |
||||
software clock from the x1205. On systems where the x1205 is the |
||||
only hardware rtc, this parameter could be used to achieve a correct |
||||
date/time earlier in the system boot sequence. |
||||
|
||||
example: |
||||
|
||||
modprobe x1205 probe=0,0x6f hctosys=1 |
@ -0,0 +1,653 @@ |
||||
/*
|
||||
* Specific bus support for PMC-TWI compliant implementation on MSP71xx. |
||||
* |
||||
* Copyright 2005-2007 PMC-Sierra, Inc. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify it |
||||
* under the terms of the GNU General Public License as published by the |
||||
* Free Software Foundation; either version 2 of the License, or (at your |
||||
* option) any later version. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED |
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN |
||||
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 675 Mass Ave, Cambridge, MA 02139, USA. |
||||
*/ |
||||
|
||||
#include <linux/kernel.h> |
||||
#include <linux/module.h> |
||||
#include <linux/init.h> |
||||
#include <linux/platform_device.h> |
||||
#include <linux/i2c.h> |
||||
#include <linux/interrupt.h> |
||||
#include <linux/completion.h> |
||||
#include <linux/mutex.h> |
||||
#include <linux/delay.h> |
||||
#include <asm/io.h> |
||||
|
||||
#define DRV_NAME "pmcmsptwi" |
||||
|
||||
#define MSP_TWI_SF_CLK_REG_OFFSET 0x00 |
||||
#define MSP_TWI_HS_CLK_REG_OFFSET 0x04 |
||||
#define MSP_TWI_CFG_REG_OFFSET 0x08 |
||||
#define MSP_TWI_CMD_REG_OFFSET 0x0c |
||||
#define MSP_TWI_ADD_REG_OFFSET 0x10 |
||||
#define MSP_TWI_DAT_0_REG_OFFSET 0x14 |
||||
#define MSP_TWI_DAT_1_REG_OFFSET 0x18 |
||||
#define MSP_TWI_INT_STS_REG_OFFSET 0x1c |
||||
#define MSP_TWI_INT_MSK_REG_OFFSET 0x20 |
||||
#define MSP_TWI_BUSY_REG_OFFSET 0x24 |
||||
|
||||
#define MSP_TWI_INT_STS_DONE (1 << 0) |
||||
#define MSP_TWI_INT_STS_LOST_ARBITRATION (1 << 1) |
||||
#define MSP_TWI_INT_STS_NO_RESPONSE (1 << 2) |
||||
#define MSP_TWI_INT_STS_DATA_COLLISION (1 << 3) |
||||
#define MSP_TWI_INT_STS_BUSY (1 << 4) |
||||
#define MSP_TWI_INT_STS_ALL 0x1f |
||||
|
||||
#define MSP_MAX_BYTES_PER_RW 8 |
||||
#define MSP_MAX_POLL 5 |
||||
#define MSP_POLL_DELAY 10 |
||||
#define MSP_IRQ_TIMEOUT (MSP_MAX_POLL * MSP_POLL_DELAY) |
||||
|
||||
/* IO Operation macros */ |
||||
#define pmcmsptwi_readl __raw_readl |
||||
#define pmcmsptwi_writel __raw_writel |
||||
|
||||
/* TWI command type */ |
||||
enum pmcmsptwi_cmd_type { |
||||
MSP_TWI_CMD_WRITE = 0, /* Write only */ |
||||
MSP_TWI_CMD_READ = 1, /* Read only */ |
||||
MSP_TWI_CMD_WRITE_READ = 2, /* Write then Read */ |
||||
}; |
||||
|
||||
/* The possible results of the xferCmd */ |
||||
enum pmcmsptwi_xfer_result { |
||||
MSP_TWI_XFER_OK = 0, |
||||
MSP_TWI_XFER_TIMEOUT, |
||||
MSP_TWI_XFER_BUSY, |
||||
MSP_TWI_XFER_DATA_COLLISION, |
||||
MSP_TWI_XFER_NO_RESPONSE, |
||||
MSP_TWI_XFER_LOST_ARBITRATION, |
||||
}; |
||||
|
||||
/* Corresponds to a PMCTWI clock configuration register */ |
||||
struct pmcmsptwi_clock { |
||||
u8 filter; /* Bits 15:12, default = 0x03 */ |
||||
u16 clock; /* Bits 9:0, default = 0x001f */ |
||||
}; |
||||
|
||||
struct pmcmsptwi_clockcfg { |
||||
struct pmcmsptwi_clock standard; /* The standard/fast clock config */ |
||||
struct pmcmsptwi_clock highspeed; /* The highspeed clock config */ |
||||
}; |
||||
|
||||
/* Corresponds to the main TWI configuration register */ |
||||
struct pmcmsptwi_cfg { |
||||
u8 arbf; /* Bits 15:12, default=0x03 */ |
||||
u8 nak; /* Bits 11:8, default=0x03 */ |
||||
u8 add10; /* Bit 7, default=0x00 */ |
||||
u8 mst_code; /* Bits 6:4, default=0x00 */ |
||||
u8 arb; /* Bit 1, default=0x01 */ |
||||
u8 highspeed; /* Bit 0, default=0x00 */ |
||||
}; |
||||
|
||||
/* A single pmctwi command to issue */ |
||||
struct pmcmsptwi_cmd { |
||||
u16 addr; /* The slave address (7 or 10 bits) */ |
||||
enum pmcmsptwi_cmd_type type; /* The command type */ |
||||
u8 write_len; /* Number of bytes in the write buffer */ |
||||
u8 read_len; /* Number of bytes in the read buffer */ |
||||
u8 *write_data; /* Buffer of characters to send */ |
||||
u8 *read_data; /* Buffer to fill with incoming data */ |
||||
}; |
||||
|
||||
/* The private data */ |
||||
struct pmcmsptwi_data { |
||||
void __iomem *iobase; /* iomapped base for IO */ |
||||
int irq; /* IRQ to use (0 disables) */ |
||||
struct completion wait; /* Completion for xfer */ |
||||
struct mutex lock; /* Used for threadsafeness */ |
||||
enum pmcmsptwi_xfer_result last_result; /* result of last xfer */ |
||||
}; |
||||
|
||||
/* The default settings */ |
||||
const static struct pmcmsptwi_clockcfg pmcmsptwi_defclockcfg = { |
||||
.standard = { |
||||
.filter = 0x3, |
||||
.clock = 0x1f, |
||||
}, |
||||
.highspeed = { |
||||
.filter = 0x3, |
||||
.clock = 0x1f, |
||||
}, |
||||
}; |
||||
|
||||
const static struct pmcmsptwi_cfg pmcmsptwi_defcfg = { |
||||
.arbf = 0x03, |
||||
.nak = 0x03, |
||||
.add10 = 0x00, |
||||
.mst_code = 0x00, |
||||
.arb = 0x01, |
||||
.highspeed = 0x00, |
||||
}; |
||||
|
||||
static struct pmcmsptwi_data pmcmsptwi_data; |
||||
|
||||
static struct i2c_adapter pmcmsptwi_adapter; |
||||
|
||||
/* inline helper functions */ |
||||
static inline u32 pmcmsptwi_clock_to_reg( |
||||
const struct pmcmsptwi_clock *clock) |
||||
{ |
||||
return ((clock->filter & 0xf) << 12) | (clock->clock & 0x03ff); |
||||
} |
||||
|
||||
static inline void pmcmsptwi_reg_to_clock( |
||||
u32 reg, struct pmcmsptwi_clock *clock) |
||||
{ |
||||
clock->filter = (reg >> 12) & 0xf; |
||||
clock->clock = reg & 0x03ff; |
||||
} |
||||
|
||||
static inline u32 pmcmsptwi_cfg_to_reg(const struct pmcmsptwi_cfg *cfg) |
||||
{ |
||||
return ((cfg->arbf & 0xf) << 12) | |
||||
((cfg->nak & 0xf) << 8) | |
||||
((cfg->add10 & 0x1) << 7) | |
||||
((cfg->mst_code & 0x7) << 4) | |
||||
((cfg->arb & 0x1) << 1) | |
||||
(cfg->highspeed & 0x1); |
||||
} |
||||
|
||||
static inline void pmcmsptwi_reg_to_cfg(u32 reg, struct pmcmsptwi_cfg *cfg) |
||||
{ |
||||
cfg->arbf = (reg >> 12) & 0xf; |
||||
cfg->nak = (reg >> 8) & 0xf; |
||||
cfg->add10 = (reg >> 7) & 0x1; |
||||
cfg->mst_code = (reg >> 4) & 0x7; |
||||
cfg->arb = (reg >> 1) & 0x1; |
||||
cfg->highspeed = reg & 0x1; |
||||
} |
||||
|
||||
/*
|
||||
* Sets the current clock configuration |
||||
*/ |
||||
static void pmcmsptwi_set_clock_config(const struct pmcmsptwi_clockcfg *cfg, |
||||
struct pmcmsptwi_data *data) |
||||
{ |
||||
mutex_lock(&data->lock); |
||||
pmcmsptwi_writel(pmcmsptwi_clock_to_reg(&cfg->standard), |
||||
data->iobase + MSP_TWI_SF_CLK_REG_OFFSET); |
||||
pmcmsptwi_writel(pmcmsptwi_clock_to_reg(&cfg->highspeed), |
||||
data->iobase + MSP_TWI_HS_CLK_REG_OFFSET); |
||||
mutex_unlock(&data->lock); |
||||
} |
||||
|
||||
/*
|
||||
* Gets the current TWI bus configuration |
||||
*/ |
||||
static void pmcmsptwi_get_twi_config(struct pmcmsptwi_cfg *cfg, |
||||
struct pmcmsptwi_data *data) |
||||
{ |
||||
mutex_lock(&data->lock); |
||||
pmcmsptwi_reg_to_cfg(pmcmsptwi_readl( |
||||
data->iobase + MSP_TWI_CFG_REG_OFFSET), cfg); |
||||
mutex_unlock(&data->lock); |
||||
} |
||||
|
||||
/*
|
||||
* Sets the current TWI bus configuration |
||||
*/ |
||||
static void pmcmsptwi_set_twi_config(const struct pmcmsptwi_cfg *cfg, |
||||
struct pmcmsptwi_data *data) |
||||
{ |
||||
mutex_lock(&data->lock); |
||||
pmcmsptwi_writel(pmcmsptwi_cfg_to_reg(cfg), |
||||
data->iobase + MSP_TWI_CFG_REG_OFFSET); |
||||
mutex_unlock(&data->lock); |
||||
} |
||||
|
||||
/*
|
||||
* Parses the 'int_sts' register and returns a well-defined error code |
||||
*/ |
||||
static enum pmcmsptwi_xfer_result pmcmsptwi_get_result(u32 reg) |
||||
{ |
||||
if (reg & MSP_TWI_INT_STS_LOST_ARBITRATION) { |
||||
dev_dbg(&pmcmsptwi_adapter.dev, |
||||
"Result: Lost arbitration\n"); |
||||
return MSP_TWI_XFER_LOST_ARBITRATION; |
||||
} else if (reg & MSP_TWI_INT_STS_NO_RESPONSE) { |
||||
dev_dbg(&pmcmsptwi_adapter.dev, |
||||
"Result: No response\n"); |
||||
return MSP_TWI_XFER_NO_RESPONSE; |
||||
} else if (reg & MSP_TWI_INT_STS_DATA_COLLISION) { |
||||
dev_dbg(&pmcmsptwi_adapter.dev, |
||||
"Result: Data collision\n"); |
||||
return MSP_TWI_XFER_DATA_COLLISION; |
||||
} else if (reg & MSP_TWI_INT_STS_BUSY) { |
||||
dev_dbg(&pmcmsptwi_adapter.dev, |
||||
"Result: Bus busy\n"); |
||||
return MSP_TWI_XFER_BUSY; |
||||
} |
||||
|
||||
dev_dbg(&pmcmsptwi_adapter.dev, "Result: Operation succeeded\n"); |
||||
return MSP_TWI_XFER_OK; |
||||
} |
||||
|
||||
/*
|
||||
* In interrupt mode, handle the interrupt. |
||||
* NOTE: Assumes data->lock is held. |
||||
*/ |
||||
static irqreturn_t pmcmsptwi_interrupt(int irq, void *ptr) |
||||
{ |
||||
struct pmcmsptwi_data *data = ptr; |
||||
|
||||
u32 reason = pmcmsptwi_readl(data->iobase + |
||||
MSP_TWI_INT_STS_REG_OFFSET); |
||||
pmcmsptwi_writel(reason, data->iobase + MSP_TWI_INT_STS_REG_OFFSET); |
||||
|
||||
dev_dbg(&pmcmsptwi_adapter.dev, "Got interrupt 0x%08x\n", reason); |
||||
if (!(reason & MSP_TWI_INT_STS_DONE)) |
||||
return IRQ_NONE; |
||||
|
||||
data->last_result = pmcmsptwi_get_result(reason); |
||||
complete(&data->wait); |
||||
|
||||
return IRQ_HANDLED; |
||||
} |
||||
|
||||
/*
|
||||
* Probe for and register the device and return 0 if there is one. |
||||
*/ |
||||
static int __devinit pmcmsptwi_probe(struct platform_device *pldev) |
||||
{ |
||||
struct resource *res; |
||||
int rc = -ENODEV; |
||||
|
||||
/* get the static platform resources */ |
||||
res = platform_get_resource(pldev, IORESOURCE_MEM, 0); |
||||
if (!res) { |
||||
dev_err(&pldev->dev, "IOMEM resource not found\n"); |
||||
goto ret_err; |
||||
} |
||||
|
||||
/* reserve the memory region */ |
||||
if (!request_mem_region(res->start, res->end - res->start + 1, |
||||
pldev->name)) { |
||||
dev_err(&pldev->dev, |
||||
"Unable to get memory/io address region 0x%08x\n", |
||||
res->start); |
||||
rc = -EBUSY; |
||||
goto ret_err; |
||||
} |
||||
|
||||
/* remap the memory */ |
||||
pmcmsptwi_data.iobase = ioremap_nocache(res->start, |
||||
res->end - res->start + 1); |
||||
if (!pmcmsptwi_data.iobase) { |
||||
dev_err(&pldev->dev, |
||||
"Unable to ioremap address 0x%08x\n", res->start); |
||||
rc = -EIO; |
||||
goto ret_unreserve; |
||||
} |
||||
|
||||
/* request the irq */ |
||||
pmcmsptwi_data.irq = platform_get_irq(pldev, 0); |
||||
if (pmcmsptwi_data.irq) { |
||||
rc = request_irq(pmcmsptwi_data.irq, &pmcmsptwi_interrupt, |
||||
IRQF_SHARED | IRQF_DISABLED | IRQF_SAMPLE_RANDOM, |
||||
pldev->name, &pmcmsptwi_data); |
||||
if (rc == 0) { |
||||
/*
|
||||
* Enable 'DONE' interrupt only. |
||||
* |
||||
* If you enable all interrupts, you will get one on |
||||
* error and another when the operation completes. |
||||
* This way you only have to handle one interrupt, |
||||
* but you can still check all result flags. |
||||
*/ |
||||
pmcmsptwi_writel(MSP_TWI_INT_STS_DONE, |
||||
pmcmsptwi_data.iobase + |
||||
MSP_TWI_INT_MSK_REG_OFFSET); |
||||
} else { |
||||
dev_warn(&pldev->dev, |
||||
"Could not assign TWI IRQ handler " |
||||
"to irq %d (continuing with poll)\n", |
||||
pmcmsptwi_data.irq); |
||||
pmcmsptwi_data.irq = 0; |
||||
} |
||||
} |
||||
|
||||
init_completion(&pmcmsptwi_data.wait); |
||||
mutex_init(&pmcmsptwi_data.lock); |
||||
|
||||
pmcmsptwi_set_clock_config(&pmcmsptwi_defclockcfg, &pmcmsptwi_data); |
||||
pmcmsptwi_set_twi_config(&pmcmsptwi_defcfg, &pmcmsptwi_data); |
||||
|
||||
printk(KERN_INFO DRV_NAME ": Registering MSP71xx I2C adapter\n"); |
||||
|
||||
pmcmsptwi_adapter.dev.parent = &pldev->dev; |
||||
platform_set_drvdata(pldev, &pmcmsptwi_adapter); |
||||
i2c_set_adapdata(&pmcmsptwi_adapter, &pmcmsptwi_data); |
||||
|
||||
rc = i2c_add_adapter(&pmcmsptwi_adapter); |
||||
if (rc) { |
||||
dev_err(&pldev->dev, "Unable to register I2C adapter\n"); |
||||
goto ret_unmap; |
||||
} |
||||
|
||||
return 0; |
||||
|
||||
ret_unmap: |
||||
platform_set_drvdata(pldev, NULL); |
||||
if (pmcmsptwi_data.irq) { |
||||
pmcmsptwi_writel(0, |
||||
pmcmsptwi_data.iobase + MSP_TWI_INT_MSK_REG_OFFSET); |
||||
free_irq(pmcmsptwi_data.irq, &pmcmsptwi_data); |
||||
} |
||||
|
||||
iounmap(pmcmsptwi_data.iobase); |
||||
|
||||
ret_unreserve: |
||||
release_mem_region(res->start, res->end - res->start + 1); |
||||
|
||||
ret_err: |
||||
return rc; |
||||
} |
||||
|
||||
/*
|
||||
* Release the device and return 0 if there is one. |
||||
*/ |
||||
static int __devexit pmcmsptwi_remove(struct platform_device *pldev) |
||||
{ |
||||
struct resource *res; |
||||
|
||||
i2c_del_adapter(&pmcmsptwi_adapter); |
||||
|
||||
platform_set_drvdata(pldev, NULL); |
||||
if (pmcmsptwi_data.irq) { |
||||
pmcmsptwi_writel(0, |
||||
pmcmsptwi_data.iobase + MSP_TWI_INT_MSK_REG_OFFSET); |
||||
free_irq(pmcmsptwi_data.irq, &pmcmsptwi_data); |
||||
} |
||||
|
||||
iounmap(pmcmsptwi_data.iobase); |
||||
|
||||
res = platform_get_resource(pldev, IORESOURCE_MEM, 0); |
||||
release_mem_region(res->start, res->end - res->start + 1); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
/*
|
||||
* Polls the 'busy' register until the command is complete. |
||||
* NOTE: Assumes data->lock is held. |
||||
*/ |
||||
static void pmcmsptwi_poll_complete(struct pmcmsptwi_data *data) |
||||
{ |
||||
int i; |
||||
|
||||
for (i = 0; i < MSP_MAX_POLL; i++) { |
||||
u32 val = pmcmsptwi_readl(data->iobase + |
||||
MSP_TWI_BUSY_REG_OFFSET); |
||||
if (val == 0) { |
||||
u32 reason = pmcmsptwi_readl(data->iobase + |
||||
MSP_TWI_INT_STS_REG_OFFSET); |
||||
pmcmsptwi_writel(reason, data->iobase + |
||||
MSP_TWI_INT_STS_REG_OFFSET); |
||||
data->last_result = pmcmsptwi_get_result(reason); |
||||
return; |
||||
} |
||||
udelay(MSP_POLL_DELAY); |
||||
} |
||||
|
||||
dev_dbg(&pmcmsptwi_adapter.dev, "Result: Poll timeout\n"); |
||||
data->last_result = MSP_TWI_XFER_TIMEOUT; |
||||
} |
||||
|
||||
/*
|
||||
* Do the transfer (low level): |
||||
* May use interrupt-driven or polling, depending on if an IRQ is |
||||
* presently registered. |
||||
* NOTE: Assumes data->lock is held. |
||||
*/ |
||||
static enum pmcmsptwi_xfer_result pmcmsptwi_do_xfer( |
||||
u32 reg, struct pmcmsptwi_data *data) |
||||
{ |
||||
dev_dbg(&pmcmsptwi_adapter.dev, "Writing cmd reg 0x%08x\n", reg); |
||||
pmcmsptwi_writel(reg, data->iobase + MSP_TWI_CMD_REG_OFFSET); |
||||
if (data->irq) { |
||||
unsigned long timeleft = wait_for_completion_timeout( |
||||
&data->wait, MSP_IRQ_TIMEOUT); |
||||
if (timeleft == 0) { |
||||
dev_dbg(&pmcmsptwi_adapter.dev, |
||||
"Result: IRQ timeout\n"); |
||||
complete(&data->wait); |
||||
data->last_result = MSP_TWI_XFER_TIMEOUT; |
||||
} |
||||
} else |
||||
pmcmsptwi_poll_complete(data); |
||||
|
||||
return data->last_result; |
||||
} |
||||
|
||||
/*
|
||||
* Helper routine, converts 'pmctwi_cmd' struct to register format |
||||
*/ |
||||
static inline u32 pmcmsptwi_cmd_to_reg(const struct pmcmsptwi_cmd *cmd) |
||||
{ |
||||
return ((cmd->type & 0x3) << 8) | |
||||
(((cmd->write_len - 1) & 0x7) << 4) | |
||||
((cmd->read_len - 1) & 0x7); |
||||
} |
||||
|
||||
/*
|
||||
* Do the transfer (high level) |
||||
*/ |
||||
static enum pmcmsptwi_xfer_result pmcmsptwi_xfer_cmd( |
||||
struct pmcmsptwi_cmd *cmd, |
||||
struct pmcmsptwi_data *data) |
||||
{ |
||||
enum pmcmsptwi_xfer_result retval; |
||||
|
||||
if ((cmd->type == MSP_TWI_CMD_WRITE && cmd->write_len == 0) || |
||||
(cmd->type == MSP_TWI_CMD_READ && cmd->read_len == 0) || |
||||
(cmd->type == MSP_TWI_CMD_WRITE_READ && |
||||
(cmd->read_len == 0 || cmd->write_len == 0))) { |
||||
dev_err(&pmcmsptwi_adapter.dev, |
||||
"%s: Cannot transfer less than 1 byte\n", |
||||
__FUNCTION__); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
if (cmd->read_len > MSP_MAX_BYTES_PER_RW || |
||||
cmd->write_len > MSP_MAX_BYTES_PER_RW) { |
||||
dev_err(&pmcmsptwi_adapter.dev, |
||||
"%s: Cannot transfer more than %d bytes\n", |
||||
__FUNCTION__, MSP_MAX_BYTES_PER_RW); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
mutex_lock(&data->lock); |
||||
dev_dbg(&pmcmsptwi_adapter.dev, |
||||
"Setting address to 0x%04x\n", cmd->addr); |
||||
pmcmsptwi_writel(cmd->addr, data->iobase + MSP_TWI_ADD_REG_OFFSET); |
||||
|
||||
if (cmd->type == MSP_TWI_CMD_WRITE || |
||||
cmd->type == MSP_TWI_CMD_WRITE_READ) { |
||||
__be64 tmp = cpu_to_be64p((u64 *)cmd->write_data); |
||||
tmp >>= (MSP_MAX_BYTES_PER_RW - cmd->write_len) * 8; |
||||
dev_dbg(&pmcmsptwi_adapter.dev, "Writing 0x%016llx\n", tmp); |
||||
pmcmsptwi_writel(tmp & 0x00000000ffffffffLL, |
||||
data->iobase + MSP_TWI_DAT_0_REG_OFFSET); |
||||
if (cmd->write_len > 4) |
||||
pmcmsptwi_writel(tmp >> 32, |
||||
data->iobase + MSP_TWI_DAT_1_REG_OFFSET); |
||||
} |
||||
|
||||
retval = pmcmsptwi_do_xfer(pmcmsptwi_cmd_to_reg(cmd), data); |
||||
if (retval != MSP_TWI_XFER_OK) |
||||
goto xfer_err; |
||||
|
||||
if (cmd->type == MSP_TWI_CMD_READ || |
||||
cmd->type == MSP_TWI_CMD_WRITE_READ) { |
||||
int i; |
||||
u64 rmsk = ~(0xffffffffffffffffLL << (cmd->read_len * 8)); |
||||
u64 tmp = (u64)pmcmsptwi_readl(data->iobase + |
||||
MSP_TWI_DAT_0_REG_OFFSET); |
||||
if (cmd->read_len > 4) |
||||
tmp |= (u64)pmcmsptwi_readl(data->iobase + |
||||
MSP_TWI_DAT_1_REG_OFFSET) << 32; |
||||
tmp &= rmsk; |
||||
dev_dbg(&pmcmsptwi_adapter.dev, "Read 0x%016llx\n", tmp); |
||||
|
||||
for (i = 0; i < cmd->read_len; i++) |
||||
cmd->read_data[i] = tmp >> i; |
||||
} |
||||
|
||||
xfer_err: |
||||
mutex_unlock(&data->lock); |
||||
|
||||
return retval; |
||||
} |
||||
|
||||
/* -- Algorithm functions -- */ |
||||
|
||||
/*
|
||||
* Sends an i2c command out on the adapter |
||||
*/ |
||||
static int pmcmsptwi_master_xfer(struct i2c_adapter *adap, |
||||
struct i2c_msg *msg, int num) |
||||
{ |
||||
struct pmcmsptwi_data *data = i2c_get_adapdata(adap); |
||||
struct pmcmsptwi_cmd cmd; |
||||
struct pmcmsptwi_cfg oldcfg, newcfg; |
||||
int ret; |
||||
|
||||
if (num > 2) { |
||||
dev_dbg(&adap->dev, "%d messages unsupported\n", num); |
||||
return -EINVAL; |
||||
} else if (num == 2) { |
||||
/* Check for a dual write-then-read command */ |
||||
struct i2c_msg *nextmsg = msg + 1; |
||||
if (!(msg->flags & I2C_M_RD) && |
||||
(nextmsg->flags & I2C_M_RD) && |
||||
msg->addr == nextmsg->addr) { |
||||
cmd.type = MSP_TWI_CMD_WRITE_READ; |
||||
cmd.write_len = msg->len; |
||||
cmd.write_data = msg->buf; |
||||
cmd.read_len = nextmsg->len; |
||||
cmd.read_data = nextmsg->buf; |
||||
} else { |
||||
dev_dbg(&adap->dev, |
||||
"Non write-read dual messages unsupported\n"); |
||||
return -EINVAL; |
||||
} |
||||
} else if (msg->flags & I2C_M_RD) { |
||||
cmd.type = MSP_TWI_CMD_READ; |
||||
cmd.read_len = msg->len; |
||||
cmd.read_data = msg->buf; |
||||
cmd.write_len = 0; |
||||
cmd.write_data = NULL; |
||||
} else { |
||||
cmd.type = MSP_TWI_CMD_WRITE; |
||||
cmd.read_len = 0; |
||||
cmd.read_data = NULL; |
||||
cmd.write_len = msg->len; |
||||
cmd.write_data = msg->buf; |
||||
} |
||||
|
||||
if (msg->len == 0) { |
||||
dev_err(&adap->dev, "Zero-byte messages unsupported\n"); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
cmd.addr = msg->addr; |
||||
|
||||
if (msg->flags & I2C_M_TEN) { |
||||
pmcmsptwi_get_twi_config(&newcfg, data); |
||||
memcpy(&oldcfg, &newcfg, sizeof(oldcfg)); |
||||
|
||||
/* Set the special 10-bit address flag */ |
||||
newcfg.add10 = 1; |
||||
|
||||
pmcmsptwi_set_twi_config(&newcfg, data); |
||||
} |
||||
|
||||
/* Execute the command */ |
||||
ret = pmcmsptwi_xfer_cmd(&cmd, data); |
||||
|
||||
if (msg->flags & I2C_M_TEN) |
||||
pmcmsptwi_set_twi_config(&oldcfg, data); |
||||
|
||||
dev_dbg(&adap->dev, "I2C %s of %d bytes ", |
||||
(msg->flags & I2C_M_RD) ? "read" : "write", msg->len); |
||||
if (ret != MSP_TWI_XFER_OK) { |
||||
/*
|
||||
* TODO: We could potentially loop and retry in the case |
||||
* of MSP_TWI_XFER_TIMEOUT. |
||||
*/ |
||||
dev_dbg(&adap->dev, "failed\n"); |
||||
return -1; |
||||
} |
||||
|
||||
dev_dbg(&adap->dev, "succeeded\n"); |
||||
return 0; |
||||
} |
||||
|
||||
static u32 pmcmsptwi_i2c_func(struct i2c_adapter *adapter) |
||||
{ |
||||
return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR | |
||||
I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | |
||||
I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_PROC_CALL; |
||||
} |
||||
|
||||
/* -- Initialization -- */ |
||||
|
||||
static struct i2c_algorithm pmcmsptwi_algo = { |
||||
.master_xfer = pmcmsptwi_master_xfer, |
||||
.functionality = pmcmsptwi_i2c_func, |
||||
}; |
||||
|
||||
static struct i2c_adapter pmcmsptwi_adapter = { |
||||
.owner = THIS_MODULE, |
||||
.class = I2C_CLASS_HWMON, |
||||
.algo = &pmcmsptwi_algo, |
||||
.name = DRV_NAME, |
||||
}; |
||||
|
||||
static struct platform_driver pmcmsptwi_driver = { |
||||
.probe = pmcmsptwi_probe, |
||||
.remove = __devexit_p(pmcmsptwi_remove), |
||||
.driver { |
||||
.name = DRV_NAME, |
||||
.owner = THIS_MODULE, |
||||
}, |
||||
}; |
||||
|
||||
static int __init pmcmsptwi_init(void) |
||||
{ |
||||
return platform_driver_register(&pmcmsptwi_driver); |
||||
} |
||||
|
||||
static void __exit pmcmsptwi_exit(void) |
||||
{ |
||||
platform_driver_unregister(&pmcmsptwi_driver); |
||||
} |
||||
|
||||
MODULE_DESCRIPTION("PMC MSP TWI/SMBus/I2C driver"); |
||||
MODULE_LICENSE("GPL"); |
||||
|
||||
module_init(pmcmsptwi_init); |
||||
module_exit(pmcmsptwi_exit); |
@ -1,101 +0,0 @@ |
||||
/*
|
||||
* Embedded Planet RPX Lite MPC8xx CPM I2C interface. |
||||
* Copyright (c) 1999 Dan Malek (dmalek@jlc.net). |
||||
* |
||||
* moved into proper i2c interface; |
||||
* Brad Parker (brad@heeltoe.com) |
||||
* |
||||
* RPX lite specific parts of the i2c interface |
||||
* Update: There actually isn't anything RPXLite-specific about this module. |
||||
* This should work for most any 8xx board. The console messages have been
|
||||
* changed to eliminate RPXLite references. |
||||
*/ |
||||
|
||||
#include <linux/kernel.h> |
||||
#include <linux/module.h> |
||||
#include <linux/init.h> |
||||
#include <linux/stddef.h> |
||||
#include <linux/i2c.h> |
||||
#include <linux/i2c-algo-8xx.h> |
||||
#include <asm/mpc8xx.h> |
||||
#include <asm/commproc.h> |
||||
|
||||
|
||||
static void |
||||
rpx_iic_init(struct i2c_algo_8xx_data *data) |
||||
{ |
||||
volatile cpm8xx_t *cp; |
||||
volatile immap_t *immap; |
||||
|
||||
cp = cpmp; /* Get pointer to Communication Processor */ |
||||
immap = (immap_t *)IMAP_ADDR; /* and to internal registers */ |
||||
|
||||
data->iip = (iic_t *)&cp->cp_dparam[PROFF_IIC]; |
||||
|
||||
/* Check for and use a microcode relocation patch.
|
||||
*/ |
||||
if ((data->reloc = data->iip->iic_rpbase)) |
||||
data->iip = (iic_t *)&cp->cp_dpmem[data->iip->iic_rpbase]; |
||||
|
||||
data->i2c = (i2c8xx_t *)&(immap->im_i2c); |
||||
data->cp = cp; |
||||
|
||||
/* Initialize Port B IIC pins.
|
||||
*/ |
||||
cp->cp_pbpar |= 0x00000030; |
||||
cp->cp_pbdir |= 0x00000030; |
||||
cp->cp_pbodr |= 0x00000030; |
||||
|
||||
/* Allocate space for two transmit and two receive buffer
|
||||
* descriptors in the DP ram. |
||||
*/ |
||||
data->dp_addr = cpm_dpalloc(sizeof(cbd_t) * 4, 8); |
||||
|
||||
/* ptr to i2c area */ |
||||
data->i2c = (i2c8xx_t *)&(((immap_t *)IMAP_ADDR)->im_i2c); |
||||
} |
||||
|
||||
static int rpx_install_isr(int irq, void (*func)(void *), void *data) |
||||
{ |
||||
/* install interrupt handler */ |
||||
cpm_install_handler(irq, func, data); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static struct i2c_algo_8xx_data rpx_data = { |
||||
.setisr = rpx_install_isr |
||||
}; |
||||
|
||||
static struct i2c_adapter rpx_ops = { |
||||
.owner = THIS_MODULE, |
||||
.name = "m8xx", |
||||
.id = I2C_HW_MPC8XX_EPON, |
||||
.algo_data = &rpx_data, |
||||
}; |
||||
|
||||
int __init i2c_rpx_init(void) |
||||
{ |
||||
printk(KERN_INFO "i2c-rpx: i2c MPC8xx driver\n"); |
||||
|
||||
/* reset hardware to sane state */ |
||||
rpx_iic_init(&rpx_data); |
||||
|
||||
if (i2c_8xx_add_bus(&rpx_ops) < 0) { |
||||
printk(KERN_ERR "i2c-rpx: Unable to register with I2C\n"); |
||||
return -ENODEV; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
void __exit i2c_rpx_exit(void) |
||||
{ |
||||
i2c_8xx_del_bus(&rpx_ops); |
||||
} |
||||
|
||||
MODULE_AUTHOR("Dan Malek <dmalek@jlc.net>"); |
||||
MODULE_DESCRIPTION("I2C-Bus adapter routines for MPC8xx boards"); |
||||
|
||||
module_init(i2c_rpx_init); |
||||
module_exit(i2c_rpx_exit); |
@ -0,0 +1,330 @@ |
||||
/*
|
||||
* Driver for the TAOS evaluation modules |
||||
* These devices include an I2C master which can be controlled over the |
||||
* serial port. |
||||
* |
||||
* Copyright (C) 2007 Jean Delvare <khali@linux-fr.org> |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; version 2 of the License. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, write to the Free Software |
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
||||
*/ |
||||
|
||||
#include <linux/delay.h> |
||||
#include <linux/module.h> |
||||
#include <linux/slab.h> |
||||
#include <linux/interrupt.h> |
||||
#include <linux/input.h> |
||||
#include <linux/serio.h> |
||||
#include <linux/init.h> |
||||
#include <linux/i2c.h> |
||||
|
||||
#define TAOS_BUFFER_SIZE 63 |
||||
|
||||
#define TAOS_STATE_INIT 0 |
||||
#define TAOS_STATE_IDLE 1 |
||||
#define TAOS_STATE_SEND 2 |
||||
#define TAOS_STATE_RECV 3 |
||||
|
||||
#define TAOS_CMD_RESET 0x12 |
||||
|
||||
static DECLARE_WAIT_QUEUE_HEAD(wq); |
||||
|
||||
struct taos_data { |
||||
struct i2c_adapter adapter; |
||||
struct i2c_client *client; |
||||
int state; |
||||
u8 addr; /* last used address */ |
||||
unsigned char buffer[TAOS_BUFFER_SIZE]; |
||||
unsigned int pos; /* position inside the buffer */ |
||||
}; |
||||
|
||||
/* TAOS TSL2550 EVM */ |
||||
static struct i2c_board_info tsl2550_info = { |
||||
I2C_BOARD_INFO("tsl2550", 0x39), |
||||
.type = "tsl2550", |
||||
}; |
||||
|
||||
/* Instantiate i2c devices based on the adapter name */ |
||||
static struct i2c_client *taos_instantiate_device(struct i2c_adapter *adapter) |
||||
{ |
||||
if (!strncmp(adapter->name, "TAOS TSL2550 EVM", 16)) { |
||||
dev_info(&adapter->dev, "Instantiating device %s at 0x%02x\n", |
||||
tsl2550_info.driver_name, tsl2550_info.addr); |
||||
return i2c_new_device(adapter, &tsl2550_info); |
||||
} |
||||
|
||||
return NULL; |
||||
} |
||||
|
||||
static int taos_smbus_xfer(struct i2c_adapter *adapter, u16 addr, |
||||
unsigned short flags, char read_write, u8 command, |
||||
int size, union i2c_smbus_data *data) |
||||
{ |
||||
struct serio *serio = adapter->algo_data; |
||||
struct taos_data *taos = serio_get_drvdata(serio); |
||||
char *p; |
||||
|
||||
/* Encode our transaction. "@" is for the device address, "$" for the
|
||||
SMBus command and "#" for the data. */ |
||||
p = taos->buffer; |
||||
|
||||
/* The device remembers the last used address, no need to send it
|
||||
again if it's the same */ |
||||
if (addr != taos->addr) |
||||
p += sprintf(p, "@%02X", addr); |
||||
|
||||
switch (size) { |
||||
case I2C_SMBUS_BYTE: |
||||
if (read_write == I2C_SMBUS_WRITE) |
||||
sprintf(p, "$#%02X", command); |
||||
else |
||||
sprintf(p, "$"); |
||||
break; |
||||
case I2C_SMBUS_BYTE_DATA: |
||||
if (read_write == I2C_SMBUS_WRITE) |
||||
sprintf(p, "$%02X#%02X", command, data->byte); |
||||
else |
||||
sprintf(p, "$%02X", command); |
||||
break; |
||||
default: |
||||
dev_dbg(&adapter->dev, "Unsupported transaction size %d\n", |
||||
size); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
/* Send the transaction to the TAOS EVM */ |
||||
dev_dbg(&adapter->dev, "Command buffer: %s\n", taos->buffer); |
||||
taos->pos = 0; |
||||
taos->state = TAOS_STATE_SEND; |
||||
serio_write(serio, taos->buffer[0]); |
||||
wait_event_interruptible_timeout(wq, taos->state == TAOS_STATE_IDLE, |
||||
msecs_to_jiffies(250)); |
||||
if (taos->state != TAOS_STATE_IDLE) { |
||||
dev_err(&adapter->dev, "Transaction failed " |
||||
"(state=%d, pos=%d)\n", taos->state, taos->pos); |
||||
taos->addr = 0; |
||||
return -EIO; |
||||
} |
||||
taos->addr = addr; |
||||
|
||||
/* Start the transaction and read the answer */ |
||||
taos->pos = 0; |
||||
taos->state = TAOS_STATE_RECV; |
||||
serio_write(serio, read_write == I2C_SMBUS_WRITE ? '>' : '<'); |
||||
wait_event_interruptible_timeout(wq, taos->state == TAOS_STATE_IDLE, |
||||
msecs_to_jiffies(150)); |
||||
if (taos->state != TAOS_STATE_IDLE |
||||
|| taos->pos != 6) { |
||||
dev_err(&adapter->dev, "Transaction timeout (pos=%d)\n", |
||||
taos->pos); |
||||
return -EIO; |
||||
} |
||||
dev_dbg(&adapter->dev, "Answer buffer: %s\n", taos->buffer); |
||||
|
||||
/* Interpret the returned string */ |
||||
p = taos->buffer + 2; |
||||
p[3] = '\0'; |
||||
if (!strcmp(p, "NAK")) |
||||
return -ENODEV; |
||||
|
||||
if (read_write == I2C_SMBUS_WRITE) { |
||||
if (!strcmp(p, "ACK")) |
||||
return 0; |
||||
} else { |
||||
if (p[0] == 'x') { |
||||
data->byte = simple_strtol(p + 1, NULL, 16); |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
return -EIO; |
||||
} |
||||
|
||||
static u32 taos_smbus_func(struct i2c_adapter *adapter) |
||||
{ |
||||
return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA; |
||||
} |
||||
|
||||
static const struct i2c_algorithm taos_algorithm = { |
||||
.smbus_xfer = taos_smbus_xfer, |
||||
.functionality = taos_smbus_func, |
||||
}; |
||||
|
||||
static irqreturn_t taos_interrupt(struct serio *serio, unsigned char data, |
||||
unsigned int flags) |
||||
{ |
||||
struct taos_data *taos = serio_get_drvdata(serio); |
||||
|
||||
switch (taos->state) { |
||||
case TAOS_STATE_INIT: |
||||
taos->buffer[taos->pos++] = data; |
||||
if (data == ':' |
||||
|| taos->pos == TAOS_BUFFER_SIZE - 1) { |
||||
taos->buffer[taos->pos] = '\0'; |
||||
taos->state = TAOS_STATE_IDLE; |
||||
wake_up_interruptible(&wq); |
||||
} |
||||
break; |
||||
case TAOS_STATE_SEND: |
||||
if (taos->buffer[++taos->pos]) |
||||
serio_write(serio, taos->buffer[taos->pos]); |
||||
else { |
||||
taos->state = TAOS_STATE_IDLE; |
||||
wake_up_interruptible(&wq); |
||||
} |
||||
break; |
||||
case TAOS_STATE_RECV: |
||||
taos->buffer[taos->pos++] = data; |
||||
if (data == ']') { |
||||
taos->buffer[taos->pos] = '\0'; |
||||
taos->state = TAOS_STATE_IDLE; |
||||
wake_up_interruptible(&wq); |
||||
} |
||||
break; |
||||
} |
||||
|
||||
return IRQ_HANDLED; |
||||
} |
||||
|
||||
/* Extract the adapter name from the buffer received after reset.
|
||||
The buffer is modified and a pointer inside the buffer is returned. */ |
||||
static char *taos_adapter_name(char *buffer) |
||||
{ |
||||
char *start, *end; |
||||
|
||||
start = strstr(buffer, "TAOS "); |
||||
if (!start) |
||||
return NULL; |
||||
|
||||
end = strchr(start, '\r'); |
||||
if (!end) |
||||
return NULL; |
||||
*end = '\0'; |
||||
|
||||
return start; |
||||
} |
||||
|
||||
static int taos_connect(struct serio *serio, struct serio_driver *drv) |
||||
{ |
||||
struct taos_data *taos; |
||||
struct i2c_adapter *adapter; |
||||
char *name; |
||||
int err; |
||||
|
||||
taos = kzalloc(sizeof(struct taos_data), GFP_KERNEL); |
||||
if (!taos) { |
||||
err = -ENOMEM; |
||||
goto exit; |
||||
} |
||||
taos->state = TAOS_STATE_INIT; |
||||
serio_set_drvdata(serio, taos); |
||||
|
||||
err = serio_open(serio, drv); |
||||
if (err) |
||||
goto exit_kfree; |
||||
|
||||
adapter = &taos->adapter; |
||||
adapter->owner = THIS_MODULE; |
||||
adapter->algo = &taos_algorithm; |
||||
adapter->algo_data = serio; |
||||
adapter->dev.parent = &serio->dev; |
||||
|
||||
/* Reset the TAOS evaluation module to identify it */ |
||||
serio_write(serio, TAOS_CMD_RESET); |
||||
wait_event_interruptible_timeout(wq, taos->state == TAOS_STATE_IDLE, |
||||
msecs_to_jiffies(2000)); |
||||
|
||||
if (taos->state != TAOS_STATE_IDLE) { |
||||
err = -ENODEV; |
||||
dev_dbg(&serio->dev, "TAOS EVM reset failed (state=%d, " |
||||
"pos=%d)\n", taos->state, taos->pos); |
||||
goto exit_close; |
||||
} |
||||
|
||||
name = taos_adapter_name(taos->buffer); |
||||
if (!name) { |
||||
err = -ENODEV; |
||||
dev_err(&serio->dev, "TAOS EVM identification failed\n"); |
||||
goto exit_close; |
||||
} |
||||
strlcpy(adapter->name, name, sizeof(adapter->name)); |
||||
|
||||
err = i2c_add_adapter(adapter); |
||||
if (err) |
||||
goto exit_close; |
||||
dev_dbg(&serio->dev, "Connected to TAOS EVM\n"); |
||||
|
||||
taos->client = taos_instantiate_device(adapter); |
||||
return 0; |
||||
|
||||
exit_close: |
||||
serio_close(serio); |
||||
exit_kfree: |
||||
serio_set_drvdata(serio, NULL); |
||||
kfree(taos); |
||||
exit: |
||||
return err; |
||||
} |
||||
|
||||
static void taos_disconnect(struct serio *serio) |
||||
{ |
||||
struct taos_data *taos = serio_get_drvdata(serio); |
||||
|
||||
if (taos->client) |
||||
i2c_unregister_device(taos->client); |
||||
i2c_del_adapter(&taos->adapter); |
||||
serio_close(serio); |
||||
serio_set_drvdata(serio, NULL); |
||||
kfree(taos); |
||||
|
||||
dev_dbg(&serio->dev, "Disconnected from TAOS EVM\n"); |
||||
} |
||||
|
||||
static struct serio_device_id taos_serio_ids[] = { |
||||
{ |
||||
.type = SERIO_RS232, |
||||
.proto = SERIO_TAOSEVM, |
||||
.id = SERIO_ANY, |
||||
.extra = SERIO_ANY, |
||||
}, |
||||
{ 0 } |
||||
}; |
||||
MODULE_DEVICE_TABLE(serio, taos_serio_ids); |
||||
|
||||
static struct serio_driver taos_drv = { |
||||
.driver = { |
||||
.name = "taos-evm", |
||||
}, |
||||
.description = "TAOS evaluation module driver", |
||||
.id_table = taos_serio_ids, |
||||
.connect = taos_connect, |
||||
.disconnect = taos_disconnect, |
||||
.interrupt = taos_interrupt, |
||||
}; |
||||
|
||||
static int __init taos_init(void) |
||||
{ |
||||
return serio_register_driver(&taos_drv); |
||||
} |
||||
|
||||
static void __exit taos_exit(void) |
||||
{ |
||||
serio_unregister_driver(&taos_drv); |
||||
} |
||||
|
||||
MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>"); |
||||
MODULE_DESCRIPTION("TAOS evaluation module driver"); |
||||
MODULE_LICENSE("GPL"); |
||||
|
||||
module_init(taos_init); |
||||
module_exit(taos_exit); |
@ -0,0 +1,259 @@ |
||||
/*
|
||||
* Dallas Semiconductor DS1682 Elapsed Time Recorder device driver |
||||
* |
||||
* Written by: Grant Likely <grant.likely@secretlab.ca> |
||||
* |
||||
* Copyright (C) 2007 Secret Lab Technologies Ltd. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License version 2 as |
||||
* published by the Free Software Foundation. |
||||
*/ |
||||
|
||||
/*
|
||||
* The DS1682 elapsed timer recorder is a simple device that implements |
||||
* one elapsed time counter, one event counter, an alarm signal and 10 |
||||
* bytes of general purpose EEPROM. |
||||
* |
||||
* This driver provides access to the DS1682 counters and user data via |
||||
* the sysfs. The following attributes are added to the device node: |
||||
* elapsed_time (u32): Total elapsed event time in ms resolution |
||||
* alarm_time (u32): When elapsed time exceeds the value in alarm_time, |
||||
* then the alarm pin is asserted. |
||||
* event_count (u16): number of times the event pin has gone low. |
||||
* eeprom (u8[10]): general purpose EEPROM |
||||
* |
||||
* Counter registers and user data are both read/write unless the device |
||||
* has been write protected. This driver does not support turning off write |
||||
* protection. Once write protection is turned on, it is impossible to |
||||
* turn it off again, so I have left the feature out of this driver to avoid |
||||
* accidental enabling, but it is trivial to add write protect support. |
||||
* |
||||
*/ |
||||
|
||||
#include <linux/module.h> |
||||
#include <linux/init.h> |
||||
#include <linux/slab.h> |
||||
#include <linux/i2c.h> |
||||
#include <linux/string.h> |
||||
#include <linux/list.h> |
||||
#include <linux/sysfs.h> |
||||
#include <linux/ctype.h> |
||||
#include <linux/hwmon-sysfs.h> |
||||
|
||||
/* Device registers */ |
||||
#define DS1682_REG_CONFIG 0x00 |
||||
#define DS1682_REG_ALARM 0x01 |
||||
#define DS1682_REG_ELAPSED 0x05 |
||||
#define DS1682_REG_EVT_CNTR 0x09 |
||||
#define DS1682_REG_EEPROM 0x0b |
||||
#define DS1682_REG_RESET 0x1d |
||||
#define DS1682_REG_WRITE_DISABLE 0x1e |
||||
#define DS1682_REG_WRITE_MEM_DISABLE 0x1f |
||||
|
||||
#define DS1682_EEPROM_SIZE 10 |
||||
|
||||
/*
|
||||
* Generic counter attributes |
||||
*/ |
||||
static ssize_t ds1682_show(struct device *dev, struct device_attribute *attr, |
||||
char *buf) |
||||
{ |
||||
struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); |
||||
struct i2c_client *client = to_i2c_client(dev); |
||||
__le32 val = 0; |
||||
int rc; |
||||
|
||||
dev_dbg(dev, "ds1682_show() called on %s\n", attr->attr.name); |
||||
|
||||
/* Read the register */ |
||||
rc = i2c_smbus_read_i2c_block_data(client, sattr->index, sattr->nr, |
||||
(u8 *) & val); |
||||
if (rc < 0) |
||||
return -EIO; |
||||
|
||||
/* Special case: the 32 bit regs are time values with 1/4s
|
||||
* resolution, scale them up to milliseconds */ |
||||
if (sattr->nr == 4) |
||||
return sprintf(buf, "%llu\n", ((u64) le32_to_cpu(val)) * 250); |
||||
|
||||
/* Format the output string and return # of bytes */ |
||||
return sprintf(buf, "%li\n", (long)le32_to_cpu(val)); |
||||
} |
||||
|
||||
static ssize_t ds1682_store(struct device *dev, struct device_attribute *attr, |
||||
const char *buf, size_t count) |
||||
{ |
||||
struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); |
||||
struct i2c_client *client = to_i2c_client(dev); |
||||
char *endp; |
||||
u64 val; |
||||
__le32 val_le; |
||||
int rc; |
||||
|
||||
dev_dbg(dev, "ds1682_store() called on %s\n", attr->attr.name); |
||||
|
||||
/* Decode input */ |
||||
val = simple_strtoull(buf, &endp, 0); |
||||
if (buf == endp) { |
||||
dev_dbg(dev, "input string not a number\n"); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
/* Special case: the 32 bit regs are time values with 1/4s
|
||||
* resolution, scale input down to quarter-seconds */ |
||||
if (sattr->nr == 4) |
||||
do_div(val, 250); |
||||
|
||||
/* write out the value */ |
||||
val_le = cpu_to_le32(val); |
||||
rc = i2c_smbus_write_i2c_block_data(client, sattr->index, sattr->nr, |
||||
(u8 *) & val_le); |
||||
if (rc < 0) { |
||||
dev_err(dev, "register write failed; reg=0x%x, size=%i\n", |
||||
sattr->index, sattr->nr); |
||||
return -EIO; |
||||
} |
||||
|
||||
return count; |
||||
} |
||||
|
||||
/*
|
||||
* Simple register attributes |
||||
*/ |
||||
static SENSOR_DEVICE_ATTR_2(elapsed_time, S_IRUGO | S_IWUSR, ds1682_show, |
||||
ds1682_store, 4, DS1682_REG_ELAPSED); |
||||
static SENSOR_DEVICE_ATTR_2(alarm_time, S_IRUGO | S_IWUSR, ds1682_show, |
||||
ds1682_store, 4, DS1682_REG_ALARM); |
||||
static SENSOR_DEVICE_ATTR_2(event_count, S_IRUGO | S_IWUSR, ds1682_show, |
||||
ds1682_store, 2, DS1682_REG_EVT_CNTR); |
||||
|
||||
static const struct attribute_group ds1682_group = { |
||||
.attrs = (struct attribute *[]) { |
||||
&sensor_dev_attr_elapsed_time.dev_attr.attr, |
||||
&sensor_dev_attr_alarm_time.dev_attr.attr, |
||||
&sensor_dev_attr_event_count.dev_attr.attr, |
||||
NULL, |
||||
}, |
||||
}; |
||||
|
||||
/*
|
||||
* User data attribute |
||||
*/ |
||||
static ssize_t ds1682_eeprom_read(struct kobject *kobj, char *buf, loff_t off, |
||||
size_t count) |
||||
{ |
||||
struct i2c_client *client = kobj_to_i2c_client(kobj); |
||||
int rc; |
||||
|
||||
dev_dbg(&client->dev, "ds1682_eeprom_read(p=%p, off=%lli, c=%zi)\n", |
||||
buf, off, count); |
||||
|
||||
if (off >= DS1682_EEPROM_SIZE) |
||||
return 0; |
||||
|
||||
if (off + count > DS1682_EEPROM_SIZE) |
||||
count = DS1682_EEPROM_SIZE - off; |
||||
|
||||
rc = i2c_smbus_read_i2c_block_data(client, DS1682_REG_EEPROM + off, |
||||
count, buf); |
||||
if (rc < 0) |
||||
return -EIO; |
||||
|
||||
return count; |
||||
} |
||||
|
||||
static ssize_t ds1682_eeprom_write(struct kobject *kobj, char *buf, loff_t off, |
||||
size_t count) |
||||
{ |
||||
struct i2c_client *client = kobj_to_i2c_client(kobj); |
||||
|
||||
dev_dbg(&client->dev, "ds1682_eeprom_write(p=%p, off=%lli, c=%zi)\n", |
||||
buf, off, count); |
||||
|
||||
if (off >= DS1682_EEPROM_SIZE) |
||||
return -ENOSPC; |
||||
|
||||
if (off + count > DS1682_EEPROM_SIZE) |
||||
count = DS1682_EEPROM_SIZE - off; |
||||
|
||||
/* Write out to the device */ |
||||
if (i2c_smbus_write_i2c_block_data(client, DS1682_REG_EEPROM + off, |
||||
count, buf) < 0) |
||||
return -EIO; |
||||
|
||||
return count; |
||||
} |
||||
|
||||
static struct bin_attribute ds1682_eeprom_attr = { |
||||
.attr = { |
||||
.name = "eeprom", |
||||
.mode = S_IRUGO | S_IWUSR, |
||||
.owner = THIS_MODULE, |
||||
}, |
||||
.size = DS1682_EEPROM_SIZE, |
||||
.read = ds1682_eeprom_read, |
||||
.write = ds1682_eeprom_write, |
||||
}; |
||||
|
||||
/*
|
||||
* Called when a ds1682 device is matched with this driver |
||||
*/ |
||||
static int ds1682_probe(struct i2c_client *client) |
||||
{ |
||||
int rc; |
||||
|
||||
if (!i2c_check_functionality(client->adapter, |
||||
I2C_FUNC_SMBUS_I2C_BLOCK)) { |
||||
dev_err(&client->dev, "i2c bus does not support the ds1682\n"); |
||||
rc = -ENODEV; |
||||
goto exit; |
||||
} |
||||
|
||||
rc = sysfs_create_group(&client->dev.kobj, &ds1682_group); |
||||
if (rc) |
||||
goto exit; |
||||
|
||||
rc = sysfs_create_bin_file(&client->dev.kobj, &ds1682_eeprom_attr); |
||||
if (rc) |
||||
goto exit_bin_attr; |
||||
|
||||
return 0; |
||||
|
||||
exit_bin_attr: |
||||
sysfs_remove_group(&client->dev.kobj, &ds1682_group); |
||||
exit: |
||||
return rc; |
||||
} |
||||
|
||||
static int ds1682_remove(struct i2c_client *client) |
||||
{ |
||||
sysfs_remove_bin_file(&client->dev.kobj, &ds1682_eeprom_attr); |
||||
sysfs_remove_group(&client->dev.kobj, &ds1682_group); |
||||
return 0; |
||||
} |
||||
|
||||
static struct i2c_driver ds1682_driver = { |
||||
.driver = { |
||||
.name = "ds1682", |
||||
}, |
||||
.probe = ds1682_probe, |
||||
.remove = ds1682_remove, |
||||
}; |
||||
|
||||
static int __init ds1682_init(void) |
||||
{ |
||||
return i2c_add_driver(&ds1682_driver); |
||||
} |
||||
|
||||
static void __exit ds1682_exit(void) |
||||
{ |
||||
i2c_del_driver(&ds1682_driver); |
||||
} |
||||
|
||||
MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>"); |
||||
MODULE_DESCRIPTION("DS1682 Elapsed Time Indicator driver"); |
||||
MODULE_LICENSE("GPL"); |
||||
|
||||
module_init(ds1682_init); |
||||
module_exit(ds1682_exit); |
@ -0,0 +1,460 @@ |
||||
/*
|
||||
* tsl2550.c - Linux kernel modules for ambient light sensor |
||||
* |
||||
* Copyright (C) 2007 Rodolfo Giometti <giometti@linux.it> |
||||
* Copyright (C) 2007 Eurotech S.p.A. <info@eurotech.it> |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, write to the Free Software |
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
||||
*/ |
||||
|
||||
#include <linux/module.h> |
||||
#include <linux/init.h> |
||||
#include <linux/slab.h> |
||||
#include <linux/i2c.h> |
||||
#include <linux/mutex.h> |
||||
#include <linux/delay.h> |
||||
|
||||
#define TSL2550_DRV_NAME "tsl2550" |
||||
#define DRIVER_VERSION "1.1.1" |
||||
|
||||
/*
|
||||
* Defines |
||||
*/ |
||||
|
||||
#define TSL2550_POWER_DOWN 0x00 |
||||
#define TSL2550_POWER_UP 0x03 |
||||
#define TSL2550_STANDARD_RANGE 0x18 |
||||
#define TSL2550_EXTENDED_RANGE 0x1d |
||||
#define TSL2550_READ_ADC0 0x43 |
||||
#define TSL2550_READ_ADC1 0x83 |
||||
|
||||
/*
|
||||
* Structs |
||||
*/ |
||||
|
||||
struct tsl2550_data { |
||||
struct i2c_client *client; |
||||
struct mutex update_lock; |
||||
|
||||
unsigned int power_state : 1; |
||||
unsigned int operating_mode : 1; |
||||
}; |
||||
|
||||
/*
|
||||
* Global data |
||||
*/ |
||||
|
||||
static const u8 TSL2550_MODE_RANGE[2] = { |
||||
TSL2550_STANDARD_RANGE, TSL2550_EXTENDED_RANGE, |
||||
}; |
||||
|
||||
/*
|
||||
* Management functions |
||||
*/ |
||||
|
||||
static int tsl2550_set_operating_mode(struct i2c_client *client, int mode) |
||||
{ |
||||
struct tsl2550_data *data = i2c_get_clientdata(client); |
||||
|
||||
int ret = i2c_smbus_write_byte(client, TSL2550_MODE_RANGE[mode]); |
||||
|
||||
data->operating_mode = mode; |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
static int tsl2550_set_power_state(struct i2c_client *client, int state) |
||||
{ |
||||
struct tsl2550_data *data = i2c_get_clientdata(client); |
||||
int ret; |
||||
|
||||
if (state == 0) |
||||
ret = i2c_smbus_write_byte(client, TSL2550_POWER_DOWN); |
||||
else { |
||||
ret = i2c_smbus_write_byte(client, TSL2550_POWER_UP); |
||||
|
||||
/* On power up we should reset operating mode also... */ |
||||
tsl2550_set_operating_mode(client, data->operating_mode); |
||||
} |
||||
|
||||
data->power_state = state; |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
static int tsl2550_get_adc_value(struct i2c_client *client, u8 cmd) |
||||
{ |
||||
unsigned long end; |
||||
int loop = 0, ret = 0; |
||||
|
||||
/*
|
||||
* Read ADC channel waiting at most 400ms (see data sheet for further |
||||
* info). |
||||
* To avoid long busy wait we spin for few milliseconds then |
||||
* start sleeping. |
||||
*/ |
||||
end = jiffies + msecs_to_jiffies(400); |
||||
while (time_before(jiffies, end)) { |
||||
i2c_smbus_write_byte(client, cmd); |
||||
|
||||
if (loop++ < 5) |
||||
mdelay(1); |
||||
else |
||||
msleep(1); |
||||
|
||||
ret = i2c_smbus_read_byte(client); |
||||
if (ret < 0) |
||||
return ret; |
||||
else if (ret & 0x0080) |
||||
break; |
||||
} |
||||
if (!(ret & 0x80)) |
||||
return -EIO; |
||||
return ret & 0x7f; /* remove the "valid" bit */ |
||||
} |
||||
|
||||
/*
|
||||
* LUX calculation |
||||
*/ |
||||
|
||||
#define TSL2550_MAX_LUX 1846 |
||||
|
||||
static const u8 ratio_lut[] = { |
||||
100, 100, 100, 100, 100, 100, 100, 100, |
||||
100, 100, 100, 100, 100, 100, 99, 99, |
||||
99, 99, 99, 99, 99, 99, 99, 99, |
||||
99, 99, 99, 98, 98, 98, 98, 98, |
||||
98, 98, 97, 97, 97, 97, 97, 96, |
||||
96, 96, 96, 95, 95, 95, 94, 94, |
||||
93, 93, 93, 92, 92, 91, 91, 90, |
||||
89, 89, 88, 87, 87, 86, 85, 84, |
||||
83, 82, 81, 80, 79, 78, 77, 75, |
||||
74, 73, 71, 69, 68, 66, 64, 62, |
||||
60, 58, 56, 54, 52, 49, 47, 44, |
||||
42, 41, 40, 40, 39, 39, 38, 38, |
||||
37, 37, 37, 36, 36, 36, 35, 35, |
||||
35, 35, 34, 34, 34, 34, 33, 33, |
||||
33, 33, 32, 32, 32, 32, 32, 31, |
||||
31, 31, 31, 31, 30, 30, 30, 30, |
||||
30, |
||||
}; |
||||
|
||||
static const u16 count_lut[] = { |
||||
0, 1, 2, 3, 4, 5, 6, 7, |
||||
8, 9, 10, 11, 12, 13, 14, 15, |
||||
16, 18, 20, 22, 24, 26, 28, 30, |
||||
32, 34, 36, 38, 40, 42, 44, 46, |
||||
49, 53, 57, 61, 65, 69, 73, 77, |
||||
81, 85, 89, 93, 97, 101, 105, 109, |
||||
115, 123, 131, 139, 147, 155, 163, 171, |
||||
179, 187, 195, 203, 211, 219, 227, 235, |
||||
247, 263, 279, 295, 311, 327, 343, 359, |
||||
375, 391, 407, 423, 439, 455, 471, 487, |
||||
511, 543, 575, 607, 639, 671, 703, 735, |
||||
767, 799, 831, 863, 895, 927, 959, 991, |
||||
1039, 1103, 1167, 1231, 1295, 1359, 1423, 1487, |
||||
1551, 1615, 1679, 1743, 1807, 1871, 1935, 1999, |
||||
2095, 2223, 2351, 2479, 2607, 2735, 2863, 2991, |
||||
3119, 3247, 3375, 3503, 3631, 3759, 3887, 4015, |
||||
}; |
||||
|
||||
/*
|
||||
* This function is described into Taos TSL2550 Designer's Notebook |
||||
* pages 2, 3. |
||||
*/ |
||||
static int tsl2550_calculate_lux(u8 ch0, u8 ch1) |
||||
{ |
||||
unsigned int lux; |
||||
|
||||
/* Look up count from channel values */ |
||||
u16 c0 = count_lut[ch0]; |
||||
u16 c1 = count_lut[ch1]; |
||||
|
||||
/*
|
||||
* Calculate ratio. |
||||
* Note: the "128" is a scaling factor |
||||
*/ |
||||
u8 r = 128; |
||||
|
||||
/* Avoid division by 0 and count 1 cannot be greater than count 0 */ |
||||
if (c0 && (c1 <= c0)) |
||||
r = c1 * 128 / c0; |
||||
else |
||||
return -1; |
||||
|
||||
/* Calculate LUX */ |
||||
lux = ((c0 - c1) * ratio_lut[r]) / 256; |
||||
|
||||
/* LUX range check */ |
||||
return lux > TSL2550_MAX_LUX ? TSL2550_MAX_LUX : lux; |
||||
} |
||||
|
||||
/*
|
||||
* SysFS support |
||||
*/ |
||||
|
||||
static ssize_t tsl2550_show_power_state(struct device *dev, |
||||
struct device_attribute *attr, char *buf) |
||||
{ |
||||
struct tsl2550_data *data = i2c_get_clientdata(to_i2c_client(dev)); |
||||
|
||||
return sprintf(buf, "%u\n", data->power_state); |
||||
} |
||||
|
||||
static ssize_t tsl2550_store_power_state(struct device *dev, |
||||
struct device_attribute *attr, const char *buf, size_t count) |
||||
{ |
||||
struct i2c_client *client = to_i2c_client(dev); |
||||
struct tsl2550_data *data = i2c_get_clientdata(client); |
||||
unsigned long val = simple_strtoul(buf, NULL, 10); |
||||
int ret; |
||||
|
||||
if (val < 0 || val > 1) |
||||
return -EINVAL; |
||||
|
||||
mutex_lock(&data->update_lock); |
||||
ret = tsl2550_set_power_state(client, val); |
||||
mutex_unlock(&data->update_lock); |
||||
|
||||
if (ret < 0) |
||||
return ret; |
||||
|
||||
return count; |
||||
} |
||||
|
||||
static DEVICE_ATTR(power_state, S_IWUSR | S_IRUGO, |
||||
tsl2550_show_power_state, tsl2550_store_power_state); |
||||
|
||||
static ssize_t tsl2550_show_operating_mode(struct device *dev, |
||||
struct device_attribute *attr, char *buf) |
||||
{ |
||||
struct tsl2550_data *data = i2c_get_clientdata(to_i2c_client(dev)); |
||||
|
||||
return sprintf(buf, "%u\n", data->operating_mode); |
||||
} |
||||
|
||||
static ssize_t tsl2550_store_operating_mode(struct device *dev, |
||||
struct device_attribute *attr, const char *buf, size_t count) |
||||
{ |
||||
struct i2c_client *client = to_i2c_client(dev); |
||||
struct tsl2550_data *data = i2c_get_clientdata(client); |
||||
unsigned long val = simple_strtoul(buf, NULL, 10); |
||||
int ret; |
||||
|
||||
if (val < 0 || val > 1) |
||||
return -EINVAL; |
||||
|
||||
if (data->power_state == 0) |
||||
return -EBUSY; |
||||
|
||||
mutex_lock(&data->update_lock); |
||||
ret = tsl2550_set_operating_mode(client, val); |
||||
mutex_unlock(&data->update_lock); |
||||
|
||||
if (ret < 0) |
||||
return ret; |
||||
|
||||
return count; |
||||
} |
||||
|
||||
static DEVICE_ATTR(operating_mode, S_IWUSR | S_IRUGO, |
||||
tsl2550_show_operating_mode, tsl2550_store_operating_mode); |
||||
|
||||
static ssize_t __tsl2550_show_lux(struct i2c_client *client, char *buf) |
||||
{ |
||||
u8 ch0, ch1; |
||||
int ret; |
||||
|
||||
ret = tsl2550_get_adc_value(client, TSL2550_READ_ADC0); |
||||
if (ret < 0) |
||||
return ret; |
||||
ch0 = ret; |
||||
|
||||
mdelay(1); |
||||
|
||||
ret = tsl2550_get_adc_value(client, TSL2550_READ_ADC1); |
||||
if (ret < 0) |
||||
return ret; |
||||
ch1 = ret; |
||||
|
||||
/* Do the job */ |
||||
ret = tsl2550_calculate_lux(ch0, ch1); |
||||
if (ret < 0) |
||||
return ret; |
||||
|
||||
return sprintf(buf, "%d\n", ret); |
||||
} |
||||
|
||||
static ssize_t tsl2550_show_lux1_input(struct device *dev, |
||||
struct device_attribute *attr, char *buf) |
||||
{ |
||||
struct i2c_client *client = to_i2c_client(dev); |
||||
struct tsl2550_data *data = i2c_get_clientdata(client); |
||||
int ret; |
||||
|
||||
/* No LUX data if not operational */ |
||||
if (!data->power_state) |
||||
return -EBUSY; |
||||
|
||||
mutex_lock(&data->update_lock); |
||||
ret = __tsl2550_show_lux(client, buf); |
||||
mutex_unlock(&data->update_lock); |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
static DEVICE_ATTR(lux1_input, S_IRUGO, |
||||
tsl2550_show_lux1_input, NULL); |
||||
|
||||
static struct attribute *tsl2550_attributes[] = { |
||||
&dev_attr_power_state.attr, |
||||
&dev_attr_operating_mode.attr, |
||||
&dev_attr_lux1_input.attr, |
||||
NULL |
||||
}; |
||||
|
||||
static const struct attribute_group tsl2550_attr_group = { |
||||
.attrs = tsl2550_attributes, |
||||
}; |
||||
|
||||
/*
|
||||
* Initialization function |
||||
*/ |
||||
|
||||
static int tsl2550_init_client(struct i2c_client *client) |
||||
{ |
||||
struct tsl2550_data *data = i2c_get_clientdata(client); |
||||
int err; |
||||
|
||||
/*
|
||||
* Probe the chip. To do so we try to power up the device and then to |
||||
* read back the 0x03 code |
||||
*/ |
||||
err = i2c_smbus_write_byte(client, TSL2550_POWER_UP); |
||||
if (err < 0) |
||||
return err; |
||||
mdelay(1); |
||||
if (i2c_smbus_read_byte(client) != TSL2550_POWER_UP) |
||||
return -ENODEV; |
||||
data->power_state = 1; |
||||
|
||||
/* Set the default operating mode */ |
||||
err = i2c_smbus_write_byte(client, |
||||
TSL2550_MODE_RANGE[data->operating_mode]); |
||||
if (err < 0) |
||||
return err; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
/*
|
||||
* I2C init/probing/exit functions |
||||
*/ |
||||
|
||||
static struct i2c_driver tsl2550_driver; |
||||
static int __devinit tsl2550_probe(struct i2c_client *client) |
||||
{ |
||||
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); |
||||
struct tsl2550_data *data; |
||||
int *opmode, err = 0; |
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) { |
||||
err = -EIO; |
||||
goto exit; |
||||
} |
||||
|
||||
data = kzalloc(sizeof(struct tsl2550_data), GFP_KERNEL); |
||||
if (!data) { |
||||
err = -ENOMEM; |
||||
goto exit; |
||||
} |
||||
data->client = client; |
||||
i2c_set_clientdata(client, data); |
||||
|
||||
/* Check platform data */ |
||||
opmode = client->dev.platform_data; |
||||
if (opmode) { |
||||
if (*opmode < 0 || *opmode > 1) { |
||||
dev_err(&client->dev, "invalid operating_mode (%d)\n", |
||||
*opmode); |
||||
err = -EINVAL; |
||||
goto exit_kfree; |
||||
} |
||||
data->operating_mode = *opmode; |
||||
} else |
||||
data->operating_mode = 0; /* default mode is standard */ |
||||
dev_info(&client->dev, "%s operating mode\n", |
||||
data->operating_mode ? "extended" : "standard"); |
||||
|
||||
mutex_init(&data->update_lock); |
||||
|
||||
/* Initialize the TSL2550 chip */ |
||||
err = tsl2550_init_client(client); |
||||
if (err) |
||||
goto exit_kfree; |
||||
|
||||
/* Register sysfs hooks */ |
||||
err = sysfs_create_group(&client->dev.kobj, &tsl2550_attr_group); |
||||
if (err) |
||||
goto exit_kfree; |
||||
|
||||
dev_info(&client->dev, "support ver. %s enabled\n", DRIVER_VERSION); |
||||
|
||||
return 0; |
||||
|
||||
exit_kfree: |
||||
kfree(data); |
||||
exit: |
||||
return err; |
||||
} |
||||
|
||||
static int __devexit tsl2550_remove(struct i2c_client *client) |
||||
{ |
||||
sysfs_remove_group(&client->dev.kobj, &tsl2550_attr_group); |
||||
|
||||
/* Power down the device */ |
||||
tsl2550_set_power_state(client, 0); |
||||
|
||||
kfree(i2c_get_clientdata(client)); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static struct i2c_driver tsl2550_driver = { |
||||
.driver = { |
||||
.name = TSL2550_DRV_NAME, |
||||
.owner = THIS_MODULE, |
||||
}, |
||||
.probe = tsl2550_probe, |
||||
.remove = __devexit_p(tsl2550_remove), |
||||
}; |
||||
|
||||
static int __init tsl2550_init(void) |
||||
{ |
||||
return i2c_add_driver(&tsl2550_driver); |
||||
} |
||||
|
||||
static void __exit tsl2550_exit(void) |
||||
{ |
||||
i2c_del_driver(&tsl2550_driver); |
||||
} |
||||
|
||||
MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>"); |
||||
MODULE_DESCRIPTION("TSL2550 ambient light sensor driver"); |
||||
MODULE_LICENSE("GPL"); |
||||
MODULE_VERSION(DRIVER_VERSION); |
||||
|
||||
module_init(tsl2550_init); |
||||
module_exit(tsl2550_exit); |
Loading…
Reference in new issue