From deb1f62ea1d454b496df3231cdbe8f11ec3ac1cd Mon Sep 17 00:00:00 2001 From: Fei Liu Date: Tue, 18 Nov 2025 17:24:52 +0800 Subject: [PATCH 1/2] anolis: dt-bindings: i2c: Add binding for LRW I2C ANBZ: #27064 Add documentation for LRW I2C devicetree bindings. Signed-off-by: Fei Liu Signed-off-by: Xiaowei Han Signed-off-by: Qingtao Liu --- .../devicetree/bindings/i2c/lrw,lrw-i2c.yaml | 99 +++++++++++++++++++ MAINTAINERS | 8 ++ 2 files changed, 107 insertions(+) create mode 100644 Documentation/devicetree/bindings/i2c/lrw,lrw-i2c.yaml diff --git a/Documentation/devicetree/bindings/i2c/lrw,lrw-i2c.yaml b/Documentation/devicetree/bindings/i2c/lrw,lrw-i2c.yaml new file mode 100644 index 000000000000..380864aadff8 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/lrw,lrw-i2c.yaml @@ -0,0 +1,99 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause + +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/i2c/lrw-i2c.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: LRW I2C + +maintainers: + - Fei Liu + - Xiaowei Han + - Qingtao Liu + +description: | + Should be something similar to "lrw,-i2c" + for the I2C as integrated on a particular chip, It supports + multiple CPU architectures, currently including e.g. RISC-V and ARM. + +properties: + compatible: + const: lrw,sc-i2c + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-frequency: + description: | + frequency of the bus clock in Hz defaults to 100 kHz when not specified + + fs_hcnt: + description: | + High-level duration count in Standard/Fast Mode (100kHz / 400kHz / 1MHz) + Represents the number of clock cycles (clk cycles) that the signal remains at high level + during Standard or Fast Mode communication + + fs_lcnt: + description: | + Low-level duration count in Standard/Fast Mode (100kHz / 400kHz / 1MHz) + Represents the number of clock cycles (clk cycles) that the signal remains at low level + during Standard or Fast Mode communication + + hs_hcnt: + description: | + High-level duration count in High-Speed Mode (>1MHz). + Represents the number of clock cycles (clk cycles) that the signal remains at high level + during High-Speed Mode communication (baud rate greater than 1MHz) + + hs_lcnt: + description: | + Low-level duration count in High-Speed Mode (>1MHz). + Represents the number of clock cycles (clk cycles) that the signal remains at low level + during High-Speed Mode communication (baud rate greater than 1MHz) + + sda_hold_time: + description: | + Hold time configuration for SDA signal, in clock cycles (clk cycles) + The value is a 32-bit integer with bitfield division: + - Bits [23:16]: Hold time in RX mode (SDA signal hold duration when receiving data) + - Bits [15:0]: Hold time in TX mode (SDA signal hold duration when transmitting data) + + sda_stuck_at_low_timeout: + description: | + Timeout threshold when SDA signal is stuck at low level, in clock cycles (clk cycles). + When the SDA signal remains low for a duration exceeding this count, the I2C controller + will trigger the bus recovery mechanism to restore normal communication + +required: + - compatible + - interrupts + - reg + - clock-frequency + - clocks + +additionalProperties: false + +examples: + - | + i2c@825e822000 { + compatible = "lrw,sc-i2c"; + interrupt-parent = <&iod1_socbar_s0_aplic>; + interrupts = <0x77 0x4>; + reg = <0x82 0x08121000 0x00 0x1000>; + clocks = <&sc_i2c_clk>; + clock-frequency = <100000>; + fs_hcnt = <0x1ef>; + fs_lcnt = <0x1f4>; + hs_hcnt = <0x2d>; + hs_lcnt = <0x33>; + sda_hold_time = <0x10008>; + sda_stuck_at_low_timeout = <0x262599>; + }; + diff --git a/MAINTAINERS b/MAINTAINERS index d2f23a640dbc..f4e4dcadd944 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12584,6 +12584,14 @@ L: linux-serial@vger.kernel.org S: Maintained F: drivers/tty/serial/lrw_uart.c +LRW I2C DEVICE TREE SUPPORT +M: Fei Liu +M: Xiaowei Han +R: Qingtao Liu +L: devicetree@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/i2c/lrw,lrw-i2c.yaml + M68K ARCHITECTURE M: Geert Uytterhoeven L: linux-m68k@lists.linux-m68k.org -- Gitee From 40b2e49987d51ed9aca69dfdb790f89f05d7d64c Mon Sep 17 00:00:00 2001 From: Fei Liu Date: Tue, 18 Nov 2025 17:53:48 +0800 Subject: [PATCH 2/2] anolis: i2c: Add driver for the LRW I2C ANBZ: #27064 This commit introduces a driver for the LRW I2C controller Key features of the I2C driver: - Supports host communication interfaces under the standard I2C protocol - Supports 100kHz/400kHz/1MHz bus speeds - Supports SMBus protocol block read/write features - Supports recovery mechanisms under bus abnormal conditions - Supports obtaining timing parameters from device tree or ACPI tables - Supports standard driver lifecycle interfaces Signed-off-by: Fei Liu Signed-off-by: Xiaowei Han Signed-off-by: Qingtao Liu --- MAINTAINERS | 10 + .../default/CONFIG_I2C_LRW_PLATFORM | 1 + .../L1-RECOMMEND/riscv/CONFIG_I2C_LRW_CORE | 1 + .../riscv/CONFIG_I2C_LRW_PLATFORM | 1 + drivers/i2c/busses/Kconfig | 15 + drivers/i2c/busses/Makefile | 4 + drivers/i2c/busses/i2c-lrw-core.h | 350 ++++++ drivers/i2c/busses/i2c-lrw-master.c | 1075 +++++++++++++++++ drivers/i2c/busses/i2c-lrw-platdrv.c | 235 ++++ 9 files changed, 1692 insertions(+) create mode 100644 anolis/configs/L1-RECOMMEND/default/CONFIG_I2C_LRW_PLATFORM create mode 100644 anolis/configs/L1-RECOMMEND/riscv/CONFIG_I2C_LRW_CORE create mode 100644 anolis/configs/L1-RECOMMEND/riscv/CONFIG_I2C_LRW_PLATFORM create mode 100644 drivers/i2c/busses/i2c-lrw-core.h create mode 100644 drivers/i2c/busses/i2c-lrw-master.c create mode 100644 drivers/i2c/busses/i2c-lrw-platdrv.c diff --git a/MAINTAINERS b/MAINTAINERS index f4e4dcadd944..e4d1263e6288 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12592,6 +12592,16 @@ L: devicetree@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/i2c/lrw,lrw-i2c.yaml +LRW I2C DRIVER +M: Fei Liu +M: Xiaowei Han +R: Qingtao Liu +L: linux-i2c@vger.kernel.org +S: Maintained +F: drivers/i2c/busses/i2c-lrw-core.h +F: drivers/i2c/busses/i2c-lrw-master.c +F: drivers/i2c/busses/i2c-lrw-platdrv.c + M68K ARCHITECTURE M: Geert Uytterhoeven L: linux-m68k@lists.linux-m68k.org diff --git a/anolis/configs/L1-RECOMMEND/default/CONFIG_I2C_LRW_PLATFORM b/anolis/configs/L1-RECOMMEND/default/CONFIG_I2C_LRW_PLATFORM new file mode 100644 index 000000000000..aa7232b14cc1 --- /dev/null +++ b/anolis/configs/L1-RECOMMEND/default/CONFIG_I2C_LRW_PLATFORM @@ -0,0 +1 @@ +# CONFIG_I2C_LRW_PLATFORM is not set diff --git a/anolis/configs/L1-RECOMMEND/riscv/CONFIG_I2C_LRW_CORE b/anolis/configs/L1-RECOMMEND/riscv/CONFIG_I2C_LRW_CORE new file mode 100644 index 000000000000..b480084af782 --- /dev/null +++ b/anolis/configs/L1-RECOMMEND/riscv/CONFIG_I2C_LRW_CORE @@ -0,0 +1 @@ +CONFIG_I2C_LRW_CORE=m diff --git a/anolis/configs/L1-RECOMMEND/riscv/CONFIG_I2C_LRW_PLATFORM b/anolis/configs/L1-RECOMMEND/riscv/CONFIG_I2C_LRW_PLATFORM new file mode 100644 index 000000000000..76eb41de8431 --- /dev/null +++ b/anolis/configs/L1-RECOMMEND/riscv/CONFIG_I2C_LRW_PLATFORM @@ -0,0 +1 @@ +CONFIG_I2C_LRW_PLATFORM=m diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 32a621e43f40..abb1949453ff 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -639,6 +639,21 @@ config I2C_DESIGNWARE_PCI This driver can also be built as a module. If so, the module will be called i2c-designware-pci. +config I2C_LRW_CORE + tristate + select REGMAP + +config I2C_LRW_PLATFORM + tristate "LRW Platform" + depends on COMMON_CLK + select I2C_LRW_CORE + help + If you say yes to this option, support will be included for the + LRW I2C adapter. + + This driver can also be built as a module. If so, the module + will be called i2c-lrw-platform. + config I2C_DIGICOLOR tristate "Conexant Digicolor I2C driver" depends on ARCH_DIGICOLOR || COMPILE_TEST diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 738519b0a9cb..e18c41e40a03 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -63,6 +63,10 @@ i2c-designware-platform-$(CONFIG_I2C_DESIGNWARE_AMDPSP) += i2c-designware-amdpsp i2c-designware-platform-$(CONFIG_I2C_DESIGNWARE_BAYTRAIL) += i2c-designware-baytrail.o obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o i2c-designware-pci-y := i2c-designware-pcidrv.o +obj-$(CONFIG_I2C_LRW_CORE) += i2c-lrw-core.o +i2c-lrw-core-y += i2c-lrw-master.o +obj-$(CONFIG_I2C_LRW_PLATFORM) += i2c-lrw-platform.o +i2c-lrw-platform-y := i2c-lrw-platdrv.o obj-$(CONFIG_I2C_DIGICOLOR) += i2c-digicolor.o obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o obj-$(CONFIG_I2C_EMEV2) += i2c-emev2.o diff --git a/drivers/i2c/busses/i2c-lrw-core.h b/drivers/i2c/busses/i2c-lrw-core.h new file mode 100644 index 000000000000..474206810dd8 --- /dev/null +++ b/drivers/i2c/busses/i2c-lrw-core.h @@ -0,0 +1,350 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * I2C adapter driver for LRW + * + * Copyright (c) 2025, LRW CORPORATION. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define LRW_IC_DEFAULT_FUNCTIONALITY \ + (I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | \ + I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA | \ + I2C_FUNC_SMBUS_I2C_BLOCK) + +#define I2C_TXFLR_MASK 0xFF +#define I2C_RXFLR_MASK 0xFF +#define I2C_RXFLR_SHIFT 16 +#define I2C_FIFO_DEPTH 32 + +#define LRW_IC_CON_MASTER BIT(0) +#define LRW_IC_CON_SPEED_STD_FAST (0 << 1) +#define LRW_IC_CON_SPEED_HIGH (1 << 1) +#define LRW_IC_CON_SPEED_MASK BIT(1) +#define LRW_IC_CON_10BITADDR_SLAVE BIT(2) +#define LRW_IC_CON_10BITADDR_MASTER BIT(3) +#define LRW_IC_CON_RESTART_EN BIT(4) +#define LRW_IC_CON_STOP_DET_IFADDRESSED BIT(5) +#define LRW_IC_CON_TX_EMPTY_CTRL BIT(6) +#define LRW_IC_CON_RX_FIFO_FULL_HLD_CTRL BIT(7) +#define IC_CON_BUS_CLEAR_CTRL_POS BIT(8) + +#define LRW_IC_DATA_CMD_DAT GENMASK(7, 0) + +/* + * Registers offset + */ +#define LRW_IC_VERSION 0x0 +#define LRW_IC_CON 0x04 +#define LRW_IC_ENABLE 0x08 +#define LRW_IC_DATA_CMD 0x0c + +#define LRW_IC_FS_SCL_HCNT 0x10 +#define LRW_IC_FS_SCL_LCNT 0x14 +#define LRW_IC_FS_SPKLEN 0x18 +#define LRW_IC_HS_SCL_HCNT 0x1c +#define LRW_IC_HS_SCL_LCNT 0x20 +#define LRW_IC_HS_SPKLEN 0x24 +#define LRW_IC_HS_CTRCODE 0x28 +#define LRW_IC_TAR 0x2c +#define LRW_IC_SAR1 0x30 +#define LRW_IC_SAR2 0x34 +#define LRW_IC_SAR3 0x38 +#define LRW_IC_SAR4 0x3c +#define LRW_IC_RX_TL 0x40 +#define LRW_IC_TX_TL 0x44 +#define LRW_IC_FIFO_LEVEL 0x48 +#define LRW_IC_TX_ABRT_SOURCE 0x4c +#define LRW_IC_CLR_RAW_INTR 0x50 +#define LRW_IC_INTR_MASK 0x54 +#define LRW_IC_INTR_STAT 0x58 +#define LRW_IC_DMA_CTRL 0x5c +#define LRW_IC_SDA_SETUP 0x60 +#define LRW_IC_SDA_HOLD 0x64 +#define LRW_IC_BUS_FREE 0x68 + +#define LRW_IC_START_HOLD 0x6c +#define LRW_IC_RESTART_HOLD_HS 0x70 +#define LRW_IC_RESTART_SETUP 0x74 +#define LRW_IC_STOP_SETUP 0x78 + +#define LRW_IC_SCL_STUCK_AT_LOW_TIMEOUT_MIN 0x7c +#define LRW_IC_SCL_STUCK_AT_LOW_TIMEOUT_MAX 0x80 +#define LRW_IC_SDA_STUCK_AT_LOW_TIMEOUT 0x84 +#define LRW_IC_REG_TIMEOUT_RST 0x88 +#define LRW_IC_STATUS 0x8c +#define LRW_IC_DEBUG 0x90 + +#define LRW_SMBUS_CLR_RAW_INTR 0xa4 +#define LRW_SMBUS_INTR_MASK 0xa8 +#define LRW_SMBUS_INTR_STAT 0xac + +#define LRW_IC_CLR_INTR BIT(31) + +#define LRW_IC_SDA_HOLD_MIN_VERS 0x1C240615 +#define LRW_IC_INTR_RX_OVER BIT(0) +#define LRW_IC_INTR_RX_FULL BIT(1) +#define LRW_IC_INTR_TX_EMPTY BIT(2) +#define LRW_IC_INTR_RD_REQ BIT(3) +#define LRW_IC_INTR_TX_ABRT BIT(4) +#define LRW_IC_INTR_RX_DONE BIT(5) +#define LRW_IC_INTR_ACTIVITY BIT(6) +#define LRW_IC_INTR_STOP_DET BIT(7) +#define LRW_IC_INTR_START_DET BIT(8) +#define LRW_IC_INTR_GEN_CALL BIT(9) +#define LRW_IC_INTR_RESTART_DET BIT(10) +#define LRW_IC_INTR_MST_ON_HOLD BIT(11) + +#define LRW_IC_INTR_DEFAULT_MASK \ + (LRW_IC_INTR_RX_FULL | LRW_IC_INTR_TX_ABRT | LRW_IC_INTR_STOP_DET) +#define LRW_IC_INTR_MASTER_MASK \ + (LRW_IC_INTR_DEFAULT_MASK | LRW_IC_INTR_TX_EMPTY) +#define LRW_IC_INTR_SLAVE_MASK \ + (LRW_IC_INTR_DEFAULT_MASK | LRW_IC_INTR_RX_DONE | LRW_IC_INTR_RD_REQ) + +#define LRW_IC_ENABLE_ABORT BIT(1) + +#define LRW_IC_STATUS_ACTIVITY 0x1 +#define LRW_IC_STATUS_TFE BIT(2) +#define LRW_IC_STATUS_MASTER_ACTIVITY BIT(5) +#define LRW_IC_STATUS_SLAVE_ACTIVITY BIT(6) + +#define LRW_IC_SDA_HOLD_RX_SHIFT 16 +#define LRW_IC_SDA_HOLD_RX_MASK GENMASK(23, LRW_IC_SDA_HOLD_RX_SHIFT) + +#define LRW_IC_ERR_TX_ABRT 0x1 +#define I2C_SCL_TIMEOUT_ERROR 0x2 + +#define LRW_IC_TAR_10BITADDR_MASTER BIT(12) + +#define IC_INTR_SCL_STUCK_AT_LOW_MIN BIT(12) +/*IC_TX_ABRT_SOURCE*/ +#define IC_ABRT_SDA_STUCK_AT_LOW_POS BIT(17) +/* IC_ENABLE */ +#define IC_SDA_STUCK_RECOVERY_ENABLE_POS BIT(3) +#define IC_SDA_STUCK_AT_LOW_RECOVERIED 0x0 + +/* + * Sofware status flags + */ +#define STATUS_ACTIVE BIT(0) +#define STATUS_WRITE_IN_PROGRESS BIT(1) +#define STATUS_READ_IN_PROGRESS BIT(2) +#define STATUS_MASK GENMASK(2, 0) + +/* + * operation modes + */ +#define LRW_IC_MASTER 0 +#define LRW_IC_SLAVE 1 + +/* + * Hardware abort codes from the LRW_IC_TX_ABRT_SOURCE register + * + * Only expected abort codes are listed here + * refer to the datasheet for the full list + */ +#define ABRT_7B_ADDR_NOACK 0 +#define ABRT_10ADDR1_NOACK 1 +#define ABRT_10ADDR2_NOACK 2 +#define ABRT_TXDATA_NOACK 3 +#define ABRT_GCALL_NOACK 4 +#define ABRT_GCALL_READ 5 +#define ABRT_HS_ACK_DET 6 +#define ABRT_SBYTE_ACKDET 7 +#define ABRT_HS_NORSTRT 8 +#define ABRT_SBYTE_NORSTRT 9 +#define ABRT_10B_RD_NORSTRT 10 +#define ABRT_MASTER_DIS 11 +#define ARB_LOST 12 +#define ABRT_SLAVE_FLUSH_TXFIFO 13 +#define ABRT_SLAVE_ARBLOST 14 +#define ABRT_SLAVE_RD_INTX 15 +#define ABRT_USER_ABRT 16 +#define ABRT_SDA_STUCK_AT_LOW 17 + +#define LRW_IC_TX_ABRT_7B_ADDR_NOACK BIT(ABRT_7B_ADDR_NOACK) +#define LRW_IC_TX_ABRT_10ADDR1_NOACK BIT(ABRT_10ADDR1_NOACK) +#define LRW_IC_TX_ABRT_10ADDR2_NOACK BIT(ABRT_10ADDR2_NOACK) +#define LRW_IC_TX_ABRT_TXDATA_NOACK BIT(ABRT_TXDATA_NOACK) +#define LRW_IC_TX_ABRT_GCALL_NOACK BIT(ABRT_GCALL_NOACK) +#define LRW_IC_TX_ABRT_GCALL_READ BIT(ABRT_GCALL_READ) +#define LRW_IC_TX_ABRT_SBYTE_ACKDET BIT(ABRT_SBYTE_ACKDET) +#define LRW_IC_TX_ABRT_SBYTE_NORSTRT BIT(ABRT_SBYTE_NORSTRT) +#define LRW_IC_TX_ABRT_10B_RD_NORSTRT BIT(ABRT_10B_RD_NORSTRT) +#define LRW_IC_TX_ABRT_MASTER_DIS BIT(ABRT_MASTER_DIS) +#define LRW_IC_TX_ARB_LOST BIT(ARB_LOST) +#define LRW_IC_TX_ABRT_SDA_STUCK_AT_LOW BIT(ABRT_SDA_STUCK_AT_LOW) +#define LRW_IC_RX_ABRT_SLAVE_RD_INTX BIT(ABRT_SLAVE_RD_INTX) +#define LRW_IC_RX_ABRT_SLAVE_ARBLOST BIT(ABRT_SLAVE_ARBLOST) +#define LRW_IC_RX_ABRT_SLAVE_FLUSH_TXFIFO BIT(ABRT_SLAVE_FLUSH_TXFIFO) + +#define LRW_IC_TX_ABRT_NOACK \ + (LRW_IC_TX_ABRT_7B_ADDR_NOACK | LRW_IC_TX_ABRT_10ADDR1_NOACK | \ + LRW_IC_TX_ABRT_10ADDR2_NOACK | LRW_IC_TX_ABRT_TXDATA_NOACK | \ + LRW_IC_TX_ABRT_GCALL_NOACK) + +struct clk; +struct device; +struct reset_control; + +/** + * struct lrw_i2c_dev - private i2c-lrw data + * @dev: driver model device node + * @map: IO registers map + * @sysmap: System controller registers map + * @base: IO registers pointer + * @ext: Extended IO registers pointer + * @cmd_complete: tx completion indicator + * @clk: input reference clock + * @pclk: clock required to access the registers + * @rst: optional reset for the controller + * @slave: represent an I2C slave device + * @get_clk_rate_khz: callback to retrieve IP specific bus speed + * @cmd_err: run time hadware error code + * @msgs: points to an array of messages currently being transferred + * @msgs_num: the number of elements in msgs + * @msg_write_idx: the element index of the current tx message in the msgs array + * @tx_buf_len: the length of the current tx buffer + * @tx_buf: the current tx buffer + * @msg_read_idx: the element index of the current rx message in the msgs array + * @rx_buf_len: the length of the current rx buffer + * @rx_buf: the current rx buffer + * @msg_err: error status of the current transfer + * @status: i2c master status, one of STATUS_* + * @abort_source: copy of the TX_ABRT_SOURCE register + * @irq: interrupt number for the i2c master + * @flags: platform specific flags like type of IO accessors or model + * @adapter: i2c subsystem adapter node + * @functionality: I2C_FUNC_* ORed bits to reflect what controller does support + * @master_cfg: configuration for the master device + * @tx_fifo_depth: depth of the hardware tx fifo + * @rx_fifo_depth: depth of the hardware rx fifo + * @rx_outstanding: current master-rx elements in tx fifo + * @timings: bus clock frequency, SDA hold and other timings + * @sda_hold_time: SDA hold value + * @fs_hcnt: fast speed HCNT value + * @fs_lcnt: fast speed LCNT value + * @hs_hcnt: high speed HCNT value + * @hs_lcnt: high speed LCNT value + * @acquire_lock: function to acquire a hardware lock on the bus + * @release_lock: function to release a hardware lock on the bus + * @semaphore_idx: Index of table with semaphore type attached to the bus. It's + * -1 if there is no semaphore. + * @shared_with_punit: true if this bus is shared with the SoCs PUNIT + * @disable: function to disable the controller + * @init: function to initialize the I2C hardware + * @set_sda_hold_time: callback to retrieve IP specific SDA hold timing + * @rinfo: I²C GPIO recovery information + * + * HCNT and LCNT parameters can be used if the platform knows more accurate + * values than the one computed based only on the input clock frequency. + * Leave them to be %0 if not used. + */ +struct i2c_lrw_dev { + struct device *dev; + struct regmap *map; + struct regmap *sysmap; + void __iomem *base; + void __iomem *ext; + struct completion cmd_complete; + struct clk *clk; + struct clk *pclk; + struct reset_control *rst; + struct i2c_client *slave; + u32 (*get_clk_rate_khz)(struct i2c_lrw_dev *dev); + int cmd_err; + struct i2c_msg *msgs; + int msgs_num; + int msg_write_idx; + u32 tx_buf_len; + u8 *tx_buf; + int msg_read_idx; + u32 rx_buf_len; + u8 *rx_buf; + int msg_err; + unsigned int status; + unsigned int abort_source; + int irq; + u32 flags; + struct i2c_adapter adapter; + u32 functionality; + u32 master_cfg; + unsigned int tx_fifo_depth; + unsigned int rx_fifo_depth; + int rx_outstanding; + struct i2c_timings timings; + u32 sda_hold_time; + u32 sda_stuck_at_low_timeout; + u16 fs_hcnt; + u16 fs_lcnt; + u16 hs_hcnt; + u16 hs_lcnt; + int (*acquire_lock)(void); + void (*release_lock)(void); + int semaphore_idx; + void (*disable)(struct i2c_lrw_dev *dev); + int (*init)(struct i2c_lrw_dev *dev); + int (*set_sda_hold_time)(struct i2c_lrw_dev *dev); + struct i2c_bus_recovery_info rinfo; +}; + +#define MODEL_MASK GENMASK(11, 8) + +int i2c_lrw_prepare_clk(struct i2c_lrw_dev *dev, bool prepare); + +static inline void __i2c_lrw_enable(struct i2c_lrw_dev *dev) +{ + dev->status |= STATUS_ACTIVE; + regmap_write(dev->map, LRW_IC_ENABLE, 1); +} + +static inline void __i2c_lrw_disable_nowait(struct i2c_lrw_dev *dev) +{ + regmap_write(dev->map, LRW_IC_ENABLE, 0); + dev->status &= ~STATUS_ACTIVE; +} + +void __i2c_lrw_disable(struct i2c_lrw_dev *dev); + +extern void i2c_lrw_configure_master(struct i2c_lrw_dev *dev); +extern int i2c_lrw_probe_master(struct i2c_lrw_dev *dev); + +static inline int i2c_lrw_probe(struct i2c_lrw_dev *dev) +{ + return i2c_lrw_probe_master(dev); +} + +static inline void i2c_lrw_configure(struct i2c_lrw_dev *dev) +{ + i2c_lrw_configure_master(dev); +} + +static inline int i2c_lrw_probe_lock_support(struct i2c_lrw_dev *dev) +{ + return 0; +} + +int i2c_lrw_validate_speed(struct i2c_lrw_dev *dev); +void i2c_lrw_adjust_bus_speed(struct i2c_lrw_dev *dev); + +#if IS_ENABLED(CONFIG_ACPI) +int i2c_lrw_acpi_configure(struct device *device); +static inline int i2c_lrw_dt_configure(struct device *device) +{ + return -ENODEV; +} +#else +static inline int i2c_lrw_acpi_configure(struct device *device) +{ + return -ENODEV; +} +int i2c_lrw_dt_configure(struct device *device); +#endif diff --git a/drivers/i2c/busses/i2c-lrw-master.c b/drivers/i2c/busses/i2c-lrw-master.c new file mode 100644 index 000000000000..ab87b033a1e6 --- /dev/null +++ b/drivers/i2c/busses/i2c-lrw-master.c @@ -0,0 +1,1075 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * I2C adapter driver for LRW + * + * Copyright (c) 2025, LRW CORPORATION. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "i2c-lrw-core.h" + +static char *abort_sources[] = { + [ABRT_7B_ADDR_NOACK] = "slave address not acknowledged (7bit mode)", + [ABRT_10ADDR1_NOACK] = + "first address byte not acknowledged (10bit mode)", + [ABRT_10ADDR2_NOACK] = + "second address byte not acknowledged (10bit mode)", + [ABRT_TXDATA_NOACK] = "data not acknowledged", + [ABRT_GCALL_NOACK] = "no acknowledgment for a general call", + [ABRT_GCALL_READ] = "read after general call", + [ABRT_SBYTE_ACKDET] = "start byte acknowledged", + [ABRT_SBYTE_NORSTRT] = + "trying to send start byte when restart is disabled", + [ABRT_10B_RD_NORSTRT] = + "trying to read when restart is disabled (10bit mode)", + [ABRT_MASTER_DIS] = "trying to use disabled adapter", + [ARB_LOST] = "lost arbitration", + [ABRT_SLAVE_FLUSH_TXFIFO] = + "read command so flush old data in the TX FIFO", + [ABRT_SLAVE_ARBLOST] = + "slave lost the bus while transmitting data to a remote master", + [ABRT_SLAVE_RD_INTX] = "incorrect slave-transmitter mode configuration", + [ABRT_SDA_STUCK_AT_LOW] = "SDA line stuck at low timeout", +}; + +static int lrw_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct i2c_lrw_dev *dev = context; + + *val = readl_relaxed(dev->base + reg); + + return 0; +} + +static int lrw_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct i2c_lrw_dev *dev = context; + + writel_relaxed(val, dev->base + reg); + + return 0; +} + +static int i2c_lrw_acquire_lock(struct i2c_lrw_dev *dev) +{ + int ret; + + if (!dev->acquire_lock) + return 0; + + ret = dev->acquire_lock(); + if (!ret) + return 0; + + dev_err(dev->dev, "couldn't acquire bus ownership\n"); + + return ret; +} + +static void i2c_lrw_release_lock(struct i2c_lrw_dev *dev) +{ + if (dev->release_lock) + dev->release_lock(); +} + +static int i2c_lrw_init_regmap(struct i2c_lrw_dev *dev) +{ + struct regmap_config map_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .disable_locking = true, + .reg_read = lrw_reg_read, + .reg_write = lrw_reg_write, + .max_register = LRW_SMBUS_INTR_STAT, + }; + + if (dev->map) + return 0; + + dev->map = devm_regmap_init(dev->dev, NULL, dev, &map_cfg); + if (IS_ERR(dev->map)) { + dev_err(dev->dev, "Failed to init the registers map\n"); + return PTR_ERR(dev->map); + } + + return 0; +} + +static const u32 supported_speeds[] = { + I2C_MAX_HIGH_SPEED_MODE_FREQ, + I2C_MAX_FAST_MODE_PLUS_FREQ, + I2C_MAX_FAST_MODE_FREQ, + I2C_MAX_STANDARD_MODE_FREQ, +}; + +int i2c_lrw_validate_speed(struct i2c_lrw_dev *dev) +{ + struct i2c_timings *t = &dev->timings; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(supported_speeds); i++) { + if (t->bus_freq_hz == supported_speeds[i]) + return 0; + } + + dev_err(dev->dev, + "%d Hz is unsupported, only 100kHz, 400kHz, 1MHz and 3.4MHz are supported\n", + t->bus_freq_hz); + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(i2c_lrw_validate_speed); + +#ifdef CONFIG_ACPI + +#include + +static void i2c_lrw_acpi_params(struct device *device, char method[], u16 *hcnt, + u16 *lcnt, u32 *sda_hold, u32 *sda_stuck_timout) +{ + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER }; + acpi_handle handle = ACPI_HANDLE(device); + union acpi_object *obj; + + if (ACPI_FAILURE(acpi_evaluate_object(handle, method, NULL, &buf))) + return; + + obj = (union acpi_object *)buf.pointer; + if (obj->type == ACPI_TYPE_PACKAGE && obj->package.count == 4) { + const union acpi_object *objs = obj->package.elements; + + *hcnt = (u16)objs[0].integer.value; + *lcnt = (u16)objs[1].integer.value; + *sda_hold = (u32)objs[2].integer.value; + *sda_stuck_timout = (u32)objs[3].integer.value; + } + kfree(buf.pointer); +} + +int i2c_lrw_acpi_configure(struct device *device) +{ + struct i2c_lrw_dev *dev = dev_get_drvdata(device); + struct i2c_timings *t = &dev->timings; + u32 hs_ht = 0, fs_ht = 0; + u32 sda_stuck_at_low_timeout; + + i2c_lrw_acpi_params(device, "HSCN", &dev->hs_hcnt, &dev->hs_lcnt, + &hs_ht, &sda_stuck_at_low_timeout); + i2c_lrw_acpi_params(device, "FMCN", &dev->fs_hcnt, &dev->fs_lcnt, + &fs_ht, &sda_stuck_at_low_timeout); + dev->sda_stuck_at_low_timeout = sda_stuck_at_low_timeout; + + switch (t->bus_freq_hz) { + case I2C_MAX_STANDARD_MODE_FREQ: + case I2C_MAX_FAST_MODE_FREQ: + case I2C_MAX_FAST_MODE_PLUS_FREQ: + dev->sda_hold_time = fs_ht; + break; + case I2C_MAX_HIGH_SPEED_MODE_FREQ: + dev->sda_hold_time = hs_ht; + break; + default: + dev_err(dev->dev, + "%d Hz is unsupported, only 100kHz, 400kHz, 1MHz and 3.4MHz are supported\n", + t->bus_freq_hz); + break; + } + + return 0; +} +EXPORT_SYMBOL_GPL(i2c_lrw_acpi_configure); + +static u32 i2c_lrw_acpi_round_bus_speed(struct device *device) +{ + u32 acpi_speed; + int i; + + acpi_speed = i2c_acpi_find_bus_speed(device); + + for (i = 0; i < ARRAY_SIZE(supported_speeds); i++) { + if (acpi_speed >= supported_speeds[i]) + return supported_speeds[i]; + } + + return 0; +} + +#else /* CONFIG_ACPI */ + +static inline u32 i2c_lrw_acpi_round_bus_speed(struct device *device) +{ + return 0; +} + +int i2c_lrw_dt_configure(struct device *device) +{ + struct i2c_lrw_dev *dev = dev_get_drvdata(device); + struct device_node *node = device->of_node; + u32 raw_property; + + if (node) { + of_property_read_u32(node, "hs_hcnt", &raw_property); + dev->hs_hcnt = (u16)raw_property; + of_property_read_u32(node, "hs_lcnt", &raw_property); + dev->hs_lcnt = (u16)raw_property; + of_property_read_u32(node, "fs_hcnt", &raw_property); + dev->fs_hcnt = (u16)raw_property; + of_property_read_u32(node, "fs_lcnt", &raw_property); + dev->fs_lcnt = (u16)raw_property; + of_property_read_u32(node, "sda_hold_time", + &dev->sda_hold_time); + of_property_read_u32(node, "sda_stuck_at_low_timeout", + &dev->sda_stuck_at_low_timeout); + } + dev_dbg(dev->dev, "dt dev param = %x, %x, %x, %x, %x, %x\n", + dev->hs_hcnt, dev->hs_lcnt, dev->fs_hcnt, dev->fs_lcnt, + dev->sda_hold_time, dev->sda_stuck_at_low_timeout); + return 0; +} +EXPORT_SYMBOL_GPL(i2c_lrw_dt_configure); + +#endif /* CONFIG_ACPI */ + +void i2c_lrw_adjust_bus_speed(struct i2c_lrw_dev *dev) +{ + u32 acpi_speed = i2c_lrw_acpi_round_bus_speed(dev->dev); + struct i2c_timings *t = &dev->timings; + + if (acpi_speed && t->bus_freq_hz) + t->bus_freq_hz = min(t->bus_freq_hz, acpi_speed); + else if (acpi_speed || t->bus_freq_hz) + t->bus_freq_hz = max(t->bus_freq_hz, acpi_speed); + else + t->bus_freq_hz = I2C_MAX_FAST_MODE_FREQ; +} +EXPORT_SYMBOL_GPL(i2c_lrw_adjust_bus_speed); + +static u32 i2c_lrw_scl_hcnt(u32 ic_clk, u32 tSYMBOL, u32 tf, int cond, + int offset) +{ + if (cond) + return DIV_ROUND_CLOSEST_ULL((u64)ic_clk * tSYMBOL, 1000000) - + 5 + offset; + else + return DIV_ROUND_CLOSEST_ULL((u64)ic_clk * (tSYMBOL + tf), + 1000000) - 3 + offset; +} + +static u32 i2c_lrw_scl_lcnt(u32 ic_clk, u32 tLOW, u32 tf, int offset) +{ + return DIV_ROUND_CLOSEST_ULL((u64)ic_clk * (tLOW + tf), 1000000) - 1 + + offset; +} + +static int i2c_lrw_set_sda_hold(struct i2c_lrw_dev *dev) +{ + unsigned int reg; + int ret; + + ret = i2c_lrw_acquire_lock(dev); + if (ret) + return ret; + + ret = regmap_read(dev->map, LRW_IC_VERSION, ®); + if (ret) + goto err_release_lock; + + if (reg >= LRW_IC_SDA_HOLD_MIN_VERS) { + if (!dev->sda_hold_time) { + ret = regmap_read(dev->map, LRW_IC_SDA_HOLD, + &dev->sda_hold_time); + if (ret) + goto err_release_lock; + } + + if (!(dev->sda_hold_time & LRW_IC_SDA_HOLD_RX_MASK)) + dev->sda_hold_time |= 1 << LRW_IC_SDA_HOLD_RX_SHIFT; + + dev_dbg(dev->dev, "SDA Hold Time TX:RX = %d:%d\n", + dev->sda_hold_time & ~(u32)LRW_IC_SDA_HOLD_RX_MASK, + dev->sda_hold_time >> LRW_IC_SDA_HOLD_RX_SHIFT); + } else if (dev->set_sda_hold_time) { + dev->set_sda_hold_time(dev); + } else if (dev->sda_hold_time) { + dev_warn(dev->dev, + "Hardware too old to adjust SDA hold time.\n"); + dev->sda_hold_time = 0; + } + +err_release_lock: + i2c_lrw_release_lock(dev); + + return ret; +} + +void __i2c_lrw_disable(struct i2c_lrw_dev *dev) +{ + unsigned int raw_intr_stats; + unsigned int enable; + int timeout = 100; + bool abort_needed; + unsigned int status; + int ret; + + regmap_read(dev->map, LRW_IC_CLR_RAW_INTR, &raw_intr_stats); + regmap_read(dev->map, LRW_IC_ENABLE, &enable); + + abort_needed = raw_intr_stats & LRW_IC_INTR_MST_ON_HOLD; + if (abort_needed) { + regmap_write(dev->map, LRW_IC_ENABLE, + enable | LRW_IC_ENABLE_ABORT); + ret = regmap_read_poll_timeout(dev->map, LRW_IC_ENABLE, enable, + !(enable & LRW_IC_ENABLE_ABORT), + 50, 200); + if (ret) + dev_err(dev->dev, + "timeout while trying to abort current transfer\n"); + } + + do { + __i2c_lrw_disable_nowait(dev); + regmap_read(dev->map, LRW_IC_STATUS, &status); + if ((status & 1) == 0) + return; + + usleep_range(25, 250); + } while (timeout--); + + dev_warn(dev->dev, "timeout in disabling adapter\n"); +} + +int i2c_lrw_bus_recover(struct i2c_adapter *adap) +{ + struct i2c_lrw_dev *dev = i2c_get_adapdata(adap); + int timeout = 1000; + u32 enabled; + + if (dev->abort_source & IC_ABRT_SDA_STUCK_AT_LOW_POS) { + regmap_update_bits(dev->map, LRW_IC_ENABLE, + IC_SDA_STUCK_RECOVERY_ENABLE_POS, + IC_SDA_STUCK_RECOVERY_ENABLE_POS); + do { + regmap_read(dev->map, LRW_IC_ENABLE, &enabled); + if ((enabled & IC_SDA_STUCK_RECOVERY_ENABLE_POS) == + IC_SDA_STUCK_AT_LOW_RECOVERIED) + break; + udelay(10); + } while (--timeout); + + if (!timeout) { + dev_err(dev->dev, "I2C bus recovery timeout\n"); + return -ETIMEDOUT; + } + + dev_err(dev->dev, "I2C hardware recovery complete\n"); + } + return 0; +} +EXPORT_SYMBOL_GPL(i2c_lrw_bus_recover); + +static u32 i2c_lrw_clk_rate(struct i2c_lrw_dev *dev) +{ + if (WARN_ON_ONCE(!dev->get_clk_rate_khz)) + return 0; + return dev->get_clk_rate_khz(dev); +} + +int i2c_lrw_prepare_clk(struct i2c_lrw_dev *dev, bool prepare) +{ + int ret; + + if (prepare) { + ret = clk_prepare_enable(dev->pclk); + if (ret) + return ret; + + ret = clk_prepare_enable(dev->clk); + if (ret) + clk_disable_unprepare(dev->pclk); + + return ret; + } + + clk_disable_unprepare(dev->clk); + clk_disable_unprepare(dev->pclk); + + return 0; +} +EXPORT_SYMBOL_GPL(i2c_lrw_prepare_clk); + +static int i2c_lrw_wait_bus_not_busy(struct i2c_lrw_dev *dev) +{ + u32 status; + int ret; + + ret = regmap_read_poll_timeout(dev->map, LRW_IC_STATUS, status, + !(status & LRW_IC_STATUS_ACTIVITY), 1100, + 20000); + if (ret) { + dev_warn(dev->dev, "timeout waiting for bus ready\n"); + + i2c_recover_bus(&dev->adapter); + + regmap_read(dev->map, LRW_IC_STATUS, &status); + if (!(status & LRW_IC_STATUS_ACTIVITY)) + ret = 0; + } + + return ret; +} + +static int i2c_lrw_handle_tx_abort(struct i2c_lrw_dev *dev) +{ + unsigned long abort_source = dev->abort_source; + int i; + + if (abort_source & LRW_IC_TX_ABRT_NOACK) { + for_each_set_bit(i, &abort_source, ARRAY_SIZE(abort_sources)) + dev_dbg(dev->dev, "%s: %s\n", __func__, + abort_sources[i]); + return -EREMOTEIO; + } + + for_each_set_bit(i, &abort_source, ARRAY_SIZE(abort_sources)) + dev_err(dev->dev, "%s: %s\n", __func__, abort_sources[i]); + + if (abort_source & LRW_IC_TX_ARB_LOST) + return -EAGAIN; + else if (abort_source & LRW_IC_TX_ABRT_GCALL_READ) + return -EINVAL; + else if (abort_source & LRW_IC_TX_ABRT_SDA_STUCK_AT_LOW) + return i2c_lrw_bus_recover(&dev->adapter); + else + return -EIO; +} + +static int i2c_lrw_set_fifo_size(struct i2c_lrw_dev *dev) +{ + u32 tx_fifo_depth, rx_fifo_depth; + + tx_fifo_depth = I2C_FIFO_DEPTH; + rx_fifo_depth = I2C_FIFO_DEPTH; + if (!dev->tx_fifo_depth) { + dev->tx_fifo_depth = tx_fifo_depth; + dev->rx_fifo_depth = rx_fifo_depth; + } else if (tx_fifo_depth >= 2) { + dev->tx_fifo_depth = + min_t(u32, dev->tx_fifo_depth, tx_fifo_depth); + dev->rx_fifo_depth = + min_t(u32, dev->rx_fifo_depth, rx_fifo_depth); + } + + return 0; +} + +static u32 i2c_lrw_func(struct i2c_adapter *adap) +{ + struct i2c_lrw_dev *dev = i2c_get_adapdata(adap); + + return dev->functionality; +} + +static void i2c_lrw_disable(struct i2c_lrw_dev *dev) +{ + int ret; + + ret = i2c_lrw_acquire_lock(dev); + if (ret) + return; + + __i2c_lrw_disable(dev); + + regmap_write(dev->map, LRW_IC_INTR_MASK, 0); + regmap_write_bits(dev->map, LRW_IC_CLR_RAW_INTR, LRW_IC_CLR_INTR, + LRW_IC_CLR_INTR); + + i2c_lrw_release_lock(dev); +} + +static void i2c_lrw_configure_fifo_master(struct i2c_lrw_dev *dev) +{ + regmap_write(dev->map, LRW_IC_TX_TL, dev->tx_fifo_depth / 2); + regmap_write(dev->map, LRW_IC_RX_TL, 0); + + regmap_write(dev->map, LRW_IC_CON, dev->master_cfg); +} + +static int i2c_lrw_set_timings_master(struct i2c_lrw_dev *dev) +{ + u32 sda_falling_time, scl_falling_time; + struct i2c_timings *t = &dev->timings; + const char *fp_str = ""; + u32 ic_clk; + int ret; + + sda_falling_time = t->sda_fall_ns ?: 300; + scl_falling_time = t->scl_fall_ns ?: 300; + + if ((dev->master_cfg & LRW_IC_CON_SPEED_MASK) == + LRW_IC_CON_SPEED_HIGH) { + if (!dev->hs_hcnt || !dev->hs_lcnt) { + ic_clk = i2c_lrw_clk_rate(dev); + dev->hs_hcnt = i2c_lrw_scl_hcnt(ic_clk, 160, + sda_falling_time, 0, 0); + dev->hs_lcnt = i2c_lrw_scl_lcnt(ic_clk, 320, + scl_falling_time, 0); + } else { + dev_err(dev->dev, "High Speed not supported!\n"); + t->bus_freq_hz = I2C_MAX_FAST_MODE_FREQ; + dev->master_cfg &= ~LRW_IC_CON_SPEED_MASK; + dev->master_cfg |= LRW_IC_CON_SPEED_STD_FAST; + dev->hs_hcnt = 0; + dev->hs_lcnt = 0; + } + dev_dbg(dev->dev, "High Speed Mode HCNT:LCNT = %d:%d\n", + dev->hs_hcnt, dev->hs_lcnt); + } + + if (!dev->fs_hcnt || !dev->fs_lcnt) { + ic_clk = i2c_lrw_clk_rate(dev); + switch (t->bus_freq_hz) { + case I2C_MAX_STANDARD_MODE_FREQ: /* 100kHz */ + dev->fs_hcnt = i2c_lrw_scl_hcnt(ic_clk, 4000, + sda_falling_time, 0, 0); + dev->fs_lcnt = i2c_lrw_scl_lcnt(ic_clk, 4700, + scl_falling_time, 0); + break; + case I2C_MAX_FAST_MODE_FREQ: /* 400kHz */ + dev->fs_hcnt = i2c_lrw_scl_hcnt(ic_clk, 600, + sda_falling_time, 0, 0); + dev->fs_lcnt = i2c_lrw_scl_lcnt(ic_clk, 1300, + scl_falling_time, 0); + break; + + case I2C_MAX_FAST_MODE_PLUS_FREQ: /* 1MHz */ + dev->fs_hcnt = i2c_lrw_scl_hcnt(ic_clk, 260, + sda_falling_time, 0, 0); + dev->fs_lcnt = i2c_lrw_scl_lcnt(ic_clk, 500, + scl_falling_time, 0); + fp_str = " Plus"; + break; + default: + break; + } + dev_dbg(dev->dev, "Fast Mode%s HCNT:LCNT = %d:%d\n", fp_str, + dev->fs_hcnt, dev->fs_lcnt); + } + + ret = i2c_lrw_set_sda_hold(dev); + if (ret) + return ret; + + dev_dbg(dev->dev, "Bus speed: %s\n", + i2c_freq_mode_string(t->bus_freq_hz)); + return 0; +} + +static int i2c_lrw_init_master(struct i2c_lrw_dev *dev) +{ + int ret; + + ret = i2c_lrw_acquire_lock(dev); + if (ret) + return ret; + + __i2c_lrw_disable(dev); + + regmap_write(dev->map, LRW_IC_FS_SCL_HCNT, dev->fs_hcnt); + regmap_write(dev->map, LRW_IC_FS_SCL_LCNT, dev->fs_lcnt); + regmap_write(dev->map, LRW_IC_SDA_STUCK_AT_LOW_TIMEOUT, + dev->sda_stuck_at_low_timeout); + if (dev->hs_hcnt && dev->hs_lcnt) { + regmap_write(dev->map, LRW_IC_HS_SCL_HCNT, dev->hs_hcnt); + regmap_write(dev->map, LRW_IC_HS_SCL_LCNT, dev->hs_lcnt); + } + + if (dev->sda_hold_time) + regmap_write(dev->map, LRW_IC_SDA_HOLD, dev->sda_hold_time); + + i2c_lrw_configure_fifo_master(dev); + i2c_lrw_release_lock(dev); + + return 0; +} + +static void i2c_lrw_xfer_init(struct i2c_lrw_dev *dev) +{ + struct i2c_msg *msgs = dev->msgs; + u32 ic_con = 0, ic_tar = 0; + + __i2c_lrw_disable(dev); + + if (msgs[dev->msg_write_idx].flags & I2C_M_TEN) { + ic_con = LRW_IC_CON_10BITADDR_MASTER; + ic_tar = LRW_IC_TAR_10BITADDR_MASTER; + } + + regmap_update_bits(dev->map, LRW_IC_CON, LRW_IC_CON_10BITADDR_MASTER, + ic_con); + regmap_write(dev->map, LRW_IC_TAR, + msgs[dev->msg_write_idx].addr | ic_tar); + + regmap_write(dev->map, LRW_IC_INTR_MASK, 0); + + __i2c_lrw_enable(dev); + + regmap_update_bits(dev->map, LRW_IC_CLR_RAW_INTR, LRW_IC_CLR_INTR, + LRW_IC_CLR_INTR); + regmap_write(dev->map, LRW_IC_INTR_MASK, LRW_IC_INTR_MASTER_MASK); +} + +static void i2c_lrw_xfer_msg(struct i2c_lrw_dev *dev) +{ + struct i2c_msg *msgs = dev->msgs; + u32 intr_mask; + u32 fifo_level; + int tx_limit, rx_limit; + u32 addr = msgs[dev->msg_write_idx].addr; + u32 buf_len = dev->tx_buf_len; + u8 *buf = dev->tx_buf; + bool need_restart = false; + unsigned int flr; + + intr_mask = LRW_IC_INTR_MASTER_MASK; + + for (; dev->msg_write_idx < dev->msgs_num; dev->msg_write_idx++) { + u32 flags = msgs[dev->msg_write_idx].flags; + + if (msgs[dev->msg_write_idx].addr != addr) { + dev_err(dev->dev, "%s: invalid target address\n", + __func__); + dev->msg_err = -EINVAL; + break; + } + + if (!(dev->status & STATUS_WRITE_IN_PROGRESS)) { + buf = msgs[dev->msg_write_idx].buf; + buf_len = msgs[dev->msg_write_idx].len; + + if ((dev->master_cfg & LRW_IC_CON_RESTART_EN) && + (dev->msg_write_idx > 0)) + need_restart = true; + } + + regmap_read(dev->map, LRW_IC_FIFO_LEVEL, &fifo_level); + flr = fifo_level & I2C_TXFLR_MASK; + tx_limit = dev->tx_fifo_depth - flr; + + flr = (fifo_level >> I2C_RXFLR_SHIFT) & I2C_RXFLR_MASK; + rx_limit = dev->rx_fifo_depth - flr; + + while (buf_len > 0 && tx_limit > 0 && rx_limit > 0) { + u32 cmd = 0; + + if (dev->msg_write_idx == dev->msgs_num - 1 && + buf_len == 1 && !(flags & I2C_M_RECV_LEN)) + cmd |= BIT(9); + + if (need_restart) { + cmd |= BIT(10); + need_restart = false; + } + + if (msgs[dev->msg_write_idx].flags & I2C_M_RD) { + if (dev->rx_outstanding >= dev->rx_fifo_depth) + break; + + regmap_write(dev->map, LRW_IC_DATA_CMD, + cmd | 0x100); + rx_limit--; + dev->rx_outstanding++; + } else { + regmap_write(dev->map, LRW_IC_DATA_CMD, + cmd | *buf++); + } + tx_limit--; + buf_len--; + } + + dev->tx_buf = buf; + dev->tx_buf_len = buf_len; + + if (flags & I2C_M_RECV_LEN) { + dev->status |= STATUS_WRITE_IN_PROGRESS; + intr_mask &= ~LRW_IC_INTR_TX_EMPTY; + break; + } + + if (buf_len > 0) { + dev->status |= STATUS_WRITE_IN_PROGRESS; + break; + } + + dev->status &= ~STATUS_WRITE_IN_PROGRESS; + } + + if (dev->msg_write_idx == dev->msgs_num) + intr_mask &= ~LRW_IC_INTR_TX_EMPTY; + + if (dev->msg_err) + intr_mask = 0; + + regmap_write(dev->map, LRW_IC_INTR_MASK, intr_mask); +} + +static u8 i2c_lrw_recv_len(struct i2c_lrw_dev *dev, u8 len) +{ + struct i2c_msg *msgs = dev->msgs; + u32 flags = msgs[dev->msg_read_idx].flags; + + len += (flags & I2C_CLIENT_PEC) ? 2 : 1; + dev->tx_buf_len = len - min_t(u8, len, dev->rx_outstanding); + msgs[dev->msg_read_idx].len = len; + msgs[dev->msg_read_idx].flags &= ~I2C_M_RECV_LEN; + + regmap_update_bits(dev->map, LRW_IC_INTR_MASK, LRW_IC_INTR_TX_EMPTY, + LRW_IC_INTR_TX_EMPTY); + + return len; +} + +static void i2c_lrw_read(struct i2c_lrw_dev *dev) +{ + struct i2c_msg *msgs = dev->msgs; + u32 fifo_level; + unsigned int rx_valid; + + for (; dev->msg_read_idx < dev->msgs_num; dev->msg_read_idx++) { + unsigned int tmp; + u32 len; + u8 *buf; + + if (!(msgs[dev->msg_read_idx].flags & I2C_M_RD)) + continue; + + if (!(dev->status & STATUS_READ_IN_PROGRESS)) { + len = msgs[dev->msg_read_idx].len; + buf = msgs[dev->msg_read_idx].buf; + } else { + len = dev->rx_buf_len; + buf = dev->rx_buf; + } + + regmap_read(dev->map, LRW_IC_FIFO_LEVEL, &fifo_level); + rx_valid = (fifo_level >> I2C_RXFLR_SHIFT) & I2C_RXFLR_MASK; + + for (; len > 0 && rx_valid > 0; len--, rx_valid--) { + u32 flags = msgs[dev->msg_read_idx].flags; + + regmap_read(dev->map, LRW_IC_DATA_CMD, &tmp); + tmp &= LRW_IC_DATA_CMD_DAT; + if (flags & I2C_M_RECV_LEN) { + if (!tmp || tmp > I2C_SMBUS_BLOCK_MAX) + tmp = 1; + + len = i2c_lrw_recv_len(dev, tmp); + } + *buf++ = tmp; + dev->rx_outstanding--; + } + + if (len > 0) { + dev->status |= STATUS_READ_IN_PROGRESS; + dev->rx_buf_len = len; + dev->rx_buf = buf; + return; + } + dev->status &= ~STATUS_READ_IN_PROGRESS; + } +} + +static int i2c_lrw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], + int num) +{ + struct i2c_lrw_dev *dev = i2c_get_adapdata(adap); + int ret; + + reinit_completion(&dev->cmd_complete); + dev->msgs = msgs; + dev->msgs_num = num; + dev->cmd_err = 0; + dev->msg_write_idx = 0; + dev->msg_read_idx = 0; + dev->msg_err = 0; + dev->status = 0; + dev->abort_source = 0; + dev->rx_outstanding = 0; + ret = i2c_lrw_acquire_lock(dev); + if (ret) + goto done_nolock; + ret = i2c_lrw_wait_bus_not_busy(dev); + if (ret < 0) + goto done; + i2c_lrw_xfer_init(dev); + + if (!wait_for_completion_timeout(&dev->cmd_complete, adap->timeout)) { + dev_err(dev->dev, "controller timed out\n"); + i2c_lrw_init_master(dev); + ret = -ETIMEDOUT; + goto done; + } + + dev->status &= ~STATUS_ACTIVE; + + if (dev->msg_err) { + ret = dev->msg_err; + goto disable_controller; + } + + if (likely(!dev->cmd_err && !dev->status)) { + ret = num; + goto disable_controller; + } + + if (dev->cmd_err == LRW_IC_ERR_TX_ABRT) { + ret = i2c_lrw_handle_tx_abort(dev); + goto disable_controller; + } + + if (dev->status) { + dev_err(dev->dev, + "transfer terminated early - interrupt latency too high?\n"); + ret = -EIO; + goto disable_controller; + } + + ret = -EIO; + +disable_controller: + __i2c_lrw_disable_nowait(dev); +done: + i2c_lrw_release_lock(dev); + +done_nolock: + return ret; +} + +static const struct i2c_algorithm i2c_lrw_algo = { + .master_xfer = i2c_lrw_xfer, + .functionality = i2c_lrw_func, +}; + +static const struct i2c_adapter_quirks i2c_lrw_quirks = { + .flags = I2C_AQ_NO_ZERO_LEN, +}; + +static u32 i2c_lrw_read_clear_intrbits(struct i2c_lrw_dev *dev) +{ + u32 stat; + + regmap_read(dev->map, LRW_IC_INTR_STAT, &stat); + + if (stat & LRW_IC_INTR_RX_OVER) + regmap_write_bits(dev->map, LRW_IC_CLR_RAW_INTR, + LRW_IC_INTR_RX_OVER, LRW_IC_INTR_RX_OVER); + if (stat & LRW_IC_INTR_RD_REQ) + regmap_write_bits(dev->map, LRW_IC_CLR_RAW_INTR, + LRW_IC_INTR_RD_REQ, LRW_IC_INTR_RD_REQ); + if (stat & LRW_IC_INTR_TX_ABRT) { + regmap_read(dev->map, LRW_IC_TX_ABRT_SOURCE, + &dev->abort_source); + regmap_write_bits(dev->map, LRW_IC_CLR_RAW_INTR, + LRW_IC_INTR_TX_ABRT, LRW_IC_INTR_TX_ABRT); + } + if (stat & LRW_IC_INTR_RX_DONE) + regmap_write_bits(dev->map, LRW_IC_CLR_RAW_INTR, + LRW_IC_INTR_RX_DONE, LRW_IC_INTR_RX_DONE); + if (stat & LRW_IC_INTR_ACTIVITY) + regmap_write_bits(dev->map, LRW_IC_CLR_RAW_INTR, + LRW_IC_INTR_ACTIVITY, LRW_IC_INTR_ACTIVITY); + if (stat & LRW_IC_INTR_STOP_DET) + regmap_write_bits(dev->map, LRW_IC_CLR_RAW_INTR, + LRW_IC_INTR_STOP_DET, LRW_IC_INTR_STOP_DET); + if (stat & LRW_IC_INTR_START_DET) + regmap_write_bits(dev->map, LRW_IC_CLR_RAW_INTR, + LRW_IC_INTR_START_DET, + LRW_IC_INTR_START_DET); + if (stat & LRW_IC_INTR_GEN_CALL) + regmap_write_bits(dev->map, LRW_IC_CLR_RAW_INTR, + LRW_IC_INTR_GEN_CALL, LRW_IC_INTR_GEN_CALL); + + return stat; +} + +static irqreturn_t i2c_lrw_isr(int this_irq, void *dev_id) +{ + struct i2c_lrw_dev *dev = dev_id; + u32 stat, enabled; + + regmap_read(dev->map, LRW_IC_ENABLE, &enabled); + regmap_read(dev->map, LRW_IC_CLR_RAW_INTR, &stat); + if (!enabled || !(stat & ~LRW_IC_INTR_ACTIVITY)) + return IRQ_NONE; + + stat = i2c_lrw_read_clear_intrbits(dev); + + if (!(dev->status & STATUS_ACTIVE)) { + regmap_write(dev->map, LRW_IC_INTR_MASK, 0); + return IRQ_HANDLED; + } + + if (stat & LRW_IC_INTR_TX_ABRT) { + dev->cmd_err |= LRW_IC_ERR_TX_ABRT; + dev->status &= ~STATUS_MASK; + dev->rx_outstanding = 0; + + dev_dbg(dev->dev, "abrt intr:0x%x, abort:0x%x\n", stat, + dev->abort_source); + regmap_write(dev->map, LRW_IC_INTR_MASK, 0); + goto tx_aborted; + } + + if (stat & LRW_IC_INTR_RX_FULL) + i2c_lrw_read(dev); + + if (stat & LRW_IC_INTR_TX_EMPTY) + i2c_lrw_xfer_msg(dev); + +tx_aborted: + if (((stat & (LRW_IC_INTR_TX_ABRT | LRW_IC_INTR_STOP_DET)) || + dev->msg_err) && + (dev->rx_outstanding == 0)) { + complete(&dev->cmd_complete); + } else { + regmap_read(dev->map, LRW_IC_INTR_MASK, &stat); + regmap_write(dev->map, LRW_IC_INTR_MASK, 0); + regmap_write(dev->map, LRW_IC_INTR_MASK, stat); + } + + return IRQ_HANDLED; +} + +void i2c_lrw_configure_master(struct i2c_lrw_dev *dev) +{ + struct i2c_timings *t = &dev->timings; + + dev->functionality = I2C_FUNC_10BIT_ADDR | LRW_IC_DEFAULT_FUNCTIONALITY; + + dev->master_cfg = LRW_IC_CON_MASTER | LRW_IC_CON_RESTART_EN | + IC_CON_BUS_CLEAR_CTRL_POS; + + switch (t->bus_freq_hz) { + case I2C_MAX_STANDARD_MODE_FREQ: + case I2C_MAX_FAST_MODE_FREQ: + case I2C_MAX_FAST_MODE_PLUS_FREQ: + dev->master_cfg |= LRW_IC_CON_SPEED_STD_FAST; + break; + case I2C_MAX_HIGH_SPEED_MODE_FREQ: + dev->master_cfg |= LRW_IC_CON_SPEED_HIGH; + break; + default: + dev_warn(dev->dev, + "dev bus_freq_hz outlined in the device datasheet?\n"); + } +} +EXPORT_SYMBOL_GPL(i2c_lrw_configure_master); + +static int i2c_lrw_init_recovery_info(struct i2c_lrw_dev *dev) +{ + struct i2c_bus_recovery_info *rinfo = &dev->rinfo; + struct i2c_adapter *adap = &dev->adapter; + + rinfo->recover_bus = i2c_lrw_bus_recover; + adap->bus_recovery_info = rinfo; + + return 0; +} + +int i2c_lrw_probe_master(struct i2c_lrw_dev *dev) +{ + struct i2c_adapter *adap = &dev->adapter; + unsigned long irq_flags; + unsigned int ic_con; + int ret; + + init_completion(&dev->cmd_complete); + + dev->init = i2c_lrw_init_master; + dev->disable = i2c_lrw_disable; + + ret = i2c_lrw_init_regmap(dev); + if (ret) + return ret; + + ret = i2c_lrw_set_timings_master(dev); + if (ret) + return ret; + + ret = i2c_lrw_set_fifo_size(dev); + if (ret) + return ret; + + ret = i2c_lrw_acquire_lock(dev); + if (ret) + return ret; + + ret = regmap_read(dev->map, LRW_IC_CON, &ic_con); + i2c_lrw_release_lock(dev); + if (ret) + return ret; + + if (ic_con & IC_CON_BUS_CLEAR_CTRL_POS) + dev->master_cfg |= IC_CON_BUS_CLEAR_CTRL_POS; + + ret = dev->init(dev); + if (ret) + return ret; + + snprintf(adap->name, sizeof(adap->name), "LRW I2C adapter"); + adap->retries = 3; + adap->algo = &i2c_lrw_algo; + adap->quirks = &i2c_lrw_quirks; + adap->dev.parent = dev->dev; + i2c_set_adapdata(adap, dev); + + irq_flags = IRQF_SHARED | IRQF_COND_SUSPEND; + + ret = i2c_lrw_acquire_lock(dev); + if (ret) + return ret; + + regmap_write(dev->map, LRW_IC_INTR_MASK, 0); + regmap_write(dev->map, LRW_SMBUS_INTR_MASK, 0); + + i2c_lrw_release_lock(dev); + + ret = devm_request_irq(dev->dev, dev->irq, i2c_lrw_isr, irq_flags, + dev_name(dev->dev), dev); + if (ret) { + dev_err(dev->dev, "failure requesting irq %i: %d\n", dev->irq, + ret); + return ret; + } + + ret = i2c_lrw_init_recovery_info(dev); + if (ret) + return ret; + + ret = i2c_add_numbered_adapter(adap); + if (ret) + dev_err(dev->dev, "failure adding adapter: %d\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(i2c_lrw_probe_master); + +MODULE_DESCRIPTION("LRW I2C bus master adapter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-lrw-platdrv.c b/drivers/i2c/busses/i2c-lrw-platdrv.c new file mode 100644 index 000000000000..65a8d69cbfb8 --- /dev/null +++ b/drivers/i2c/busses/i2c-lrw-platdrv.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * I2C adapter driver for LRW + * + * Copyright (c) 2025, LRW CORPORATION. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "i2c-lrw-core.h" + +static u32 i2c_lrw_get_clk_rate_khz(struct i2c_lrw_dev *dev) +{ + return clk_get_rate(dev->clk) / 1000; +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id i2c_lrw_acpi_match[] = { { "LRWX0002", 0 }, + {} }; +MODULE_DEVICE_TABLE(acpi, i2c_lrw_acpi_match); +#endif + +#ifdef CONFIG_OF + +static int i2c_lrw_of_configure(struct platform_device *pdev) +{ + struct i2c_lrw_dev *dev = platform_get_drvdata(pdev); + + switch (dev->flags & MODEL_MASK) { + default: + break; + } + + return 0; +} + +static const struct of_device_id i2c_lrw_of_match[] = { + { + .compatible = "lrw,sc-i2c", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, i2c_lrw_of_match); +#else + +static inline int i2c_lrw_of_configure(struct platform_device *pdev) +{ + return -ENODEV; +} +#endif + +static int i2c_lrw_plat_request_regs(struct i2c_lrw_dev *dev) +{ + struct platform_device *pdev = to_platform_device(dev->dev); + int ret; + + switch (dev->flags & MODEL_MASK) { + default: + dev->base = devm_platform_ioremap_resource(pdev, 0); + ret = PTR_ERR_OR_ZERO(dev->base); + break; + } + + return ret; +} + +static const struct dmi_system_id i2c_lrw_hwmon_class_dmi[] = { + { + .ident = "lrw 0002", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "lrw"), + DMI_MATCH(DMI_PRODUCT_NAME, "0002"), + }, + }, + { } /* terminate list */ +}; + +static int i2c_lrw_plat_probe(struct platform_device *pdev) +{ + struct i2c_adapter *adap; + struct i2c_lrw_dev *dev; + struct i2c_timings *t; + int irq, ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + dev = devm_kzalloc(&pdev->dev, sizeof(struct i2c_lrw_dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->flags = (uintptr_t)device_get_match_data(&pdev->dev); + dev->dev = &pdev->dev; + dev->irq = irq; + + platform_set_drvdata(pdev, dev); + + ret = i2c_lrw_plat_request_regs(dev); + if (ret) + return ret; + + dev->rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL); + if (IS_ERR(dev->rst)) + return PTR_ERR(dev->rst); + + reset_control_deassert(dev->rst); + + t = &dev->timings; + i2c_parse_fw_timings(&pdev->dev, t, false); + + i2c_lrw_adjust_bus_speed(dev); + + if (pdev->dev.of_node) + i2c_lrw_of_configure(pdev); + + if (has_acpi_companion(&pdev->dev)) + i2c_lrw_acpi_configure(&pdev->dev); + else + i2c_lrw_dt_configure(&pdev->dev); + + ret = i2c_lrw_validate_speed(dev); + if (ret) + goto exit_reset; + + ret = i2c_lrw_probe_lock_support(dev); + if (ret) + goto exit_reset; + + i2c_lrw_configure(dev); + + dev->pclk = devm_clk_get_optional(&pdev->dev, "pclk"); + if (IS_ERR(dev->pclk)) { + ret = PTR_ERR(dev->pclk); + goto exit_reset; + } + + dev->clk = devm_clk_get_optional(&pdev->dev, NULL); + if (IS_ERR(dev->clk)) { + ret = PTR_ERR(dev->clk); + goto exit_reset; + } + + ret = i2c_lrw_prepare_clk(dev, true); + if (ret) + goto exit_reset; + + if (dev->clk) { + u64 clk_khz; + + dev->get_clk_rate_khz = i2c_lrw_get_clk_rate_khz; + clk_khz = dev->get_clk_rate_khz(dev); + if (!dev->sda_hold_time && t->sda_hold_ns) { + dev->sda_hold_time = div_u64( + clk_khz * t->sda_hold_ns + 500000, 1000000); + } + } + + adap = &dev->adapter; + adap->owner = THIS_MODULE; + adap->class = dmi_check_system(i2c_lrw_hwmon_class_dmi) ? + I2C_CLASS_HWMON : + I2C_CLASS_DEPRECATED; + ACPI_COMPANION_SET(&adap->dev, ACPI_COMPANION(&pdev->dev)); + adap->dev.of_node = pdev->dev.of_node; + adap->nr = -1; + + ret = i2c_lrw_probe(dev); + if (ret) + goto exit_reset; + + return ret; + +exit_reset: + reset_control_assert(dev->rst); + return ret; +} + +static void i2c_lrw_plat_remove(struct platform_device *pdev) +{ + struct i2c_lrw_dev *dev = platform_get_drvdata(pdev); + + i2c_del_adapter(&dev->adapter); + + dev->disable(dev); + reset_control_assert(dev->rst); +} + +MODULE_ALIAS("platform:i2c_lrw"); + +static struct platform_driver i2c_lrw_driver = { + .probe = i2c_lrw_plat_probe, + .remove_new = i2c_lrw_plat_remove, + .driver = { + .name = "i2c_lrw", + .of_match_table = of_match_ptr(i2c_lrw_of_match), + .acpi_match_table = ACPI_PTR(i2c_lrw_acpi_match), + }, +}; + +static int __init i2c_lrw_init_driver(void) +{ + return platform_driver_register(&i2c_lrw_driver); +} +subsys_initcall(i2c_lrw_init_driver); + +static void __exit i2c_lrw_exit_driver(void) +{ + platform_driver_unregister(&i2c_lrw_driver); +} +module_exit(i2c_lrw_exit_driver); + +MODULE_AUTHOR("HXW"); +MODULE_DESCRIPTION("LRW I2C bus adapter"); +MODULE_LICENSE("GPL"); -- Gitee