diff options
author | Alex Gonzalez <alex.gonzalez@digi.com> | 2012-01-17 18:35:55 +0100 |
---|---|---|
committer | Alex Gonzalez <alex.gonzalez@digi.com> | 2012-01-24 13:42:22 +0100 |
commit | 87ac4650973e1e2d9ff372ff41acf59c289b60a2 (patch) | |
tree | 821cc5ab88376c2a538a729db6e265147b925385 | |
parent | aad4aa6cfd4e960074ca4455fc44fbd88917476c (diff) |
da9052: Integrate a PMIC I2C access workaround.
This patch was provided by Dialog and it doesn't seem to be yet integrated
with Freescale upstream.
It basically does a write flush on a set of registers.
Dialog contact Christophe.Molle@diasemi.com
Signed-off-by: Alex Gonzalez <alex.gonzalez@digi.com>
(cherry picked from commit 8d5aa68cad489a1bba040dd32c92b0dd565cc881)
-rw-r--r-- | drivers/mfd/da9052-i2c.c | 336 |
1 files changed, 177 insertions, 159 deletions
diff --git a/drivers/mfd/da9052-i2c.c b/drivers/mfd/da9052-i2c.c index 3a1011682224..1a9fd368db66 100644 --- a/drivers/mfd/da9052-i2c.c +++ b/drivers/mfd/da9052-i2c.c @@ -19,6 +19,8 @@ static struct da9052 *da9052_i2c; #define I2C_CONNECTED 0 +#define I2C_BUG_WORKAROUND + static int da9052_i2c_is_connected(void) { struct da9052_ssc_msg msg; @@ -76,6 +78,15 @@ static int __devinit da9052_i2c_probe(struct i2c_client *client, /* Validate I2C connectivity */ if ( I2C_CONNECTED == da9052_i2c_is_connected()) { + /* Enable Repeated Write Mode permanently */ + struct da9052_ssc_msg ctrl_msg = + {DA9052_CONTROLB_REG, DA9052_CONTROLB_WRITEMODE}; + if (da9052_i2c_write(da9052_i2c, &ctrl_msg) < 0) { + dev_info(&da9052_i2c->i2c_client->dev, + "%s: repeated mode not set!!\n", __func__); + return -ENODEV; + } + /* I2C is connected */ da9052_i2c->connecting_device = I2C; if( 0!= da9052_ssc_init(da9052_i2c) ) @@ -100,30 +111,77 @@ static int da9052_i2c_remove(struct i2c_client *client) return 0; } +#ifdef I2C_BUG_WORKAROUND +#define I2C_DUMMY_REG 0xFF +const unsigned char i2c_flush_data[] = {I2C_DUMMY_REG, 0xFF}; + +#if 1 /* Enable safe register addresses checking */ +/* DLG TODO: Add read only registers here (they are not affected with write) */ +static inline int da9052_is_i2c_reg_safe(unsigned char reg) +{ + const char safe_table[256] = { + [DA9052_STATUSA_REG] = 1, + [DA9052_STATUSB_REG] = 1, + [DA9052_STATUSC_REG] = 1, + [DA9052_STATUSD_REG] = 1, + [DA9052_EVENTA_REG] = 1, + [DA9052_EVENTB_REG] = 1, + [DA9052_EVENTC_REG] = 1, + [DA9052_EVENTD_REG] = 1, + [DA9052_ADCRESL_REG] = 1, + [DA9052_ADCRESH_REG] = 1, + [DA9052_VDDRES_REG] = 1, + [DA9052_ICHGAV_REG] = 1, + [DA9052_TBATRES_REG] = 1, + [DA9052_ADCIN4RES_REG] = 1, + [DA9052_ADCIN5RES_REG] = 1, + [DA9052_ADCIN6RES_REG] = 1, + [DA9052_TJUNCRES_REG] = 1, + [DA9052_TSIXMSB_REG] = 1, + [DA9052_TSIYMSB_REG] = 1, + [DA9052_TSILSB_REG] = 1, + [DA9052_TSIZMSB_REG] = 1, + [I2C_DUMMY_REG] = 1, /* Dummy reg must be a save reg */ + }; + + return safe_table[reg]; +} +#else +static inline int da9052_is_i2c_reg_safe(unsigned char reg) +{ + return 0; +} +#endif +#endif + int da9052_i2c_write(struct da9052 *da9052, struct da9052_ssc_msg *msg) { struct i2c_msg i2cmsg; - unsigned char buf[2] = {0}; + unsigned char buf[4]; int ret = 0; - /* Copy the ssc msg to local character buffer */ - buf[0] = msg->addr; - buf[1] = msg->data; - /*Construct a i2c msg for a da9052 driver ssc message request */ i2cmsg.addr = da9052->slave_addr; - i2cmsg.len = 2; i2cmsg.buf = buf; - - /* To write the data on I2C set flag to zero */ i2cmsg.flags = 0; + i2cmsg.len = 2; - /* Start the i2c transfer by calling host i2c driver function */ - ret = i2c_transfer(da9052->adapter, &i2cmsg, 1); + /* Copy the ssc msg and additional data to flush chip I2C registers */ + buf[0] = msg->addr; + buf[1] = msg->data; - if (ret < 0) { - dev_info(&da9052->i2c_client->dev,\ - "_%s:master_xfer Failed!!\n", __func__); +#ifdef I2C_BUG_WORKAROUND + /* Test, whether register to be accessed needs to be flushed */ + if (!da9052_is_i2c_reg_safe(msg->addr)) { + i2cmsg.len = 4; + buf[2] = i2c_flush_data[0]; + buf[3] = i2c_flush_data[1]; + } +#endif + /* Start the i2c transfer by calling host i2c driver function */ + ret = i2c_transfer(da9052->adapter, &i2cmsg, 1); if (ret < 0) { + dev_info(&da9052->i2c_client->dev, + "%s: i2c_transfer failed!!!\n", __func__); return ret; } @@ -132,10 +190,8 @@ int da9052_i2c_write(struct da9052 *da9052, struct da9052_ssc_msg *msg) int da9052_i2c_read(struct da9052 *da9052, struct da9052_ssc_msg *msg) { - - /*Get the da9052_i2c client details*/ unsigned char buf[2] = {0, 0}; - struct i2c_msg i2cmsg[2]; + struct i2c_msg i2cmsg[3]; int ret = 0; /* Copy SSC Msg to local character buffer */ @@ -145,107 +201,86 @@ int da9052_i2c_read(struct da9052 *da9052, struct da9052_ssc_msg *msg) i2cmsg[0].addr = da9052->slave_addr ; i2cmsg[0].len = 1; i2cmsg[0].buf = &buf[0]; - - /*To write the data on I2C set flag to zero */ i2cmsg[0].flags = 0; - /* Read the data from da9052*/ /*Construct a i2c msg for a da9052 driver ssc message request */ i2cmsg[1].addr = da9052->slave_addr ; i2cmsg[1].len = 1; i2cmsg[1].buf = &buf[1]; - - /*To read the data on I2C set flag to I2C_M_RD */ i2cmsg[1].flags = I2C_M_RD; - /* Start the i2c transfer by calling host i2c driver function */ + /* Standard read transfer */ ret = i2c_transfer(da9052->adapter, i2cmsg, 2); - if (ret < 0) { - dev_info(&da9052->i2c_client->dev,\ - "2 - %s:master_xfer Failed!!\n", __func__); - return ret; + +#ifdef I2C_BUG_WORKAROUND + /* Test, whether register to be accessed needs to be flushed */ + if (!da9052_is_i2c_reg_safe(msg->addr)) { + i2cmsg[2].addr = da9052->slave_addr; + i2cmsg[2].len = 2; + i2cmsg[2].flags = 0; /* Write operation */ + /* i2c_flush_data is only to read from */ + i2cmsg[2].buf = (unsigned char *)i2c_flush_data; + + /* Read transfer with additional flush write */ + ret = i2c_transfer(da9052->adapter, &i2cmsg[2], 1); } +#endif - msg->data = *i2cmsg[1].buf; + if (ret < 0) { + dev_info(&da9052->i2c_client->dev, + "%s: i2c_transfer failed!!!\n", __func__); + return ret; + } + msg->data = buf[1]; return 0; } int da9052_i2c_write_many(struct da9052 *da9052, struct da9052_ssc_msg *sscmsg, int msg_no) { - struct i2c_msg i2cmsg; - unsigned char data_buf[MAX_READ_WRITE_CNT+1]; - struct da9052_ssc_msg ctrlb_msg; - struct da9052_ssc_msg *msg_queue = sscmsg; int ret = 0; - /* Flag to check if requested registers are contiguous */ - unsigned char cont_data = 1; - unsigned char cnt = 0; - - /* Check if requested registers are contiguous */ - for (cnt = 1; cnt < msg_no; cnt++) { - if ((msg_queue[cnt].addr - msg_queue[cnt-1].addr) != 1) { - /* Difference is not 1, i.e. non-contiguous registers */ - cont_data = 0; - break; - } - } - - if (cont_data == 0) { - /* Requested registers are non-contiguous */ - for (cnt = 0; cnt < msg_no; cnt++) { - ret = da9052->write(da9052, &msg_queue[cnt]); - if (ret != 0) - return ret; - } - return 0; - } - /* - * Requested registers are contiguous - * or PAGE WRITE sequence of I2C transactions is as below - * (slave_addr + reg_addr + data_1 + data_2 + ...) - * First read current WRITE MODE via CONTROL_B register of DA9052 - */ - ctrlb_msg.addr = DA9052_CONTROLB_REG; - ctrlb_msg.data = 0x0; - ret = da9052->read(da9052, &ctrlb_msg); - - if (ret != 0) - return ret; - - /* Check if PAGE WRITE mode is set */ - if (ctrlb_msg.data & DA9052_CONTROLB_WRITEMODE) { - /* REPEAT WRITE mode is configured */ - /* Now set DA9052 into PAGE WRITE mode */ - ctrlb_msg.data &= ~DA9052_CONTROLB_WRITEMODE; - ret = da9052->write(da9052, &ctrlb_msg); - - if (ret != 0) - return ret; - } - - /* Put first register address */ - data_buf[0] = msg_queue[0].addr; - - for (cnt = 0; cnt < msg_no; cnt++) - data_buf[cnt+1] = msg_queue[cnt].data; + int cnt; + unsigned char *data_ptr; +#ifdef I2C_BUG_WORKAROUND + unsigned char data_buf[2*MAX_READ_WRITE_CNT + 2]; +#else + unsigned char data_buf[2*MAX_READ_WRITE_CNT]; +#endif + + BUG_ON(msg_no < 0); + BUG_ON(msg_no >= MAX_READ_WRITE_CNT); +/* + printk(KERN_CRIT "D9053: %s: write %d messages starting from %hhu reg\n", + __func__, msg_no, sscmsg->addr); +*/ - /* Construct a i2c msg for PAGE WRITE */ + /* Construct a i2c msg for REPEATED WRITE */ i2cmsg.addr = da9052->slave_addr ; - /* First register address + all data*/ - i2cmsg.len = (msg_no + 1); + i2cmsg.len = 2*msg_no; i2cmsg.buf = data_buf; - - /*To write the data on I2C set flag to zero */ i2cmsg.flags = 0; + for (data_ptr = data_buf, cnt = msg_no; cnt; cnt--) { + *(data_ptr++) = sscmsg->addr; + *(data_ptr++) = sscmsg->data; + sscmsg++; + } +#ifdef I2C_BUG_WORKAROUND + /* Test, whether last register to be accessed needs to be flushed */ + if (msg_no != 0 && !da9052_is_i2c_reg_safe(sscmsg[-1].addr)) { + i2cmsg.len += 2; + *(data_ptr++) = i2c_flush_data[0]; + *data_ptr = i2c_flush_data[1]; + } +#endif + /* Start the i2c transfer by calling host i2c driver function */ ret = i2c_transfer(da9052->adapter, &i2cmsg, 1); if (ret < 0) { - dev_info(&da9052->i2c_client->dev,\ - "1 - i2c_transfer function falied in [%s]!!!\n", __func__); + dev_info(&da9052->i2c_client->dev, + "%s: i2c_transfer failed!!!\n", __func__); return ret; } @@ -255,83 +290,66 @@ int da9052_i2c_write_many(struct da9052 *da9052, int da9052_i2c_read_many(struct da9052 *da9052, struct da9052_ssc_msg *sscmsg, int msg_no) { - - struct i2c_msg i2cmsg; + struct i2c_msg i2cmsg[2]; unsigned char data_buf[MAX_READ_WRITE_CNT]; - struct da9052_ssc_msg *msg_queue = sscmsg; int ret = 0; - /* Flag to check if requested registers are contiguous */ - unsigned char cont_data = 1; - unsigned char cnt = 0; - - /* Check if requested registers are contiguous */ - for (cnt = 1; cnt < msg_no; cnt++) { - if ((msg_queue[cnt].addr - msg_queue[cnt-1].addr) != 1) { - /* Difference is not 1, i.e. non-contiguous registers */ - cont_data = 0; - break; - } - } - - if (cont_data == 0) { - /* Requested registers are non-contiguous */ - for (cnt = 0; cnt < msg_no; cnt++) { - ret = da9052->read(da9052, &msg_queue[cnt]); - if (ret != 0) { - dev_info(&da9052->i2c_client->dev,\ - "Error in %s", __func__); - return ret; - } - } - return 0; - } - - /* - * We want to perform PAGE READ via I2C - * For PAGE READ sequence of I2C transactions is as below - * (slave_addr + reg_addr) + (slave_addr + data_1 + data_2 + ...) - */ - /* Copy address of first register */ - data_buf[0] = msg_queue[0].addr; - - /* Construct a i2c msg for first transaction of PAGE READ i.e. write */ - i2cmsg.addr = da9052->slave_addr ; - i2cmsg.len = 1; - i2cmsg.buf = data_buf; + int expected_addr = 0xFF; + int cnt; - /*To write the data on I2C set flag to zero */ - i2cmsg.flags = 0; - - /* Start the i2c transfer by calling host i2c driver function */ - ret = i2c_transfer(da9052->adapter, &i2cmsg, 1); - if (ret < 0) { - dev_info(&da9052->i2c_client->dev,\ - "1 - i2c_transfer function falied in [%s]!!!\n", __func__); - return ret; - } - - /* Now Read the data from da9052 */ - /* Construct a i2c msg for second transaction of PAGE READ i.e. read */ - i2cmsg.addr = da9052->slave_addr ; - i2cmsg.len = msg_no; - i2cmsg.buf = data_buf; + BUG_ON(msg_no < 0); + BUG_ON(msg_no >= MAX_READ_WRITE_CNT); +/* + printk(KERN_CRIT "D9053: %s: read %d messages starting from %hhu reg\n", + __func__, msg_no, sscmsg->addr); +*/ - /*To read the data on I2C set flag to I2C_M_RD */ - i2cmsg.flags = I2C_M_RD; + /* Construct a i2c msgs for a da9052 driver ssc message request */ + cnt = 0; + do { + /* Build messages for read transaction */ + i2cmsg[0].addr = da9052->slave_addr; + i2cmsg[0].buf = &sscmsg[cnt].addr; + i2cmsg[0].flags = 0; + i2cmsg[0].len = 1; + i2cmsg[1].addr = da9052->slave_addr; + i2cmsg[1].buf = &data_buf[cnt]; + i2cmsg[1].flags = I2C_M_RD; + /* Grab consecutive register reads into one message */ + for (i2cmsg[1].len = 0, expected_addr = sscmsg[cnt].addr; + (cnt < msg_no) && (sscmsg[cnt].addr == expected_addr); + cnt++, expected_addr++) + { + i2cmsg[1].len++; + } - /* Start the i2c transfer by calling host i2c driver function */ - ret = i2c_transfer(da9052->adapter, - &i2cmsg, 1); - if (ret < 0) { - dev_info(&da9052->i2c_client->dev,\ - "2 - i2c_transfer function falied in [%s]!!!\n", __func__); - return ret; + /* Perform read transaction */ + ret = i2c_transfer(da9052->adapter, i2cmsg, 2); + if (ret < 0) { + dev_info(&da9052->i2c_client->dev, + "%s: i2c_transfer failed!!!\n", __func__); + return ret; + } + } while (cnt < msg_no); + +#ifdef I2C_BUG_WORKAROUND + /* Test, whether last register to be accessed needs to be flushed */ + if (!da9052_is_i2c_reg_safe(sscmsg[msg_no-1].addr)) { + i2cmsg[0].addr = da9052->slave_addr; + i2cmsg[0].len = 2; + i2cmsg[0].flags = 0; /* Write operation */ + /* i2c_flush_data is only to read from */ + i2cmsg[0].buf = (unsigned char *)i2c_flush_data; + + ret = i2c_transfer(da9052->adapter, i2cmsg, 1); + if (ret < 0) { + dev_info(&da9052->i2c_client->dev, + "%s: i2c_transfer failed!!!\n", __func__); + return ret; + } } +#endif - /* Gather READ data */ - for (cnt = 0; cnt < msg_no; cnt++) - sscmsg[cnt].data = data_buf[cnt]; - + for (cnt = 0; cnt < msg_no; cnt++) sscmsg[cnt].data = data_buf[cnt]; return 0; } |