diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2024-11-20 12:37:06 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2024-11-20 12:37:06 -0800 |
commit | 131561f2ca075f3737c2b4821c4d067dfba0f55f (patch) | |
tree | f62a35cb236393a15cafd3b628c4113518527caf /drivers/i2c | |
parent | 7d7560666515855f67df6b0a78fecf2007d35dcc (diff) | |
parent | bef29ca3a6458582ac13320d47bf2646e5734dc8 (diff) | |
download | linux-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/Kconfig | 10 | ||||
-rw-r--r-- | drivers/i2c/busses/Makefile | 1 | ||||
-rw-r--r-- | drivers/i2c/busses/i2c-cgbc.c | 406 |
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"); |