Re: [PATCH 5/5] i2c: davinci: use ICPFUNC to toggle I2C as gpio for bus recovery

From: Alexander Sverdlin
Date: Wed Apr 01 2015 - 10:39:03 EST


Hello Grygorii,

On 01/12/14 16:34, Grygorii Strashko wrote:
> Having a board where the I2C bus locks up occasionally made it clear
> that the bus recovery in the i2c-davinci driver will only work on
> some boards, because on regular boards, this will only toggle GPIO
> lines that aren't muxed to the actual pins.
>
> The I2C controller on SoCs like da850 (and da830), Keystone 2 has the
> built-in capability to bit-bang its lines by using the ICPFUNC registers
> of the i2c controller.
> Implement the suggested procedure by toggling SCL and checking SDA using
> the ICPFUNC registers of the I2C controller when present. Allow platforms
> to indicate the presence of the ICPFUNC registers with a has_pfunc platform
> data flag and add optional DT property "ti,has-pfunc" to indicate
> the same in DT.
>
> CC: Sekhar Nori <nsekhar@xxxxxx>
> CC: Kevin Hilman <khilman@xxxxxxxxxxxxxxxxxxx>
> CC: Santosh Shilimkar <ssantosh@xxxxxxxxxx>
> CC: Murali Karicheri <m-karicheri2@xxxxxx>
> CC: Mike Looijmans <info@xxxxxxxxxxxxxxxx>
> CC: <devicetree@xxxxxxxxxxxxxxx>
> Reviewed-by: Uwe Kleine-KÃnig <u.kleine-koenig@xxxxxxxxxxxxxx>
> Signed-off-by: Ben Gardiner <bengardiner@xxxxxxxxxxxxxx>
> Signed-off-by: Mike Looijmans <milo-software@xxxxxxxxxxxxxxxxxxxxx>
> [grygorii.strashko@xxxxxx: combined patches from Ben Gardiner and
> Mike Looijmans and reimplemented ICPFUNC bus recovery using I2C
> bus recovery infrastructure]
> Signed-off-by: Grygorii Strashko <grygorii.strashko@xxxxxx>

We have tested it on a custom Keystone2-based board, recovery seems to work
when SDA is held low externally.

Acked-by: Alexander Sverdlin <alexander.sverdlin@xxxxxxxxx>
Tested-by: Michael Lawnick <michael.lawnick@xxxxxxxxx>

> ---
> .../devicetree/bindings/i2c/i2c-davinci.txt | 3 +
> drivers/i2c/busses/i2c-davinci.c | 102 ++++++++++++++++++++-
> include/linux/platform_data/i2c-davinci.h | 1 +
> 3 files changed, 105 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/devicetree/bindings/i2c/i2c-davinci.txt b/Documentation/devicetree/bindings/i2c/i2c-davinci.txt
> index 2dc935b..a4e1cbc 100644
> --- a/Documentation/devicetree/bindings/i2c/i2c-davinci.txt
> +++ b/Documentation/devicetree/bindings/i2c/i2c-davinci.txt
> @@ -10,6 +10,9 @@ Required properties:
> Recommended properties :
> - interrupts : standard interrupt property.
> - clock-frequency : desired I2C bus clock frequency in Hz.
> +- ti,has-pfunc: boolean; if defined, it indicates that SoC supports PFUNC
> + registers. PFUNC registers allow to switch I2C pins to function as
> + GPIOs, so they can by toggled manually.
>
> Example (enbw_cmc board):
> i2c@1c22000 {
> diff --git a/drivers/i2c/busses/i2c-davinci.c b/drivers/i2c/busses/i2c-davinci.c
> index 00aed63..a1bb587 100644
> --- a/drivers/i2c/busses/i2c-davinci.c
> +++ b/drivers/i2c/busses/i2c-davinci.c
> @@ -64,6 +64,12 @@
> #define DAVINCI_I2C_IVR_REG 0x28
> #define DAVINCI_I2C_EMDR_REG 0x2c
> #define DAVINCI_I2C_PSC_REG 0x30
> +#define DAVINCI_I2C_FUNC_REG 0x48
> +#define DAVINCI_I2C_DIR_REG 0x4c
> +#define DAVINCI_I2C_DIN_REG 0x50
> +#define DAVINCI_I2C_DOUT_REG 0x54
> +#define DAVINCI_I2C_DSET_REG 0x58
> +#define DAVINCI_I2C_DCLR_REG 0x5c
>
> #define DAVINCI_I2C_IVR_AAS 0x07
> #define DAVINCI_I2C_IVR_SCD 0x06
> @@ -97,6 +103,29 @@
> #define DAVINCI_I2C_IMR_NACK BIT(1)
> #define DAVINCI_I2C_IMR_AL BIT(0)
>
> +/* set SDA and SCL as GPIO */
> +#define DAVINCI_I2C_FUNC_PFUNC0 BIT(0)
> +
> +/* set SCL as output when used as GPIO*/
> +#define DAVINCI_I2C_DIR_PDIR0 BIT(0)
> +/* set SDA as output when used as GPIO*/
> +#define DAVINCI_I2C_DIR_PDIR1 BIT(1)
> +
> +/* read SCL GPIO level */
> +#define DAVINCI_I2C_DIN_PDIN0 BIT(0)
> +/* read SDA GPIO level */
> +#define DAVINCI_I2C_DIN_PDIN1 BIT(1)
> +
> +/*set the SCL GPIO high */
> +#define DAVINCI_I2C_DSET_PDSET0 BIT(0)
> +/*set the SDA GPIO high */
> +#define DAVINCI_I2C_DSET_PDSET1 BIT(1)
> +
> +/* set the SCL GPIO low */
> +#define DAVINCI_I2C_DCLR_PDCLR0 BIT(0)
> +/* set the SDA GPIO low */
> +#define DAVINCI_I2C_DCLR_PDCLR1 BIT(1)
> +
> struct davinci_i2c_dev {
> struct device *dev;
> void __iomem *base;
> @@ -257,6 +286,71 @@ static struct i2c_bus_recovery_info davinci_i2c_gpio_recovery_info = {
> .unprepare_recovery = davinci_i2c_unprepare_recovery,
> };
>
> +static void davinci_i2c_set_scl(struct i2c_adapter *adap, int val)
> +{
> + struct davinci_i2c_dev *dev = i2c_get_adapdata(adap);
> +
> + if (val)
> + davinci_i2c_write_reg(dev, DAVINCI_I2C_DSET_REG,
> + DAVINCI_I2C_DSET_PDSET0);
> + else
> + davinci_i2c_write_reg(dev, DAVINCI_I2C_DCLR_REG,
> + DAVINCI_I2C_DCLR_PDCLR0);
> +}
> +
> +static int davinci_i2c_get_scl(struct i2c_adapter *adap)
> +{
> + struct davinci_i2c_dev *dev = i2c_get_adapdata(adap);
> + int val;
> +
> + /* read the state of SCL */
> + val = davinci_i2c_read_reg(dev, DAVINCI_I2C_DIN_REG);
> + return val & DAVINCI_I2C_DIN_PDIN0;
> +}
> +
> +static int davinci_i2c_get_sda(struct i2c_adapter *adap)
> +{
> + struct davinci_i2c_dev *dev = i2c_get_adapdata(adap);
> + int val;
> +
> + /* read the state of SDA */
> + val = davinci_i2c_read_reg(dev, DAVINCI_I2C_DIN_REG);
> + return val & DAVINCI_I2C_DIN_PDIN1;
> +}
> +
> +static void davinci_i2c_scl_prepare_recovery(struct i2c_adapter *adap)
> +{
> + struct davinci_i2c_dev *dev = i2c_get_adapdata(adap);
> +
> + davinci_i2c_prepare_recovery(adap);
> +
> + /* SCL output, SDA input */
> + davinci_i2c_write_reg(dev, DAVINCI_I2C_DIR_REG, DAVINCI_I2C_DIR_PDIR0);
> +
> + /* change to GPIO mode */
> + davinci_i2c_write_reg(dev, DAVINCI_I2C_FUNC_REG,
> + DAVINCI_I2C_FUNC_PFUNC0);
> +}
> +
> +static void davinci_i2c_scl_unprepare_recovery(struct i2c_adapter *adap)
> +{
> + struct davinci_i2c_dev *dev = i2c_get_adapdata(adap);
> +
> + /* change back to I2C mode */
> + davinci_i2c_write_reg(dev, DAVINCI_I2C_FUNC_REG, 0);
> +
> + davinci_i2c_unprepare_recovery(adap);
> +}
> +
> +static struct i2c_bus_recovery_info davinci_i2c_scl_recovery_info = {
> + .recover_bus = i2c_generic_scl_recovery,
> + .set_scl = davinci_i2c_set_scl,
> + .get_scl = davinci_i2c_get_scl,
> + .get_sda = davinci_i2c_get_sda,
> + .prepare_recovery = davinci_i2c_scl_prepare_recovery,
> + .unprepare_recovery = davinci_i2c_scl_unprepare_recovery,
> +};
> +
> /*
> * Waiting for bus not busy
> */
> @@ -669,6 +763,10 @@ static int davinci_i2c_probe(struct platform_device *pdev)
> if (!of_property_read_u32(pdev->dev.of_node, "clock-frequency",
> &prop))
> dev->pdata->bus_freq = prop / 1000;
> +
> + dev->pdata->has_pfunc =
> + of_property_read_bool(pdev->dev.of_node,
> + "ti,has-pfunc");
> } else if (!dev->pdata) {
> dev->pdata = &davinci_i2c_platform_data_default;
> }
> @@ -710,7 +808,9 @@ static int davinci_i2c_probe(struct platform_device *pdev)
> adap->timeout = DAVINCI_I2C_TIMEOUT;
> adap->dev.of_node = pdev->dev.of_node;
>
> - if (dev->pdata->scl_pin) {
> + if (dev->pdata->has_pfunc)
> + adap->bus_recovery_info = &davinci_i2c_scl_recovery_info;
> + else if (dev->pdata->scl_pin) {
> adap->bus_recovery_info = &davinci_i2c_gpio_recovery_info;
> adap->bus_recovery_info->scl_gpio = dev->pdata->scl_pin;
> adap->bus_recovery_info->sda_gpio = dev->pdata->sda_pin;
> diff --git a/include/linux/platform_data/i2c-davinci.h b/include/linux/platform_data/i2c-davinci.h
> index 2312d19..89fd347 100644
> --- a/include/linux/platform_data/i2c-davinci.h
> +++ b/include/linux/platform_data/i2c-davinci.h
> @@ -18,6 +18,7 @@ struct davinci_i2c_platform_data {
> unsigned int bus_delay; /* post-transaction delay (usec) */
> unsigned int sda_pin; /* GPIO pin ID to use for SDA */
> unsigned int scl_pin; /* GPIO pin ID to use for SCL */
> + bool has_pfunc; /*chip has a ICPFUNC register */
> };
>
> /* for board setup code */

--
Best regards,
Alexander Sverdlin.

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/