summaryrefslogtreecommitdiffstats
path: root/drivers/i2c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2024-11-20 12:37:06 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2024-11-20 12:37:06 -0800
commit131561f2ca075f3737c2b4821c4d067dfba0f55f (patch)
treef62a35cb236393a15cafd3b628c4113518527caf /drivers/i2c
parent7d7560666515855f67df6b0a78fecf2007d35dcc (diff)
parentbef29ca3a6458582ac13320d47bf2646e5734dc8 (diff)
downloadlinux-stable-131561f2ca075f3737c2b4821c4d067dfba0f55f.tar.gz
linux-stable-131561f2ca075f3737c2b4821c4d067dfba0f55f.tar.bz2
linux-stable-131561f2ca075f3737c2b4821c4d067dfba0f55f.zip
Merge tag 'gpio-updates-for-v6.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux
Pull gpio updates from Bartosz Golaszewski: "Three new drivers, support for some new models in existing ones and lots of various tweaks and improvements across the board (switching to using recommended APIs, code shrink and simplification, etc.). Also a new feature in the character device uAPI where we now notify the user-space about changes triggered by in-kernel users as well, not only when they were done by other user-space agents. Summary: GPIOLIB core: - use the new mem_is_zero() instead of memchr_inv(s, 0, n) - don't store debounce period twice needlessly - clean-up debugfs handling - remove leftover comments referring to no longer used spinlocks - unduplicate some operations like SRCU locks and initializing GPIO descriptors - constify the sysfs class struct - use lock guards in GPIO sysfs code - update GPIO uAPI internal flags all at once atomically for consistency with other places - modify the behavior of the sysfs interface by no longer exporting lines that are named inside the driver code or board files with the sysfs links bearing the line names as this has for many years been largely unused due to the prevalence of DT, ACPI and firmware nodes over board files and made the API inconsistent - for GPIO interrupt providers: free irqs that are still requested by users when removing the chip GPIO uAPI: - notify user-space about changes to GPIO lines' state (requested, released, reconfigured) triggered from the kernel as well (until now we'd only do this for changes triggered from user-space) - to that end: modify the internal workings of the notification mechanism by switching to an atomic notifier which allows us to send events from atomic context - also to that end store the debounce period in the GPIO descriptor struct and not in the character device context struct - while at it, also cover the corner-case of users introducing changes over sysfs while others watch them via the character device - don't report GPIO lines requested as interrupts as "used" to user-space as it can still request them as GPIOs New drivers: - GPIO part of the MFD Congatec Board Controller - PolarFire GPIO controller - GPIOs on FTDI FT2232H Driver improvements: - use generic device property accessors instead of OF-specific ones across many GPIO drivers (mpc8xxx, vf610, eic-sprd, davinci, ts4900, xilinx, mvebu) - use devres helpers to simplify error paths and either shrink or entirely remove the driver's remove() callback (grgpio, amdpt, menz127, max730x, ftgpio010, 74x164, ljca) - use helper variables to store the address of pdev->dev and avoid some line-breaks - use device_for_each_child_node_scoped() to avoid having to put the fwnode on breaks or errors (gpio-sim, gpio-dwapb, gpiolib-acpi) - use a scoped bitmap to simplify the code and drop goto labels in gpio-aggregator - drop unneeded Kconfig dependencies on OF_GPIO (grgpio, mveby, xilinx) - add support for new models to gpio-aspeed, gpio-rockchip and gpio-dwapb - clean-up ACPI handling and some other bits in gpio-xgene-sb - replace deprecated PCI functions in pcie-idio-24 and pci-idio-16 - allow to build davinci and mvebu drivers with COMPILE_TEST=y - remove dead code in gpio-mb86s7x - switch back to using platform_driver::remove() (after the conversion to remove_new()) across the GPIO drivers - remove remaining uses of GPIOF_ACTIVE_LOW across the tree and drop this deprecated symbol - convert the gpio-altera driver to no longer pull in the deprecated legacy-of-mm-gpiochip.h header - use of_property_present() instead of of_property_read_bool() in gpiolib-of and gpio-rockchip - allow to build the tegra186 driver on Tegra234 platforms in Kconfig Late fixes: - add a missing return value check after devm_kasprintf() to gpio-grgpio DT bindings: - document the ngpios property of gpio-mmio - add support for a new aspeed model - fix the example for st,nomadik-gpio Other: - kernel doc and comments tweaks - fix typos in TODO - reorder headers alphabetically in some drivers - fix incorrect format specifiers in gpio tools" * tag 'gpio-updates-for-v6.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux: (98 commits) gpio: tegra186: Allow to enable driver on Tegra234 gpio: grgpio: Add NULL check in grgpio_probe tools: gpio: Fix several incorrect format specifiers gpio: mpfs: add CoreGPIO support gpio: rockchip: support new version GPIO gpio: rockchip: change the GPIO version judgment logic gpio: rockchip: explan the format of the GPIO version ID gpiolib: cdev: use !mem_is_zero() instead of memchr_inv(s, 0, n) MAINTAINERS: add gpio driver to PolarFire entry gpio: Get rid of GPIOF_ACTIVE_LOW USB: gadget: pxa27x_udc: Avoid using GPIOF_ACTIVE_LOW pcmcia: soc_common: Avoid using GPIOF_ACTIVE_LOW leds: gpio: Avoid using GPIOF_ACTIVE_LOW Input: gpio_keys_polled - avoid using GPIOF_ACTIVE_LOW Input: gpio_keys - avoid using GPIOF_ACTIVE_LOW gpio: Use of_property_present() for non-boolean properties gpio: mpfs: add polarfire soc gpio support gpio: altera: Drop legacy-of-mm-gpiochip.h header gpio: pcie-idio-24: Replace deprecated PCI functions gpio: pci-idio-16: Replace deprecated PCI functions ...
Diffstat (limited to 'drivers/i2c')
-rw-r--r--drivers/i2c/busses/Kconfig10
-rw-r--r--drivers/i2c/busses/Makefile1
-rw-r--r--drivers/i2c/busses/i2c-cgbc.c406
3 files changed, 417 insertions, 0 deletions
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 6b3ba7e5723a..4977abcd7c46 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -535,6 +535,16 @@ config I2C_CBUS_GPIO
This driver can also be built as a module. If so, the module
will be called i2c-cbus-gpio.
+config I2C_CGBC
+ tristate "Congatec I2C Controller"
+ depends on MFD_CGBC
+ help
+ This driver supports the 2 I2C interfaces on the Congatec Board
+ Controller.
+
+ This driver can also be built as a module. If so, the module will
+ be called i2c-cgbc.ko.
+
config I2C_CPM
tristate "Freescale CPM1 or CPM2 (MPC8xx/826x)"
depends on CPM1 || CPM2
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index ecc07c50f2a0..a6bcbf2febcf 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -50,6 +50,7 @@ obj-$(CONFIG_I2C_BCM2835) += i2c-bcm2835.o
obj-$(CONFIG_I2C_BCM_IPROC) += i2c-bcm-iproc.o
obj-$(CONFIG_I2C_CADENCE) += i2c-cadence.o
obj-$(CONFIG_I2C_CBUS_GPIO) += i2c-cbus-gpio.o
+obj-$(CONFIG_I2C_CGBC) += i2c-cgbc.o
obj-$(CONFIG_I2C_CPM) += i2c-cpm.o
obj-$(CONFIG_I2C_DAVINCI) += i2c-davinci.o
obj-$(CONFIG_I2C_DESIGNWARE_CORE) += i2c-designware-core.o
diff --git a/drivers/i2c/busses/i2c-cgbc.c b/drivers/i2c/busses/i2c-cgbc.c
new file mode 100644
index 000000000000..eba0b205de11
--- /dev/null
+++ b/drivers/i2c/busses/i2c-cgbc.c
@@ -0,0 +1,406 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Congatec Board Controller I2C busses driver
+ *
+ * Copyright (C) 2024 Bootlin
+ * Author: Thomas Richard <thomas.richard@bootlin.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/iopoll.h>
+#include <linux/mfd/cgbc.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#define CGBC_I2C_PRIMARY_BUS_ID 0
+#define CGBC_I2C_PM_BUS_ID 4
+
+#define CGBC_I2C_CMD_START 0x40
+#define CGBC_I2C_CMD_STAT 0x48
+#define CGBC_I2C_CMD_DATA 0x50
+#define CGBC_I2C_CMD_SPEED 0x58
+
+#define CGBC_I2C_STAT_IDL 0x00
+#define CGBC_I2C_STAT_DAT 0x01
+#define CGBC_I2C_STAT_BUSY 0x02
+
+#define CGBC_I2C_START 0x80
+#define CGBC_I2C_STOP 0x40
+
+#define CGBC_I2C_LAST_ACK 0x80 /* send ACK on last read byte */
+
+/*
+ * Reference code defines 1kHz as min freq and 6.1MHz as max freq.
+ * But in practice, the board controller limits the frequency to 1MHz, and the
+ * 1kHz is not functional (minimal working freq is 50kHz).
+ * So use these values as limits.
+ */
+#define CGBC_I2C_FREQ_MIN_HZ 50000 /* 50 kHz */
+#define CGBC_I2C_FREQ_MAX_HZ 1000000 /* 1 MHz */
+
+#define CGBC_I2C_FREQ_UNIT_1KHZ 0x40
+#define CGBC_I2C_FREQ_UNIT_10KHZ 0x80
+#define CGBC_I2C_FREQ_UNIT_100KHZ 0xC0
+
+#define CGBC_I2C_FREQ_UNIT_MASK 0xC0
+#define CGBC_I2C_FREQ_VALUE_MASK 0x3F
+
+#define CGBC_I2C_READ_MAX_LEN 31
+#define CGBC_I2C_WRITE_MAX_LEN 32
+
+#define CGBC_I2C_CMD_HEADER_SIZE 4
+#define CGBC_I2C_CMD_SIZE (CGBC_I2C_CMD_HEADER_SIZE + CGBC_I2C_WRITE_MAX_LEN)
+
+enum cgbc_i2c_state {
+ CGBC_I2C_STATE_DONE = 0,
+ CGBC_I2C_STATE_INIT,
+ CGBC_I2C_STATE_START,
+ CGBC_I2C_STATE_READ,
+ CGBC_I2C_STATE_WRITE,
+ CGBC_I2C_STATE_ERROR,
+};
+
+struct i2c_algo_cgbc_data {
+ u8 bus_id;
+ unsigned long read_maxtime_us;
+};
+
+struct cgbc_i2c_data {
+ struct device *dev;
+ struct cgbc_device_data *cgbc;
+ struct i2c_adapter adap;
+ struct i2c_msg *msg;
+ int nmsgs;
+ int pos;
+ enum cgbc_i2c_state state;
+};
+
+struct cgbc_i2c_transfer {
+ u8 bus_id;
+ bool start;
+ bool stop;
+ bool last_ack;
+ u8 read;
+ u8 write;
+ u8 addr;
+ u8 data[CGBC_I2C_WRITE_MAX_LEN];
+};
+
+static u8 cgbc_i2c_freq_to_reg(unsigned int bus_frequency)
+{
+ u8 reg;
+
+ if (bus_frequency <= 10000)
+ reg = CGBC_I2C_FREQ_UNIT_1KHZ | (bus_frequency / 1000);
+ else if (bus_frequency <= 100000)
+ reg = CGBC_I2C_FREQ_UNIT_10KHZ | (bus_frequency / 10000);
+ else
+ reg = CGBC_I2C_FREQ_UNIT_100KHZ | (bus_frequency / 100000);
+
+ return reg;
+}
+
+static unsigned int cgbc_i2c_reg_to_freq(u8 reg)
+{
+ unsigned int freq = reg & CGBC_I2C_FREQ_VALUE_MASK;
+ u8 unit = reg & CGBC_I2C_FREQ_UNIT_MASK;
+
+ if (unit == CGBC_I2C_FREQ_UNIT_100KHZ)
+ return freq * 100000;
+ else if (unit == CGBC_I2C_FREQ_UNIT_10KHZ)
+ return freq * 10000;
+ else
+ return freq * 1000;
+}
+
+static int cgbc_i2c_get_status(struct i2c_adapter *adap)
+{
+ struct i2c_algo_cgbc_data *algo_data = adap->algo_data;
+ struct cgbc_i2c_data *i2c = i2c_get_adapdata(adap);
+ struct cgbc_device_data *cgbc = i2c->cgbc;
+ u8 cmd = CGBC_I2C_CMD_STAT | algo_data->bus_id;
+ u8 status;
+ int ret;
+
+ ret = cgbc_command(cgbc, &cmd, sizeof(cmd), NULL, 0, &status);
+ if (ret)
+ return ret;
+
+ return status;
+}
+
+static int cgbc_i2c_set_frequency(struct i2c_adapter *adap,
+ unsigned int bus_frequency)
+{
+ struct i2c_algo_cgbc_data *algo_data = adap->algo_data;
+ struct cgbc_i2c_data *i2c = i2c_get_adapdata(adap);
+ struct cgbc_device_data *cgbc = i2c->cgbc;
+ u8 cmd[2], data;
+ int ret;
+
+ if (bus_frequency > CGBC_I2C_FREQ_MAX_HZ ||
+ bus_frequency < CGBC_I2C_FREQ_MIN_HZ) {
+ dev_info(i2c->dev, "invalid frequency %u, using default\n", bus_frequency);
+ bus_frequency = I2C_MAX_STANDARD_MODE_FREQ;
+ }
+
+ cmd[0] = CGBC_I2C_CMD_SPEED | algo_data->bus_id;
+ cmd[1] = cgbc_i2c_freq_to_reg(bus_frequency);
+
+ ret = cgbc_command(cgbc, &cmd, sizeof(cmd), &data, 1, NULL);
+ if (ret)
+ return dev_err_probe(i2c->dev, ret,
+ "Failed to initialize I2C bus %s",
+ adap->name);
+
+ cmd[1] = 0x00;
+
+ ret = cgbc_command(cgbc, &cmd, sizeof(cmd), &data, 1, NULL);
+ if (ret)
+ return dev_err_probe(i2c->dev, ret,
+ "Failed to get I2C bus frequency");
+
+ bus_frequency = cgbc_i2c_reg_to_freq(data);
+
+ dev_dbg(i2c->dev, "%s is running at %d Hz\n", adap->name, bus_frequency);
+
+ /*
+ * The read_maxtime_us variable represents the maximum time to wait
+ * for data during a read operation. The maximum amount of data that
+ * can be read by a command is CGBC_I2C_READ_MAX_LEN.
+ * Therefore, calculate the max time to properly size the timeout.
+ */
+ algo_data->read_maxtime_us = (BITS_PER_BYTE + 1) * CGBC_I2C_READ_MAX_LEN
+ * USEC_PER_SEC / bus_frequency;
+
+ return 0;
+}
+
+static unsigned int cgbc_i2c_xfer_to_cmd(struct cgbc_i2c_transfer xfer, u8 *cmd)
+{
+ int i = 0;
+
+ cmd[i++] = CGBC_I2C_CMD_START | xfer.bus_id;
+
+ cmd[i] = (xfer.start) ? CGBC_I2C_START : 0x00;
+ if (xfer.stop)
+ cmd[i] |= CGBC_I2C_STOP;
+ cmd[i++] |= (xfer.start) ? xfer.write + 1 : xfer.write;
+
+ cmd[i++] = (xfer.last_ack) ? (xfer.read | CGBC_I2C_LAST_ACK) : xfer.read;
+
+ if (xfer.start)
+ cmd[i++] = xfer.addr;
+
+ if (xfer.write > 0)
+ memcpy(&cmd[i], &xfer.data, xfer.write);
+
+ return i + xfer.write;
+}
+
+static int cgbc_i2c_xfer_msg(struct i2c_adapter *adap)
+{
+ struct i2c_algo_cgbc_data *algo_data = adap->algo_data;
+ struct cgbc_i2c_data *i2c = i2c_get_adapdata(adap);
+ struct cgbc_device_data *cgbc = i2c->cgbc;
+ struct i2c_msg *msg = i2c->msg;
+ u8 cmd[CGBC_I2C_CMD_SIZE];
+ int ret, max_len, len, i;
+ unsigned int cmd_len;
+ u8 cmd_data;
+
+ struct cgbc_i2c_transfer xfer = {
+ .bus_id = algo_data->bus_id,
+ .addr = i2c_8bit_addr_from_msg(msg),
+ };
+
+ if (i2c->state == CGBC_I2C_STATE_DONE)
+ return 0;
+
+ ret = cgbc_i2c_get_status(adap);
+
+ if (ret == CGBC_I2C_STAT_BUSY)
+ return -EBUSY;
+ else if (ret < 0)
+ goto err;
+
+ if (i2c->state == CGBC_I2C_STATE_INIT ||
+ (i2c->state == CGBC_I2C_STATE_WRITE && msg->flags & I2C_M_RD))
+ xfer.start = true;
+
+ i2c->state = (msg->flags & I2C_M_RD) ? CGBC_I2C_STATE_READ : CGBC_I2C_STATE_WRITE;
+
+ max_len = (i2c->state == CGBC_I2C_STATE_READ) ?
+ CGBC_I2C_READ_MAX_LEN : CGBC_I2C_WRITE_MAX_LEN;
+
+ if (msg->len - i2c->pos > max_len) {
+ len = max_len;
+ } else {
+ len = msg->len - i2c->pos;
+
+ if (i2c->nmsgs == 1)
+ xfer.stop = true;
+ }
+
+ if (i2c->state == CGBC_I2C_STATE_WRITE) {
+ xfer.write = len;
+ xfer.read = 0;
+
+ for (i = 0; i < len; i++)
+ xfer.data[i] = msg->buf[i2c->pos + i];
+
+ cmd_len = cgbc_i2c_xfer_to_cmd(xfer, &cmd[0]);
+
+ ret = cgbc_command(cgbc, &cmd, cmd_len, NULL, 0, NULL);
+ if (ret)
+ goto err;
+ } else if (i2c->state == CGBC_I2C_STATE_READ) {
+ xfer.write = 0;
+ xfer.read = len;
+
+ if (i2c->nmsgs > 1 || msg->len - i2c->pos > max_len)
+ xfer.read |= CGBC_I2C_LAST_ACK;
+
+ cmd_len = cgbc_i2c_xfer_to_cmd(xfer, &cmd[0]);
+ ret = cgbc_command(cgbc, &cmd, cmd_len, NULL, 0, NULL);
+ if (ret)
+ goto err;
+
+ ret = read_poll_timeout(cgbc_i2c_get_status, ret,
+ ret != CGBC_I2C_STAT_BUSY, 0,
+ 2 * algo_data->read_maxtime_us, false, adap);
+ if (ret < 0)
+ goto err;
+
+ cmd_data = CGBC_I2C_CMD_DATA | algo_data->bus_id;
+ ret = cgbc_command(cgbc, &cmd_data, sizeof(cmd_data),
+ msg->buf + i2c->pos, len, NULL);
+ if (ret)
+ goto err;
+ }
+
+ if (len == (msg->len - i2c->pos)) {
+ i2c->msg++;
+ i2c->nmsgs--;
+ i2c->pos = 0;
+ } else {
+ i2c->pos += len;
+ }
+
+ if (i2c->nmsgs == 0)
+ i2c->state = CGBC_I2C_STATE_DONE;
+
+ return 0;
+
+err:
+ i2c->state = CGBC_I2C_STATE_ERROR;
+ return ret;
+}
+
+static int cgbc_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
+ int num)
+{
+ struct cgbc_i2c_data *i2c = i2c_get_adapdata(adap);
+ unsigned long timeout = jiffies + HZ;
+ int ret;
+
+ i2c->state = CGBC_I2C_STATE_INIT;
+ i2c->msg = msgs;
+ i2c->nmsgs = num;
+ i2c->pos = 0;
+
+ while (time_before(jiffies, timeout)) {
+ ret = cgbc_i2c_xfer_msg(adap);
+ if (i2c->state == CGBC_I2C_STATE_DONE)
+ return num;
+
+ if (i2c->state == CGBC_I2C_STATE_ERROR)
+ return ret;
+
+ if (ret == 0)
+ timeout = jiffies + HZ;
+ }
+
+ i2c->state = CGBC_I2C_STATE_ERROR;
+ return -ETIMEDOUT;
+}
+
+static u32 cgbc_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~(I2C_FUNC_SMBUS_QUICK));
+}
+
+static const struct i2c_algorithm cgbc_i2c_algorithm = {
+ .master_xfer = cgbc_i2c_xfer,
+ .functionality = cgbc_i2c_func,
+};
+
+static struct i2c_algo_cgbc_data cgbc_i2c_algo_data[] = {
+ { .bus_id = CGBC_I2C_PRIMARY_BUS_ID },
+ { .bus_id = CGBC_I2C_PM_BUS_ID },
+};
+
+static const struct i2c_adapter cgbc_i2c_adapter[] = {
+ {
+ .owner = THIS_MODULE,
+ .name = "Congatec General Purpose I2C adapter",
+ .class = I2C_CLASS_DEPRECATED,
+ .algo = &cgbc_i2c_algorithm,
+ .algo_data = &cgbc_i2c_algo_data[0],
+ .nr = -1,
+ },
+ {
+ .owner = THIS_MODULE,
+ .name = "Congatec Power Management I2C adapter",
+ .class = I2C_CLASS_DEPRECATED,
+ .algo = &cgbc_i2c_algorithm,
+ .algo_data = &cgbc_i2c_algo_data[1],
+ .nr = -1,
+ },
+};
+
+static int cgbc_i2c_probe(struct platform_device *pdev)
+{
+ struct cgbc_device_data *cgbc = dev_get_drvdata(pdev->dev.parent);
+ struct cgbc_i2c_data *i2c;
+ int ret;
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL);
+ if (!i2c)
+ return -ENOMEM;
+
+ i2c->cgbc = cgbc;
+ i2c->dev = &pdev->dev;
+ i2c->adap = cgbc_i2c_adapter[pdev->id];
+ i2c->adap.dev.parent = i2c->dev;
+ i2c_set_adapdata(&i2c->adap, i2c);
+ platform_set_drvdata(pdev, i2c);
+
+ ret = cgbc_i2c_set_frequency(&i2c->adap, I2C_MAX_STANDARD_MODE_FREQ);
+ if (ret)
+ return ret;
+
+ return i2c_add_numbered_adapter(&i2c->adap);
+}
+
+static void cgbc_i2c_remove(struct platform_device *pdev)
+{
+ struct cgbc_i2c_data *i2c = platform_get_drvdata(pdev);
+
+ i2c_del_adapter(&i2c->adap);
+}
+
+static struct platform_driver cgbc_i2c_driver = {
+ .driver = {
+ .name = "cgbc-i2c",
+ },
+ .probe = cgbc_i2c_probe,
+ .remove_new = cgbc_i2c_remove,
+};
+
+module_platform_driver(cgbc_i2c_driver);
+
+MODULE_DESCRIPTION("Congatec Board Controller I2C Driver");
+MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cgbc_i2c");