From 1f746a2c82b1b455f7f535412afffd7e4689913d Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 7 Feb 2020 16:57:49 +0100 Subject: [PATCH 01/14] i2c: Make deblock delay and SCL clock configurable Make the delay between SCL line changes and the number of SCL clock changes configurable as a parameter of the deblock function. No functional change. Signed-off-by: Marek Vasut Reviewed-by: Heiko Schocher --- drivers/i2c/i2c-uclass.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/drivers/i2c/i2c-uclass.c b/drivers/i2c/i2c-uclass.c index 2aa3efe8aaa..25af1fabdbc 100644 --- a/drivers/i2c/i2c-uclass.c +++ b/drivers/i2c/i2c-uclass.c @@ -502,34 +502,35 @@ static int i2c_gpio_get_pin(struct gpio_desc *pin) } static int i2c_deblock_gpio_loop(struct gpio_desc *sda_pin, - struct gpio_desc *scl_pin) + struct gpio_desc *scl_pin, + unsigned int scl_count, + unsigned int delay) { - int counter = 9; int ret = 0; i2c_gpio_set_pin(sda_pin, 1); i2c_gpio_set_pin(scl_pin, 1); - udelay(5); + udelay(delay); /* Toggle SCL until slave release SDA */ - while (counter-- >= 0) { + while (scl_count-- >= 0) { i2c_gpio_set_pin(scl_pin, 1); - udelay(5); + udelay(delay); i2c_gpio_set_pin(scl_pin, 0); - udelay(5); + udelay(delay); if (i2c_gpio_get_pin(sda_pin)) break; } /* Then, send I2C stop */ i2c_gpio_set_pin(sda_pin, 0); - udelay(5); + udelay(delay); i2c_gpio_set_pin(scl_pin, 1); - udelay(5); + udelay(delay); i2c_gpio_set_pin(sda_pin, 1); - udelay(5); + udelay(delay); if (!i2c_gpio_get_pin(sda_pin) || !i2c_gpio_get_pin(scl_pin)) ret = -EREMOTEIO; @@ -561,7 +562,7 @@ static int i2c_deblock_gpio(struct udevice *bus) goto out_no_pinctrl; } - ret0 = i2c_deblock_gpio_loop(&gpios[PIN_SDA], &gpios[PIN_SCL]); + ret0 = i2c_deblock_gpio_loop(&gpios[PIN_SDA], &gpios[PIN_SCL], 9, 5); ret = pinctrl_select_state(bus, "default"); if (ret) { From 7231522a5ed1545d3206f5204676897d62a24f5f Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 7 Feb 2020 16:57:50 +0100 Subject: [PATCH 02/14] i2c: Export i2c_deblock_gpio_loop() Export the i2c_deblock_gpio_loop() so it can be used in other places in U-Boot. In particular, this is useful in the GPIO I2C driver, which claims the SDA/SCL GPIOs and thus prevents the i2c_deblock() implementation from claiming the pins as GPIOs again. Signed-off-by: Marek Vasut Reviewed-by: Heiko Schocher --- drivers/i2c/i2c-uclass.c | 8 ++++---- include/i2c.h | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/drivers/i2c/i2c-uclass.c b/drivers/i2c/i2c-uclass.c index 25af1fabdbc..86f529241f4 100644 --- a/drivers/i2c/i2c-uclass.c +++ b/drivers/i2c/i2c-uclass.c @@ -501,10 +501,10 @@ static int i2c_gpio_get_pin(struct gpio_desc *pin) return dm_gpio_get_value(pin); } -static int i2c_deblock_gpio_loop(struct gpio_desc *sda_pin, - struct gpio_desc *scl_pin, - unsigned int scl_count, - unsigned int delay) +int i2c_deblock_gpio_loop(struct gpio_desc *sda_pin, + struct gpio_desc *scl_pin, + unsigned int scl_count, + unsigned int delay) { int ret = 0; diff --git a/include/i2c.h b/include/i2c.h index 0faf8542e28..7c92042c584 100644 --- a/include/i2c.h +++ b/include/i2c.h @@ -330,6 +330,22 @@ uint i2c_get_chip_addr_offset_mask(struct udevice *dev); */ int i2c_deblock(struct udevice *bus); +/** + * i2c_deblock_gpio_loop() - recover a bus from an unknown state by toggling SDA/SCL + * + * This is the inner logic used for toggling I2C SDA/SCL lines as GPIOs + * for deblocking the I2C bus. + * + * @sda_pin: SDA GPIO + * @scl_pin: SCL GPIO + * @scl_count: Number of SCL clock cycles generated to deblock SDA + * @delay: Delay between SCL clock line changes + * @return 0 if OK, -ve on error + */ +struct gpio_desc; +int i2c_deblock_gpio_loop(struct gpio_desc *sda_pin, struct gpio_desc *scl_pin, + unsigned int scl_count, unsigned int delay); + /** * struct dm_i2c_ops - driver operations for I2C uclass * From a19172863335dcaa1b2a98009f0bfef2a61ab4a2 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 7 Feb 2020 16:57:51 +0100 Subject: [PATCH 03/14] i2c: Add option to send start condition after deblocking Add option to send start condition after deblocking SDA. Signed-off-by: Marek Vasut Reviewed-by: Heiko Schocher --- drivers/i2c/i2c-uclass.c | 23 ++++++++++++++++++++--- include/i2c.h | 4 +++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/drivers/i2c/i2c-uclass.c b/drivers/i2c/i2c-uclass.c index 86f529241f4..e9ec3885767 100644 --- a/drivers/i2c/i2c-uclass.c +++ b/drivers/i2c/i2c-uclass.c @@ -504,9 +504,10 @@ static int i2c_gpio_get_pin(struct gpio_desc *pin) int i2c_deblock_gpio_loop(struct gpio_desc *sda_pin, struct gpio_desc *scl_pin, unsigned int scl_count, + unsigned int start_count, unsigned int delay) { - int ret = 0; + int i, ret = -EREMOTEIO; i2c_gpio_set_pin(sda_pin, 1); i2c_gpio_set_pin(scl_pin, 1); @@ -518,8 +519,24 @@ int i2c_deblock_gpio_loop(struct gpio_desc *sda_pin, udelay(delay); i2c_gpio_set_pin(scl_pin, 0); udelay(delay); - if (i2c_gpio_get_pin(sda_pin)) + if (i2c_gpio_get_pin(sda_pin)) { + ret = 0; break; + } + } + + if (!ret && start_count) { + for (i = 0; i < start_count; i++) { + /* Send start condition */ + udelay(delay); + i2c_gpio_set_pin(sda_pin, 1); + udelay(delay); + i2c_gpio_set_pin(scl_pin, 1); + udelay(delay); + i2c_gpio_set_pin(sda_pin, 0); + udelay(delay); + i2c_gpio_set_pin(scl_pin, 0); + } } /* Then, send I2C stop */ @@ -562,7 +579,7 @@ static int i2c_deblock_gpio(struct udevice *bus) goto out_no_pinctrl; } - ret0 = i2c_deblock_gpio_loop(&gpios[PIN_SDA], &gpios[PIN_SCL], 9, 5); + ret0 = i2c_deblock_gpio_loop(&gpios[PIN_SDA], &gpios[PIN_SCL], 9, 0, 5); ret = pinctrl_select_state(bus, "default"); if (ret) { diff --git a/include/i2c.h b/include/i2c.h index 7c92042c584..059200115a8 100644 --- a/include/i2c.h +++ b/include/i2c.h @@ -339,12 +339,14 @@ int i2c_deblock(struct udevice *bus); * @sda_pin: SDA GPIO * @scl_pin: SCL GPIO * @scl_count: Number of SCL clock cycles generated to deblock SDA + * @start_count:Number of I2C start conditions sent after deblocking SDA * @delay: Delay between SCL clock line changes * @return 0 if OK, -ve on error */ struct gpio_desc; int i2c_deblock_gpio_loop(struct gpio_desc *sda_pin, struct gpio_desc *scl_pin, - unsigned int scl_count, unsigned int delay); + unsigned int scl_count, unsigned int start_count, + unsigned int delay); /** * struct dm_i2c_ops - driver operations for I2C uclass From 4368c6a2bc6b37f8a547a566da0ad4060f578195 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 7 Feb 2020 16:57:52 +0100 Subject: [PATCH 04/14] i2c: gpio: Run deblock sequence on probe Add deblock dequence for the I2C bus, needed on some devices. This sequence is issued once, when probing the driver, and is controlled by DT property, "i2c-gpio,deblock". Signed-off-by: Marek Vasut Reviewed-by: Heiko Schocher --- drivers/i2c/i2c-gpio.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/i2c/i2c-gpio.c b/drivers/i2c/i2c-gpio.c index 4e8fa214739..d56540b4621 100644 --- a/drivers/i2c/i2c-gpio.c +++ b/drivers/i2c/i2c-gpio.c @@ -305,6 +305,21 @@ static int i2c_gpio_set_bus_speed(struct udevice *dev, unsigned int speed_hz) return 0; } +static int i2c_gpio_drv_probe(struct udevice *dev) +{ + if (dev_read_bool(dev, "i2c-gpio,deblock")) { + /* @200kHz 9 clocks = 44us, 62us is ok */ + const unsigned int DELAY_ABORT_SEQ = 62; + struct i2c_gpio_bus *bus = dev_get_priv(dev); + + return i2c_deblock_gpio_loop(&bus->gpios[PIN_SDA], + &bus->gpios[PIN_SCL], + 16, 5, DELAY_ABORT_SEQ); + } + + return 0; +} + static int i2c_gpio_ofdata_to_platdata(struct udevice *dev) { struct i2c_gpio_bus *bus = dev_get_priv(dev); @@ -341,6 +356,7 @@ U_BOOT_DRIVER(i2c_gpio) = { .name = "i2c-gpio", .id = UCLASS_I2C, .of_match = i2c_gpio_ids, + .probe = i2c_gpio_drv_probe, .ofdata_to_platdata = i2c_gpio_ofdata_to_platdata, .priv_auto_alloc_size = sizeof(struct i2c_gpio_bus), .ops = &i2c_gpio_ops, From 092d9ea26cde419141d340fd5de16c32f7273e00 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Tue, 18 Feb 2020 18:24:42 +0100 Subject: [PATCH 05/14] doc: i2c: gpio: Document deblock sequence on probe Document the gpio-i2c deblocking sequence binding. Signed-off-by: Marek Vasut Reviewed-by: Heiko Schocher --- doc/device-tree-bindings/i2c/i2c-gpio.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/device-tree-bindings/i2c/i2c-gpio.txt b/doc/device-tree-bindings/i2c/i2c-gpio.txt index ba56ed5dea1..e29eeba9e6e 100644 --- a/doc/device-tree-bindings/i2c/i2c-gpio.txt +++ b/doc/device-tree-bindings/i2c/i2c-gpio.txt @@ -16,6 +16,8 @@ Optional: The resulting transfer speed can be adjusted by setting the delay[us] between gpio-toggle operations. Speed [Hz] = 1000000 / 4 * udelay[us], It not defined, then default is 5us (~50KHz). +* i2c-gpio,deblock + Run deblocking sequence when the driver gets probed. Example: From 7383edc2fbd56573819f9ca7401105366191b715 Mon Sep 17 00:00:00 2001 From: Michael Auchter Date: Fri, 7 Feb 2020 10:55:31 -0600 Subject: [PATCH 06/14] dm: i2c-gpio: rework gpio get/set functions This patch reworks i2c-gpio to make it easier to switch out the implementation of the sda/scl get/set functions. This is in preparation for a patch to conditionally implement clock stretching support. Signed-off-by: Michael Auchter Cc: Heiko Schocher Reviewed-by: Heiko Schocher --- drivers/i2c/i2c-gpio.c | 133 ++++++++++++++++++++--------------------- 1 file changed, 64 insertions(+), 69 deletions(-) diff --git a/drivers/i2c/i2c-gpio.c b/drivers/i2c/i2c-gpio.c index d56540b4621..e3a21ad3b29 100644 --- a/drivers/i2c/i2c-gpio.c +++ b/drivers/i2c/i2c-gpio.c @@ -32,23 +32,32 @@ struct i2c_gpio_bus { int udelay; /* sda, scl */ struct gpio_desc gpios[PIN_COUNT]; + + int (*get_sda)(struct i2c_gpio_bus *bus); + void (*set_sda)(struct i2c_gpio_bus *bus, int bit); + void (*set_scl)(struct i2c_gpio_bus *bus, int bit); }; -static int i2c_gpio_sda_get(struct gpio_desc *sda) +static int i2c_gpio_sda_get(struct i2c_gpio_bus *bus) { + struct gpio_desc *sda = &bus->gpios[PIN_SDA]; + return dm_gpio_get_value(sda); } -static void i2c_gpio_sda_set(struct gpio_desc *sda, int bit) +static void i2c_gpio_sda_set(struct i2c_gpio_bus *bus, int bit) { + struct gpio_desc *sda = &bus->gpios[PIN_SDA]; + if (bit) dm_gpio_set_dir_flags(sda, GPIOD_IS_IN); else dm_gpio_set_dir_flags(sda, GPIOD_IS_OUT); } -static void i2c_gpio_scl_set(struct gpio_desc *scl, int bit) +static void i2c_gpio_scl_set(struct i2c_gpio_bus *bus, int bit) { + struct gpio_desc *scl = &bus->gpios[PIN_SCL]; ulong flags = GPIOD_IS_OUT; if (bit) @@ -56,65 +65,60 @@ static void i2c_gpio_scl_set(struct gpio_desc *scl, int bit) dm_gpio_set_dir_flags(scl, flags); } -static void i2c_gpio_write_bit(struct gpio_desc *scl, struct gpio_desc *sda, - int delay, uchar bit) +static void i2c_gpio_write_bit(struct i2c_gpio_bus *bus, int delay, uchar bit) { - i2c_gpio_scl_set(scl, 0); + bus->set_scl(bus, 0); udelay(delay); - i2c_gpio_sda_set(sda, bit); + bus->set_sda(bus, bit); udelay(delay); - i2c_gpio_scl_set(scl, 1); + bus->set_scl(bus, 1); udelay(2 * delay); } -static int i2c_gpio_read_bit(struct gpio_desc *scl, struct gpio_desc *sda, - int delay) +static int i2c_gpio_read_bit(struct i2c_gpio_bus *bus, int delay) { int value; - i2c_gpio_scl_set(scl, 1); + bus->set_scl(bus, 1); udelay(delay); - value = i2c_gpio_sda_get(sda); + value = bus->get_sda(bus); udelay(delay); - i2c_gpio_scl_set(scl, 0); + bus->set_scl(bus, 0); udelay(2 * delay); return value; } /* START: High -> Low on SDA while SCL is High */ -static void i2c_gpio_send_start(struct gpio_desc *scl, struct gpio_desc *sda, - int delay) +static void i2c_gpio_send_start(struct i2c_gpio_bus *bus, int delay) { udelay(delay); - i2c_gpio_sda_set(sda, 1); + bus->set_sda(bus, 1); udelay(delay); - i2c_gpio_scl_set(scl, 1); + bus->set_scl(bus, 1); udelay(delay); - i2c_gpio_sda_set(sda, 0); + bus->set_sda(bus, 0); udelay(delay); } /* STOP: Low -> High on SDA while SCL is High */ -static void i2c_gpio_send_stop(struct gpio_desc *scl, struct gpio_desc *sda, - int delay) +static void i2c_gpio_send_stop(struct i2c_gpio_bus *bus, int delay) { - i2c_gpio_scl_set(scl, 0); + bus->set_scl(bus, 0); udelay(delay); - i2c_gpio_sda_set(sda, 0); + bus->set_sda(bus, 0); udelay(delay); - i2c_gpio_scl_set(scl, 1); + bus->set_scl(bus, 1); udelay(delay); - i2c_gpio_sda_set(sda, 1); + bus->set_sda(bus, 1); udelay(delay); } /* ack should be I2C_ACK or I2C_NOACK */ -static void i2c_gpio_send_ack(struct gpio_desc *scl, struct gpio_desc *sda, - int delay, int ack) +static void i2c_gpio_send_ack(struct i2c_gpio_bus *bus, int delay, int ack) { - i2c_gpio_write_bit(scl, sda, delay, ack); - i2c_gpio_scl_set(scl, 0); + i2c_gpio_write_bit(bus, delay, ack); + bus->set_scl(bus, 0); udelay(delay); } @@ -123,44 +127,41 @@ static void i2c_gpio_send_ack(struct gpio_desc *scl, struct gpio_desc *sda, * to clock any confused device back into an idle state. Also send a * at the end of the sequence for belts & suspenders. */ -static void i2c_gpio_send_reset(struct gpio_desc *scl, struct gpio_desc *sda, - int delay) +static void i2c_gpio_send_reset(struct i2c_gpio_bus *bus, int delay) { int j; for (j = 0; j < 9; j++) - i2c_gpio_write_bit(scl, sda, delay, 1); + i2c_gpio_write_bit(bus, delay, 1); - i2c_gpio_send_stop(scl, sda, delay); + i2c_gpio_send_stop(bus, delay); } /* Set sda high with low clock, before reading slave data */ -static void i2c_gpio_sda_high(struct gpio_desc *scl, struct gpio_desc *sda, - int delay) +static void i2c_gpio_sda_high(struct i2c_gpio_bus *bus, int delay) { - i2c_gpio_scl_set(scl, 0); + bus->set_scl(bus, 0); udelay(delay); - i2c_gpio_sda_set(sda, 1); + bus->set_sda(bus, 1); udelay(delay); } /* Send 8 bits and look for an acknowledgement */ -static int i2c_gpio_write_byte(struct gpio_desc *scl, struct gpio_desc *sda, - int delay, uchar data) +static int i2c_gpio_write_byte(struct i2c_gpio_bus *bus, int delay, uchar data) { int j; int nack; for (j = 0; j < 8; j++) { - i2c_gpio_write_bit(scl, sda, delay, data & 0x80); + i2c_gpio_write_bit(bus, delay, data & 0x80); data <<= 1; } udelay(delay); /* Look for an (negative logic) and return it */ - i2c_gpio_sda_high(scl, sda, delay); - nack = i2c_gpio_read_bit(scl, sda, delay); + i2c_gpio_sda_high(bus, delay); + nack = i2c_gpio_read_bit(bus, delay); return nack; /* not a nack is an ack */ } @@ -169,31 +170,29 @@ static int i2c_gpio_write_byte(struct gpio_desc *scl, struct gpio_desc *sda, * if ack == I2C_ACK, ACK the byte so can continue reading, else * send I2C_NOACK to end the read. */ -static uchar i2c_gpio_read_byte(struct gpio_desc *scl, struct gpio_desc *sda, - int delay, int ack) +static uchar i2c_gpio_read_byte(struct i2c_gpio_bus *bus, int delay, int ack) { int data; int j; - i2c_gpio_sda_high(scl, sda, delay); + i2c_gpio_sda_high(bus, delay); data = 0; for (j = 0; j < 8; j++) { data <<= 1; - data |= i2c_gpio_read_bit(scl, sda, delay); + data |= i2c_gpio_read_bit(bus, delay); } - i2c_gpio_send_ack(scl, sda, delay, ack); + i2c_gpio_send_ack(bus, delay, ack); return data; } /* send start and the slave chip address */ -int i2c_send_slave_addr(struct gpio_desc *scl, struct gpio_desc *sda, int delay, - uchar chip) +int i2c_send_slave_addr(struct i2c_gpio_bus *bus, int delay, uchar chip) { - i2c_gpio_send_start(scl, sda, delay); + i2c_gpio_send_start(bus, delay); - if (i2c_gpio_write_byte(scl, sda, delay, chip)) { - i2c_gpio_send_stop(scl, sda, delay); + if (i2c_gpio_write_byte(bus, delay, chip)) { + i2c_gpio_send_stop(bus, delay); return -EIO; } @@ -204,29 +203,27 @@ static int i2c_gpio_write_data(struct i2c_gpio_bus *bus, uchar chip, uchar *buffer, int len, bool end_with_repeated_start) { - struct gpio_desc *scl = &bus->gpios[PIN_SCL]; - struct gpio_desc *sda = &bus->gpios[PIN_SDA]; unsigned int delay = bus->udelay; int failures = 0; debug("%s: chip %x buffer %p len %d\n", __func__, chip, buffer, len); - if (i2c_send_slave_addr(scl, sda, delay, chip << 1)) { + if (i2c_send_slave_addr(bus, delay, chip << 1)) { debug("i2c_write, no chip responded %02X\n", chip); return -EIO; } while (len-- > 0) { - if (i2c_gpio_write_byte(scl, sda, delay, *buffer++)) + if (i2c_gpio_write_byte(bus, delay, *buffer++)) failures++; } if (!end_with_repeated_start) { - i2c_gpio_send_stop(scl, sda, delay); + i2c_gpio_send_stop(bus, delay); return failures; } - if (i2c_send_slave_addr(scl, sda, delay, (chip << 1) | 0x1)) { + if (i2c_send_slave_addr(bus, delay, (chip << 1) | 0x1)) { debug("i2c_write, no chip responded %02X\n", chip); return -EIO; } @@ -237,16 +234,14 @@ static int i2c_gpio_write_data(struct i2c_gpio_bus *bus, uchar chip, static int i2c_gpio_read_data(struct i2c_gpio_bus *bus, uchar chip, uchar *buffer, int len) { - struct gpio_desc *scl = &bus->gpios[PIN_SCL]; - struct gpio_desc *sda = &bus->gpios[PIN_SDA]; unsigned int delay = bus->udelay; debug("%s: chip %x buffer: %p len %d\n", __func__, chip, buffer, len); while (len-- > 0) - *buffer++ = i2c_gpio_read_byte(scl, sda, delay, len == 0); + *buffer++ = i2c_gpio_read_byte(bus, delay, len == 0); - i2c_gpio_send_stop(scl, sda, delay); + i2c_gpio_send_stop(bus, delay); return 0; } @@ -277,14 +272,12 @@ static int i2c_gpio_xfer(struct udevice *dev, struct i2c_msg *msg, int nmsgs) static int i2c_gpio_probe(struct udevice *dev, uint chip, uint chip_flags) { struct i2c_gpio_bus *bus = dev_get_priv(dev); - struct gpio_desc *scl = &bus->gpios[PIN_SCL]; - struct gpio_desc *sda = &bus->gpios[PIN_SDA]; unsigned int delay = bus->udelay; int ret; - i2c_gpio_send_start(scl, sda, delay); - ret = i2c_gpio_write_byte(scl, sda, delay, (chip << 1) | 0); - i2c_gpio_send_stop(scl, sda, delay); + i2c_gpio_send_start(bus, delay); + ret = i2c_gpio_write_byte(bus, delay, (chip << 1) | 0); + i2c_gpio_send_stop(bus, delay); debug("%s: bus: %d (%s) chip: %x flags: %x ret: %d\n", __func__, dev->seq, dev->name, chip, chip_flags, ret); @@ -295,12 +288,10 @@ static int i2c_gpio_probe(struct udevice *dev, uint chip, uint chip_flags) static int i2c_gpio_set_bus_speed(struct udevice *dev, unsigned int speed_hz) { struct i2c_gpio_bus *bus = dev_get_priv(dev); - struct gpio_desc *scl = &bus->gpios[PIN_SCL]; - struct gpio_desc *sda = &bus->gpios[PIN_SDA]; bus->udelay = 1000000 / (speed_hz << 2); - i2c_gpio_send_reset(scl, sda, bus->udelay); + i2c_gpio_send_reset(bus, bus->udelay); return 0; } @@ -335,6 +326,10 @@ static int i2c_gpio_ofdata_to_platdata(struct udevice *dev) bus->udelay = fdtdec_get_int(blob, node, "i2c-gpio,delay-us", DEFAULT_UDELAY); + bus->get_sda = i2c_gpio_sda_get; + bus->set_sda = i2c_gpio_sda_set; + bus->set_scl = i2c_gpio_scl_set; + return 0; error: pr_err("Can't get %s gpios! Error: %d", dev->name, ret); From ba6fb2f6aca54c6555742d507290cbfaa655e623 Mon Sep 17 00:00:00 2001 From: Heiko Schocher Date: Mon, 16 Mar 2020 07:55:06 +0100 Subject: [PATCH 07/14] dm: i2c-gpio: add support for clock stretching This adds support for clock stretching to the i2c-gpio driver. This is accomplished by switching the GPIO used for the SCL line to an input when it should be driven high, and polling on the SCL line value until it goes high (indicating that the I2C slave is no longer pulling it low). This is enabled by default; for gpios which cannot be configured as inputs, the i2c-gpio,scl-output-only property can be used to fall back to the previous behavior. Signed-off-by: Michael Auchter Cc: Heiko Schocher Reviewed-by: Heiko Schocher --- doc/device-tree-bindings/i2c/i2c-gpio.txt | 2 ++ drivers/i2c/i2c-gpio.c | 23 ++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/doc/device-tree-bindings/i2c/i2c-gpio.txt b/doc/device-tree-bindings/i2c/i2c-gpio.txt index e29eeba9e6e..b06b8299337 100644 --- a/doc/device-tree-bindings/i2c/i2c-gpio.txt +++ b/doc/device-tree-bindings/i2c/i2c-gpio.txt @@ -18,6 +18,8 @@ Optional: It not defined, then default is 5us (~50KHz). * i2c-gpio,deblock Run deblocking sequence when the driver gets probed. +* i2c-gpio,scl-output-only; + Set if SCL is an output only Example: diff --git a/drivers/i2c/i2c-gpio.c b/drivers/i2c/i2c-gpio.c index e3a21ad3b29..07fdd343f22 100644 --- a/drivers/i2c/i2c-gpio.c +++ b/drivers/i2c/i2c-gpio.c @@ -56,6 +56,24 @@ static void i2c_gpio_sda_set(struct i2c_gpio_bus *bus, int bit) } static void i2c_gpio_scl_set(struct i2c_gpio_bus *bus, int bit) +{ + struct gpio_desc *scl = &bus->gpios[PIN_SCL]; + int count = 0; + + if (bit) { + dm_gpio_set_dir_flags(scl, GPIOD_IS_IN); + while (!dm_gpio_get_value(scl) && count++ < 100000) + udelay(1); + + if (!dm_gpio_get_value(scl)) + pr_err("timeout waiting on slave to release scl\n"); + } else { + dm_gpio_set_dir_flags(scl, GPIOD_IS_OUT); + } +} + +/* variant for output only gpios which cannot support clock stretching */ +static void i2c_gpio_scl_set_output_only(struct i2c_gpio_bus *bus, int bit) { struct gpio_desc *scl = &bus->gpios[PIN_SCL]; ulong flags = GPIOD_IS_OUT; @@ -328,7 +346,10 @@ static int i2c_gpio_ofdata_to_platdata(struct udevice *dev) bus->get_sda = i2c_gpio_sda_get; bus->set_sda = i2c_gpio_sda_set; - bus->set_scl = i2c_gpio_scl_set; + if (fdtdec_get_bool(blob, node, "i2c-gpio,scl-output-only")) + bus->set_scl = i2c_gpio_scl_set_output_only; + else + bus->set_scl = i2c_gpio_scl_set; return 0; error: From ed6dd4e460bd94150dc09fce547a27e2fcca66eb Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Fri, 28 Feb 2020 22:04:13 +0900 Subject: [PATCH 08/14] misc: i2c_eeprom: remove pagewidth field from i2c_eeprom This struct member is not used in any effective way. Remove it. Signed-off-by: Masahiro Yamada --- drivers/misc/i2c_eeprom.c | 8 +++----- include/i2c_eeprom.h | 2 -- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/misc/i2c_eeprom.c b/drivers/misc/i2c_eeprom.c index 6c0459dc555..728e0fd79ab 100644 --- a/drivers/misc/i2c_eeprom.c +++ b/drivers/misc/i2c_eeprom.c @@ -99,13 +99,11 @@ static int i2c_eeprom_std_ofdata_to_platdata(struct udevice *dev) u32 pagesize; u32 size; - if (dev_read_u32(dev, "pagesize", &pagesize) == 0) { + if (dev_read_u32(dev, "pagesize", &pagesize) == 0) priv->pagesize = pagesize; - } else { + else /* 6 bit -> page size of up to 2^63 (should be sufficient) */ - priv->pagewidth = data->pagewidth; - priv->pagesize = (1 << priv->pagewidth); - } + priv->pagesize = 1 << data->pagewidth; if (dev_read_u32(dev, "size", &size) == 0) priv->size = size; diff --git a/include/i2c_eeprom.h b/include/i2c_eeprom.h index b96254ae79f..cd620d519fc 100644 --- a/include/i2c_eeprom.h +++ b/include/i2c_eeprom.h @@ -16,8 +16,6 @@ struct i2c_eeprom_ops { struct i2c_eeprom { /* The EEPROM's page size in byte */ unsigned long pagesize; - /* The EEPROM's page width in bits (pagesize = 2^pagewidth) */ - unsigned pagewidth; /* The EEPROM's capacity in bytes */ unsigned long size; }; From 0c17bb1cbe87c5b00880d3445b46c437f3f22e02 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Fri, 28 Feb 2020 22:04:14 +0900 Subject: [PATCH 09/14] misc: i2c_eeprom: store pagesize instead of pagewidth in i2c_eeprom_drv_data Associate the pagesize with compatible strings, and copy it to priv->pagesize. This is more straight-forward. Signed-off-by: Masahiro Yamada --- drivers/misc/i2c_eeprom.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/drivers/misc/i2c_eeprom.c b/drivers/misc/i2c_eeprom.c index 728e0fd79ab..ef5f103c98e 100644 --- a/drivers/misc/i2c_eeprom.c +++ b/drivers/misc/i2c_eeprom.c @@ -14,7 +14,7 @@ struct i2c_eeprom_drv_data { u32 size; /* size in bytes */ - u32 pagewidth; /* pagesize = 2^pagewidth */ + u32 pagesize; /* page size in bytes */ u32 addr_offset_mask; /* bits in addr used for offset overflow */ u32 offset_len; /* size in bytes of offset */ }; @@ -103,7 +103,7 @@ static int i2c_eeprom_std_ofdata_to_platdata(struct udevice *dev) priv->pagesize = pagesize; else /* 6 bit -> page size of up to 2^63 (should be sufficient) */ - priv->pagesize = 1 << data->pagewidth; + priv->pagesize = data->pagesize; if (dev_read_u32(dev, "size", &size) == 0) priv->size = size; @@ -156,98 +156,98 @@ static int i2c_eeprom_std_probe(struct udevice *dev) static const struct i2c_eeprom_drv_data eeprom_data = { .size = 0, - .pagewidth = 0, + .pagesize = 1, .addr_offset_mask = 0, .offset_len = 1, }; static const struct i2c_eeprom_drv_data mc24aa02e48_data = { .size = 256, - .pagewidth = 3, + .pagesize = 8, .addr_offset_mask = 0, .offset_len = 1, }; static const struct i2c_eeprom_drv_data atmel24c01a_data = { .size = 128, - .pagewidth = 3, + .pagesize = 8, .addr_offset_mask = 0, .offset_len = 1, }; static const struct i2c_eeprom_drv_data atmel24c02_data = { .size = 256, - .pagewidth = 3, + .pagesize = 8, .addr_offset_mask = 0, .offset_len = 1, }; static const struct i2c_eeprom_drv_data atmel24c04_data = { .size = 512, - .pagewidth = 4, + .pagesize = 16, .addr_offset_mask = 0x1, .offset_len = 1, }; static const struct i2c_eeprom_drv_data atmel24c08_data = { .size = 1024, - .pagewidth = 4, + .pagesize = 16, .addr_offset_mask = 0x3, .offset_len = 1, }; static const struct i2c_eeprom_drv_data atmel24c08a_data = { .size = 1024, - .pagewidth = 4, + .pagesize = 16, .addr_offset_mask = 0x3, .offset_len = 1, }; static const struct i2c_eeprom_drv_data atmel24c16a_data = { .size = 2048, - .pagewidth = 4, + .pagesize = 16, .addr_offset_mask = 0x7, .offset_len = 1, }; static const struct i2c_eeprom_drv_data atmel24mac402_data = { .size = 256, - .pagewidth = 4, + .pagesize = 16, .addr_offset_mask = 0, .offset_len = 1, }; static const struct i2c_eeprom_drv_data atmel24c32_data = { .size = 4096, - .pagewidth = 5, + .pagesize = 32, .addr_offset_mask = 0, .offset_len = 2, }; static const struct i2c_eeprom_drv_data atmel24c64_data = { .size = 8192, - .pagewidth = 5, + .pagesize = 32, .addr_offset_mask = 0, .offset_len = 2, }; static const struct i2c_eeprom_drv_data atmel24c128_data = { .size = 16384, - .pagewidth = 6, + .pagesize = 64, .addr_offset_mask = 0, .offset_len = 2, }; static const struct i2c_eeprom_drv_data atmel24c256_data = { .size = 32768, - .pagewidth = 6, + .pagesize = 64, .addr_offset_mask = 0, .offset_len = 2, }; static const struct i2c_eeprom_drv_data atmel24c512_data = { .size = 65536, - .pagewidth = 6, + .pagesize = 64, .addr_offset_mask = 0, .offset_len = 2, }; From 70c894f85e9e116b8f215f522ed96b60ac60f201 Mon Sep 17 00:00:00 2001 From: Jun Chen Date: Mon, 2 Mar 2020 16:58:54 +0800 Subject: [PATCH 10/14] i2c: designware_i2c: Fix IC_CON register setting for high speed mode IC_CON[2:1] should be 3 for high speed mode Signed-off-by: Jun Chen Signed-off-by: Jun Chen --- drivers/i2c/designware_i2c.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i2c/designware_i2c.c b/drivers/i2c/designware_i2c.c index 0b5e70af591..9186fcb7dc9 100644 --- a/drivers/i2c/designware_i2c.c +++ b/drivers/i2c/designware_i2c.c @@ -274,7 +274,7 @@ static int _dw_i2c_set_bus_speed(struct dw_i2c *priv, struct i2c_regs *i2c_base, switch (config.speed_mode) { case IC_SPEED_MODE_HIGH: - cntl |= IC_CON_SPD_SS; + cntl |= IC_CON_SPD_HS; writel(config.scl_hcnt, &i2c_base->ic_hs_scl_hcnt); writel(config.scl_lcnt, &i2c_base->ic_hs_scl_lcnt); break; From 565e328b959b58c181fdec33b2e161ada90dd521 Mon Sep 17 00:00:00 2001 From: Jun Chen Date: Mon, 2 Mar 2020 16:58:55 +0800 Subject: [PATCH 11/14] i2c: designware_i2c: check is high speed possible support To read IC_COMP_PARAM_1[3:2] to check is high speed possible, and fall back to fast mode if not. Signed-off-by: Jun Chen Signed-off-by: Jun Chen --- drivers/i2c/designware_i2c.c | 10 ++++++++++ drivers/i2c/designware_i2c.h | 3 +++ 2 files changed, 13 insertions(+) diff --git a/drivers/i2c/designware_i2c.c b/drivers/i2c/designware_i2c.c index 9186fcb7dc9..f4fbf3ba9f5 100644 --- a/drivers/i2c/designware_i2c.c +++ b/drivers/i2c/designware_i2c.c @@ -203,9 +203,12 @@ static int calc_bus_speed(struct dw_i2c *priv, int speed, ulong bus_clk, const struct dw_scl_sda_cfg *scl_sda_cfg = NULL; struct i2c_regs *regs = priv->regs; enum i2c_speed_mode i2c_spd; + u32 comp_param1; int spk_cnt; int ret; + comp_param1 = readl(®s->comp_param1); + if (priv) scl_sda_cfg = priv->scl_sda_cfg; /* Allow high speed if there is no config, or the config allows it */ @@ -219,6 +222,13 @@ static int calc_bus_speed(struct dw_i2c *priv, int speed, ulong bus_clk, else i2c_spd = IC_SPEED_MODE_STANDARD; + /* Check is high speed possible and fall back to fast mode if not */ + if (i2c_spd == IC_SPEED_MODE_HIGH) { + if ((comp_param1 & DW_IC_COMP_PARAM_1_SPEED_MODE_MASK) + != DW_IC_COMP_PARAM_1_SPEED_MODE_HIGH) + i2c_spd = IC_SPEED_MODE_FAST; + } + /* Get the proper spike-suppression count based on target speed */ if (!priv || !priv->has_spk_cnt) spk_cnt = 0; diff --git a/drivers/i2c/designware_i2c.h b/drivers/i2c/designware_i2c.h index 61a882cb650..23f311b61ca 100644 --- a/drivers/i2c/designware_i2c.h +++ b/drivers/i2c/designware_i2c.h @@ -138,6 +138,9 @@ struct i2c_regs { #define IC_STATUS_TFNF 0x0002 #define IC_STATUS_ACT 0x0001 +#define DW_IC_COMP_PARAM_1_SPEED_MODE_HIGH (BIT(2) | BIT(3)) +#define DW_IC_COMP_PARAM_1_SPEED_MODE_MASK (BIT(2) | BIT(3)) + /** * struct dw_scl_sda_cfg - I2C timing configuration * From be26342314a6aacc1bb1e53ad00dfb4b836f0134 Mon Sep 17 00:00:00 2001 From: Jun Chen Date: Mon, 2 Mar 2020 16:58:56 +0800 Subject: [PATCH 12/14] i2c: designware_i2c: remove 'has_high_speed' Remove 'has_high_speed' config since we can check high speed support from IC_COMP_PARAM_1 register. Signed-off-by: Jun Chen Signed-off-by: Jun Chen --- drivers/i2c/designware_i2c.c | 3 +-- drivers/i2c/designware_i2c.h | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/i2c/designware_i2c.c b/drivers/i2c/designware_i2c.c index f4fbf3ba9f5..74aef7744ca 100644 --- a/drivers/i2c/designware_i2c.c +++ b/drivers/i2c/designware_i2c.c @@ -212,8 +212,7 @@ static int calc_bus_speed(struct dw_i2c *priv, int speed, ulong bus_clk, if (priv) scl_sda_cfg = priv->scl_sda_cfg; /* Allow high speed if there is no config, or the config allows it */ - if (speed >= I2C_SPEED_HIGH_RATE && - (!scl_sda_cfg || scl_sda_cfg->has_high_speed)) + if (speed >= I2C_SPEED_HIGH_RATE) i2c_spd = IC_SPEED_MODE_HIGH; else if (speed >= I2C_SPEED_FAST_PLUS_RATE) i2c_spd = IC_SPEED_MODE_FAST_PLUS; diff --git a/drivers/i2c/designware_i2c.h b/drivers/i2c/designware_i2c.h index 23f311b61ca..5a04ce50e29 100644 --- a/drivers/i2c/designware_i2c.h +++ b/drivers/i2c/designware_i2c.h @@ -144,7 +144,6 @@ struct i2c_regs { /** * struct dw_scl_sda_cfg - I2C timing configuration * - * @has_high_speed: Support high speed (3.4Mbps) * @ss_hcnt: Standard speed high time in ns * @fs_hcnt: Fast speed high time in ns * @ss_lcnt: Standard speed low time in ns @@ -152,7 +151,6 @@ struct i2c_regs { * @sda_hold: SDA hold time */ struct dw_scl_sda_cfg { - bool has_high_speed; u32 ss_hcnt; u32 fs_hcnt; u32 ss_lcnt; From 27d483bfa34c1695c2be230efc50c52d5a3d04e2 Mon Sep 17 00:00:00 2001 From: Jun Chen Date: Mon, 2 Mar 2020 16:58:57 +0800 Subject: [PATCH 13/14] i2c: designware_i2c: add 'hs_hcnt' and 'hs_lcnt' for high speed Add support for high speed if scl_sda_cfg exist. Signed-off-by: Jun Chen Signed-off-by: Jun Chen --- drivers/i2c/designware_i2c.c | 3 +++ drivers/i2c/designware_i2c.h | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/drivers/i2c/designware_i2c.c b/drivers/i2c/designware_i2c.c index 74aef7744ca..088a6f3efb3 100644 --- a/drivers/i2c/designware_i2c.c +++ b/drivers/i2c/designware_i2c.c @@ -240,6 +240,9 @@ static int calc_bus_speed(struct dw_i2c *priv, int speed, ulong bus_clk, if (i2c_spd == IC_SPEED_MODE_STANDARD) { config->scl_hcnt = scl_sda_cfg->ss_hcnt; config->scl_lcnt = scl_sda_cfg->ss_lcnt; + } else if (i2c_spd == IC_SPEED_MODE_HIGH) { + config->scl_hcnt = scl_sda_cfg->hs_hcnt; + config->scl_lcnt = scl_sda_cfg->hs_lcnt; } else { config->scl_hcnt = scl_sda_cfg->fs_hcnt; config->scl_lcnt = scl_sda_cfg->fs_lcnt; diff --git a/drivers/i2c/designware_i2c.h b/drivers/i2c/designware_i2c.h index 5a04ce50e29..7ee236193da 100644 --- a/drivers/i2c/designware_i2c.h +++ b/drivers/i2c/designware_i2c.h @@ -146,15 +146,19 @@ struct i2c_regs { * * @ss_hcnt: Standard speed high time in ns * @fs_hcnt: Fast speed high time in ns + * @hs_hcnt: High speed high time in ns * @ss_lcnt: Standard speed low time in ns * @fs_lcnt: Fast speed low time in ns + * @hs_lcnt: High speed low time in ns * @sda_hold: SDA hold time */ struct dw_scl_sda_cfg { u32 ss_hcnt; u32 fs_hcnt; + u32 hs_hcnt; u32 ss_lcnt; u32 fs_lcnt; + u32 hs_lcnt; u32 sda_hold; }; From 80e8b8add057d2c947394d9d57fc2dcc7ff886d1 Mon Sep 17 00:00:00 2001 From: Heiko Schocher Date: Mon, 2 Mar 2020 15:43:59 +0100 Subject: [PATCH 14/14] bootcounter: add DM support for memory based bootcounter add DM/DTS support for the memory based bootcounter in drivers/bootcount/bootcount.c. Let the old implementation in, so boards which have not yet convert to DM/DTS do not break. Signed-off-by: Heiko Schocher Reviewed-by: Simon Glass --- doc/device-tree-bindings/misc/bootcounter.txt | 21 +++++ drivers/bootcount/Kconfig | 7 ++ drivers/bootcount/Makefile | 1 + drivers/bootcount/bootcount.c | 92 +++++++++++++++++++ 4 files changed, 121 insertions(+) create mode 100644 doc/device-tree-bindings/misc/bootcounter.txt diff --git a/doc/device-tree-bindings/misc/bootcounter.txt b/doc/device-tree-bindings/misc/bootcounter.txt new file mode 100644 index 00000000000..d32fbc37b2e --- /dev/null +++ b/doc/device-tree-bindings/misc/bootcounter.txt @@ -0,0 +1,21 @@ +U-Boot bootcounter Devicetree Binding +===================================== + +The device tree node describes the U-Boot bootcounter +memory based device binding. + +Required properties : + +- compatible : "u-boot,bootcount"; +- single-word : set this, if you have only one word space + for storing the bootcounter. + +Example +------- + +MPC83xx based board: + +bootcount@0x13ff8 { + compatible = "u-boot,bootcount"; + reg = <0x13ff8 0x08>; +}; diff --git a/drivers/bootcount/Kconfig b/drivers/bootcount/Kconfig index 0e506c9ea2a..0356f8ba181 100644 --- a/drivers/bootcount/Kconfig +++ b/drivers/bootcount/Kconfig @@ -106,6 +106,13 @@ config DM_BOOTCOUNT_I2C_EEPROM pointing to the underlying i2c eeprom device) and an optional 'offset' property are supported. +config BOOTCOUNT_MEM + bool "Support memory based bootcounter" + help + Enabling Memory based bootcount, typically in a SoC register which + is not cleared on softreset. + compatible = "u-boot,bootcount"; + endmenu endif diff --git a/drivers/bootcount/Makefile b/drivers/bootcount/Makefile index 73ccfb5a088..059d40d16b0 100644 --- a/drivers/bootcount/Makefile +++ b/drivers/bootcount/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0+ obj-$(CONFIG_BOOTCOUNT_GENERIC) += bootcount.o +obj-$(CONFIG_BOOTCOUNT_MEM) += bootcount.o obj-$(CONFIG_BOOTCOUNT_AT91) += bootcount_at91.o obj-$(CONFIG_BOOTCOUNT_AM33XX) += bootcount_davinci.o obj-$(CONFIG_BOOTCOUNT_RAM) += bootcount_ram.o diff --git a/drivers/bootcount/bootcount.c b/drivers/bootcount/bootcount.c index 7a6d03dcca3..655dfaf59c4 100644 --- a/drivers/bootcount/bootcount.c +++ b/drivers/bootcount/bootcount.c @@ -8,6 +8,7 @@ #include #include +#if !defined(CONFIG_DM_BOOTCOUNT) /* Now implement the generic default functions */ __weak void bootcount_store(ulong a) { @@ -49,3 +50,94 @@ __weak ulong bootcount_load(void) return raw_bootcount_load(reg); #endif /* defined(CONFIG_SYS_BOOTCOUNT_SINGLEWORD) */ } +#else +#include + +/* + * struct bootcount_mem_priv - private bootcount mem driver data + * + * @base: base address used for bootcounter + * @singleword: if true use only one 32 bit word for bootcounter + */ +struct bootcount_mem_priv { + phys_addr_t base; + bool singleword; +}; + +static int bootcount_mem_get(struct udevice *dev, u32 *a) +{ + struct bootcount_mem_priv *priv = dev_get_priv(dev); + void *reg = (void *)priv->base; + u32 magic = CONFIG_SYS_BOOTCOUNT_MAGIC; + + if (priv->singleword) { + u32 tmp = raw_bootcount_load(reg); + + if ((tmp & 0xffff0000) != (magic & 0xffff0000)) + return -ENODEV; + + *a = (tmp & 0x0000ffff); + } else { + if (raw_bootcount_load(reg + 4) != magic) + return -ENODEV; + + *a = raw_bootcount_load(reg); + } + + return 0; +}; + +static int bootcount_mem_set(struct udevice *dev, const u32 a) +{ + struct bootcount_mem_priv *priv = dev_get_priv(dev); + void *reg = (void *)priv->base; + u32 magic = CONFIG_SYS_BOOTCOUNT_MAGIC; + uintptr_t flush_start = rounddown(priv->base, + CONFIG_SYS_CACHELINE_SIZE); + uintptr_t flush_end; + + if (priv->singleword) { + raw_bootcount_store(reg, (magic & 0xffff0000) | a); + flush_end = roundup(priv->base + 4, + CONFIG_SYS_CACHELINE_SIZE); + } else { + raw_bootcount_store(reg, a); + raw_bootcount_store(reg + 4, magic); + flush_end = roundup(priv->base + 8, + CONFIG_SYS_CACHELINE_SIZE); + } + flush_dcache_range(flush_start, flush_end); + + return 0; +}; + +static const struct bootcount_ops bootcount_mem_ops = { + .get = bootcount_mem_get, + .set = bootcount_mem_set, +}; + +static int bootcount_mem_probe(struct udevice *dev) +{ + struct bootcount_mem_priv *priv = dev_get_priv(dev); + + priv->base = (phys_addr_t)dev_read_addr(dev); + if (dev_read_bool(dev, "single-word")) + priv->singleword = true; + + return 0; +} + +static const struct udevice_id bootcount_mem_ids[] = { + { .compatible = "u-boot,bootcount" }, + { } +}; + +U_BOOT_DRIVER(bootcount_mem) = { + .name = "bootcount-mem", + .id = UCLASS_BOOTCOUNT, + .priv_auto_alloc_size = sizeof(struct bootcount_mem_priv), + .probe = bootcount_mem_probe, + .of_match = bootcount_mem_ids, + .ops = &bootcount_mem_ops, +}; +#endif