Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

i2c: algo: pca: Reapply i2c bus settings after reset

If something goes wrong (such as the SCL being stuck low) then we need
to reset the PCA chip. The issue with this is that on reset we lose all
config settings and the chip ends up in a disabled state which results
in a lock up/high CPU usage. We need to re-apply any configuration that
had previously been set and re-enable the chip.

Signed-off-by: Evan Nimmo <evan.nimmo@alliedtelesis.co.nz>
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Wolfram Sang <wsa@kernel.org>

authored by

Evan Nimmo and committed by
Wolfram Sang
0a355aeb 0065ec00

+38 -12
+23 -12
drivers/i2c/algos/i2c-algo-pca.c
··· 41 41 pca_outw(adap, I2C_PCA_INDPTR, I2C_PCA_IPRESET); 42 42 pca_outw(adap, I2C_PCA_IND, 0xA5); 43 43 pca_outw(adap, I2C_PCA_IND, 0x5A); 44 + 45 + /* 46 + * After a reset we need to re-apply any configuration 47 + * (calculated in pca_init) to get the bus in a working state. 48 + */ 49 + pca_outw(adap, I2C_PCA_INDPTR, I2C_PCA_IMODE); 50 + pca_outw(adap, I2C_PCA_IND, adap->bus_settings.mode); 51 + pca_outw(adap, I2C_PCA_INDPTR, I2C_PCA_ISCLL); 52 + pca_outw(adap, I2C_PCA_IND, adap->bus_settings.tlow); 53 + pca_outw(adap, I2C_PCA_INDPTR, I2C_PCA_ISCLH); 54 + pca_outw(adap, I2C_PCA_IND, adap->bus_settings.thi); 55 + 56 + pca_set_con(adap, I2C_PCA_CON_ENSIO); 44 57 } else { 45 58 adap->reset_chip(adap->data); 59 + pca_set_con(adap, I2C_PCA_CON_ENSIO | adap->bus_settings.clock_freq); 46 60 } 47 61 } 48 62 ··· 437 423 " Use the nominal frequency.\n", adap->name); 438 424 } 439 425 440 - pca_reset(pca_data); 441 - 442 426 clock = pca_clock(pca_data); 443 427 printk(KERN_INFO "%s: Clock frequency is %dkHz\n", 444 428 adap->name, freqs[clock]); 445 429 446 - pca_set_con(pca_data, I2C_PCA_CON_ENSIO | clock); 430 + /* Store settings as these will be needed when the PCA chip is reset */ 431 + pca_data->bus_settings.clock_freq = clock; 432 + 433 + pca_reset(pca_data); 447 434 } else { 448 435 int clock; 449 436 int mode; ··· 511 496 thi = tlow * min_thi / min_tlow; 512 497 } 513 498 499 + /* Store settings as these will be needed when the PCA chip is reset */ 500 + pca_data->bus_settings.mode = mode; 501 + pca_data->bus_settings.tlow = tlow; 502 + pca_data->bus_settings.thi = thi; 503 + 514 504 pca_reset(pca_data); 515 505 516 506 printk(KERN_INFO 517 507 "%s: Clock frequency is %dHz\n", adap->name, clock * 100); 518 - 519 - pca_outw(pca_data, I2C_PCA_INDPTR, I2C_PCA_IMODE); 520 - pca_outw(pca_data, I2C_PCA_IND, mode); 521 - pca_outw(pca_data, I2C_PCA_INDPTR, I2C_PCA_ISCLL); 522 - pca_outw(pca_data, I2C_PCA_IND, tlow); 523 - pca_outw(pca_data, I2C_PCA_INDPTR, I2C_PCA_ISCLH); 524 - pca_outw(pca_data, I2C_PCA_IND, thi); 525 - 526 - pca_set_con(pca_data, I2C_PCA_CON_ENSIO); 527 508 } 528 509 udelay(500); /* 500 us for oscillator to stabilise */ 529 510
+15
include/linux/i2c-algo-pca.h
··· 53 53 #define I2C_PCA_CON_SI 0x08 /* Serial Interrupt */ 54 54 #define I2C_PCA_CON_CR 0x07 /* Clock Rate (MASK) */ 55 55 56 + /** 57 + * struct pca_i2c_bus_settings - The configured PCA i2c bus settings 58 + * @mode: Configured i2c bus mode 59 + * @tlow: Configured SCL LOW period 60 + * @thi: Configured SCL HIGH period 61 + * @clock_freq: The configured clock frequency 62 + */ 63 + struct pca_i2c_bus_settings { 64 + int mode; 65 + int tlow; 66 + int thi; 67 + int clock_freq; 68 + }; 69 + 56 70 struct i2c_algo_pca_data { 57 71 void *data; /* private low level data */ 58 72 void (*write_byte) (void *data, int reg, int val); ··· 78 64 * For PCA9665, use the frequency you want here. */ 79 65 unsigned int i2c_clock; 80 66 unsigned int chip; 67 + struct pca_i2c_bus_settings bus_settings; 81 68 }; 82 69 83 70 int i2c_pca_add_bus(struct i2c_adapter *);