[PATCH v9 2/3] i2c: ma35d1: Add Nuvoton MA35D1 I2C driver support
zychen
zychennvt at gmail.com
Mon Sep 7 23:33:26 PDT 2026
Hi Andi,
Gentle ping on this patch.
You reviewed the driver part in v7, and this part has been updated in v8/v9 based on the review feedback.
The v9 series was sent on Aug 7. Please let me know if you have any further comments on this version.
Thanks!
Zi-Yu Chen
Zi-Yu Chen 於 2026/8/7 下午 02:03 寫道:
> Add I2C support for Nuvoton MA35D1 SoC.
> The controller supports standard, fast and fast-plus modes,
> and provides controller/target functionality.
>
> Signed-off-by: Zi-Yu Chen <zychennvt at gmail.com>
> ---
> drivers/i2c/busses/Kconfig | 13 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-ma35d1.c | 779 ++++++++++++++++++++++++++++++++
> 3 files changed, 793 insertions(+)
> create mode 100644 drivers/i2c/busses/i2c-ma35d1.c
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index d7b89508311f..e69120d46977 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -1602,4 +1602,17 @@ config I2C_VIRTIO
> This driver can also be built as a module. If so, the module
> will be called i2c-virtio.
>
> +config I2C_MA35D1
> + tristate "Nuvoton MA35D1 I2C driver"
> + depends on ARCH_MA35 || COMPILE_TEST
> + select I2C_SLAVE
> + help
> + If you say yes to this option, support will be included for the
> + I2C controller in the Nuvoton MA35D1 SoC. This driver
> + supports the standard I2C bus protocols, including master and
> + slave modes.
> +
> + This driver can also be built as a module. If so, the module
> + will be called i2c-ma35d1.
> +
> endmenu
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 3755c54b3d82..ca75dae4955c 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -131,6 +131,7 @@ obj-$(CONFIG_I2C_XILINX) += i2c-xiic.o
> obj-$(CONFIG_I2C_XLP9XX) += i2c-xlp9xx.o
> obj-$(CONFIG_I2C_RCAR) += i2c-rcar.o
> obj-$(CONFIG_I2C_GXP) += i2c-gxp.o
> +obj-$(CONFIG_I2C_MA35D1) += i2c-ma35d1.o
>
> # External I2C/SMBus adapter drivers
> obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o
> diff --git a/drivers/i2c/busses/i2c-ma35d1.c b/drivers/i2c/busses/i2c-ma35d1.c
> new file mode 100644
> index 000000000000..61d7605c3a05
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-ma35d1.c
> @@ -0,0 +1,779 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2026 Nuvoton technology corporation.
> + *
> + * Author: Zi-Yu Chen <zychennvt at gmail.com>
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/i2c.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/property.h>
> +#include <linux/reset.h>
> +#include <linux/units.h>
> +
> +/* MA35D1 I2C registers offset */
> +#define MA35_CTL0 0x00
> +#define MA35_ADDR0 0x04
> +#define MA35_DAT 0x08
> +#define MA35_STATUS0 0x0c
> +#define MA35_CLKDIV 0x10
> +#define MA35_TOCTL 0x14
> +#define MA35_ADDR1 0x18
> +#define MA35_ADDR2 0x1c
> +#define MA35_ADDR3 0x20
> +#define MA35_ADDRMSK0 0x24
> +#define MA35_ADDRMSK1 0x28
> +#define MA35_ADDRMSK2 0x2c
> +#define MA35_ADDRMSK3 0x30
> +#define MA35_WKCTL 0x3c
> +#define MA35_WKSTS 0x40
> +#define MA35_CTL1 0x44
> +#define MA35_STATUS1 0x48
> +#define MA35_TMCTL 0x4c
> +#define MA35_BUSCTL 0x50
> +#define MA35_BUSTCTL 0x54
> +#define MA35_BUSSTS 0x58
> +#define MA35_PKTSIZE 0x5c
> +#define MA35_PKTCRC 0x60
> +#define MA35_BUSTOUT 0x64
> +#define MA35_CLKTOUT 0x68
> +#define MA35_AUTOCNT 0x78
> +
> +/* MA35D1 I2C Status */
> +/* Controller */
> +#define MA35_M_START 0x08 /* Start */
> +#define MA35_M_REPEAT_START 0x10 /* Controller Repeat Start */
> +#define MA35_M_TRAN_ADDR_ACK 0x18 /* Controller Transmit Address ACK */
> +#define MA35_M_TRAN_ADDR_NACK 0x20 /* Controller Transmit Address NACK */
> +#define MA35_M_TRAN_DATA_ACK 0x28 /* Controller Transmit Data ACK */
> +#define MA35_M_TRAN_DATA_NACK 0x30 /* Controller Transmit Data NACK */
> +#define MA35_M_ARB_LOST 0x38 /* Controller Arbitration Lost */
> +#define MA35_M_RECE_ADDR_ACK 0x40 /* Controller Receive Address ACK */
> +#define MA35_M_RECE_ADDR_NACK 0x48 /* Controller Receive Address NACK */
> +#define MA35_M_RECE_DATA_ACK 0x50 /* Controller Receive Data ACK */
> +#define MA35_M_RECE_DATA_NACK 0x58 /* Controller Receive Data NACK */
> +#define MA35_BUS_ERROR 0x00 /* Bus error */
> +
> +/* Target */
> +#define MA35_S_REPEAT_START_STOP 0xa0 /* Target Transmit Repeat Start or Stop */
> +#define MA35_S_TRAN_ADDR_ACK 0xa8 /* Target Transmit Address ACK */
> +#define MA35_S_TRAN_DATA_ACK 0xb8 /* Target Transmit Data ACK */
> +#define MA35_S_TRAN_DATA_NACK 0xc0 /* Target Transmit Data NACK */
> +#define MA35_S_TRAN_LAST_DATA_ACK 0xc8 /* Target Transmit Last Data ACK */
> +#define MA35_S_RECE_ADDR_ACK 0x60 /* Target Receive Address ACK */
> +#define MA35_S_RECE_ARB_LOST 0x68 /* Target Receive Arbitration Lost */
> +#define MA35_S_RECE_DATA_ACK 0x80 /* Target Receive Data ACK */
> +#define MA35_S_RECE_DATA_NACK 0x88 /* Target Receive Data NACK */
> +
> +/* Other */
> +#define MA35_ADDR_TRAN_ARB_LOST 0xb0 /* Address Transmit Arbitration Lost */
> +#define MA35_BUS_RELEASED 0xf8 /* Bus Released */
> +
> +/* I2C_CTL constant definitions. */
> +#define MA35_CTL_AA BIT(2)
> +#define MA35_CTL_SI BIT(3)
> +#define MA35_CTL_STO BIT(4)
> +#define MA35_CTL_STA BIT(5)
> +#define MA35_CTL_I2CEN BIT(6)
> +#define MA35_CTL_INTEN BIT(7)
> +#define MA35_CTL_SI_AA (MA35_CTL_SI | MA35_CTL_AA)
> +#define MA35_CTL_STO_SI (MA35_CTL_STO | MA35_CTL_SI)
> +#define MA35_CTL_STA_SI (MA35_CTL_STA | MA35_CTL_SI)
> +
> +/* Constants */
> +#define MA35_CLKDIV_MSK GENMASK(15, 0)
> +#define I2C_PM_TIMEOUT_MS 5000
> +#define STOP_TIMEOUT_MS 50
> +
> +struct ma35d1_i2c {
> + spinlock_t lock;
> + wait_queue_head_t wait;
> + struct i2c_msg *msg;
> + unsigned int msg_num;
> + unsigned int msg_idx;
> + unsigned int msg_ptr;
> + int err;
> + int irq;
> + void __iomem *regs;
> + struct clk *clk;
> + struct device *dev;
> + struct i2c_adapter adap;
> + struct i2c_client *target;
> + struct reset_control *rst;
> +};
> +
> +static inline bool ma35d1_is_controller_status(unsigned int status)
> +{
> + return status >= MA35_M_START && status <= MA35_M_RECE_DATA_NACK;
> +}
> +
> +/*
> + * ma35d1_i2c_write_ctl - Update the I2C control register
> + * @i2c: Pointer to the ma35d1 i2c instance
> + * @ctl: Control bits to set (e.g., MA35_CTL_STA, SI, AA)
> + *
> + * Read-modify-write the CTL0 register while preserving unrelated bits.
> + */
> +static void ma35d1_i2c_write_ctl(struct ma35d1_i2c *i2c, unsigned int ctl)
> +{
> + unsigned int val;
> +
> + guard(spinlock_irqsave)(&i2c->lock);
> + val = readl(i2c->regs + MA35_CTL0);
> + val &= ~(MA35_CTL_SI_AA);
> + val |= ctl;
> + writel(val, i2c->regs + MA35_CTL0);
> +}
> +
> +static void ma35d1_i2c_set_addr(struct ma35d1_i2c *i2c)
> +{
> + unsigned int rw = i2c->msg->flags & I2C_M_RD;
> +
> + writel(((i2c->msg->addr & 0x7f) << 1) | rw, i2c->regs + MA35_DAT);
> +}
> +
> +static void ma35d1_i2c_controller_complete(struct ma35d1_i2c *i2c)
> +{
> + i2c->msg_ptr = 0;
> + i2c->msg = NULL;
> + i2c->msg_idx++;
> + i2c->msg_num = 0;
> +
> + wake_up(&i2c->wait);
> +}
> +
> +static void ma35d1_i2c_reset(struct ma35d1_i2c *i2c)
> +{
> + unsigned int clkdiv, slvaddr;
> +
> + clkdiv = readl(i2c->regs + MA35_CLKDIV);
> + slvaddr = readl(i2c->regs + MA35_ADDR0);
> +
> + disable_irq(i2c->irq);
> + i2c->msg = NULL;
> + reset_control_assert(i2c->rst);
> + usleep_range(10, 20);
> + reset_control_deassert(i2c->rst);
> + enable_irq(i2c->irq);
> +
> + writel(clkdiv, (i2c->regs + MA35_CLKDIV));
> + ma35d1_i2c_write_ctl(i2c, MA35_CTL_I2CEN | MA35_CTL_INTEN);
> +
> + if (i2c->target)
> + writel(slvaddr, i2c->regs + MA35_ADDR0);
> +}
> +
> +static void ma35d1_i2c_stop(struct ma35d1_i2c *i2c, int ret)
> +{
> + ma35d1_i2c_write_ctl(i2c, MA35_CTL_STO_SI);
> +
> + if (ret)
> + i2c->err = ret;
> +
> + ma35d1_i2c_controller_complete(i2c);
> +}
> +
> +/*
> + * ma35d1_i2c_restore_idle - restore controller state to idle
> + * @i2c: Pointer to the ma35d1 i2c instance
> + *
> + * Restore the controller to an idle state after an unexpected event.
> + * Abort an active controller transfer, otherwise re-arm target mode
> + * if enabled.
> + */
> +static void ma35d1_i2c_restore_idle(struct ma35d1_i2c *i2c)
> +{
> + if (i2c->msg)
> + ma35d1_i2c_stop(i2c, -EIO);
> + else if (i2c->target)
> + ma35d1_i2c_write_ctl(i2c, MA35_CTL_SI_AA);
> + else
> + ma35d1_i2c_write_ctl(i2c, MA35_CTL_SI);
> +}
> +
> +/* Check if this is the last message in the set */
> +static inline bool is_last_msg(struct ma35d1_i2c *i2c)
> +{
> + return i2c->msg_idx >= (i2c->msg_num - 1);
> +}
> +
> +/* Check if this is the last byte in the current message */
> +static inline bool is_last_byte(struct ma35d1_i2c *i2c)
> +{
> + return i2c->msg_ptr == i2c->msg->len - 1;
> +}
> +
> +/* Check if reached the end of the current message */
> +static inline bool is_msgend(struct ma35d1_i2c *i2c)
> +{
> + return i2c->msg_ptr >= i2c->msg->len;
> +}
> +
> +/*
> + * ma35d1_i2c_irq_target_trx - I2C Target state machine handler
> + * @i2c: ma35d1 i2c instance
> + * @i2c_status: hardware status code from MA35_STATUS0
> + */
> +static irqreturn_t ma35d1_i2c_irq_target_trx(struct ma35d1_i2c *i2c,
> + unsigned long i2c_status)
> +{
> + unsigned char byte = 0;
> +
> + switch (i2c_status) {
> + case MA35_S_RECE_ARB_LOST:
> + /*
> + * Arbitration lost during address transmission phase.
> + * The hardware switches to Target Transmitter mode when
> + * our own SLA+W is detected on the bus.
> + */
> + i2c->err = -EAGAIN;
> + ma35d1_i2c_controller_complete(i2c);
> + i2c_slave_event(i2c->target, I2C_SLAVE_WRITE_REQUESTED, &byte);
> + break;
> +
> + case MA35_S_RECE_ADDR_ACK:
> + /* Own SLA+W has been receive; ACK has been return */
> + i2c_slave_event(i2c->target, I2C_SLAVE_WRITE_REQUESTED, &byte);
> + break;
> +
> + case MA35_S_TRAN_DATA_NACK:
> + case MA35_S_RECE_DATA_NACK:
> + /*
> + * Data byte or last data in I2CDAT has been transmitted and NACK received,
> + * or previously addressed with own SLA address and NACK returned.
> + */
> + break;
> +
> + case MA35_S_RECE_DATA_ACK:
> + /*
> + * Previously address with own SLA address Data has been received;
> + * ACK has been returned
> + */
> + byte = readb(i2c->regs + MA35_DAT);
> + i2c_slave_event(i2c->target, I2C_SLAVE_WRITE_RECEIVED, &byte);
> + break;
> +
> + case MA35_ADDR_TRAN_ARB_LOST:
> + /*
> + * Arbitration lost during address transmission phase.
> + * The hardware switches to Target Receiver mode when
> + * our own SLA+R is detected on the bus.
> + */
> + i2c->err = -EAGAIN;
> + ma35d1_i2c_controller_complete(i2c);
> + i2c_slave_event(i2c->target, I2C_SLAVE_READ_REQUESTED, &byte);
> + writel(byte, i2c->regs + MA35_DAT);
> + break;
> +
> + case MA35_S_TRAN_ADDR_ACK:
> + /* Own SLA+R has been receive; ACK has been return */
> + i2c_slave_event(i2c->target, I2C_SLAVE_READ_REQUESTED, &byte);
> + writel(byte, i2c->regs + MA35_DAT);
> + break;
> +
> + case MA35_S_TRAN_DATA_ACK:
> + i2c_slave_event(i2c->target, I2C_SLAVE_READ_PROCESSED, &byte);
> + writel(byte, i2c->regs + MA35_DAT);
> + break;
> +
> + case MA35_S_REPEAT_START_STOP:
> + /*
> + * A STOP or repeated START has been received
> + * while still addressed as Target/Receiver
> + */
> + i2c_slave_event(i2c->target, I2C_SLAVE_STOP, &byte);
> + break;
> +
> + default:
> + dev_err(i2c->dev, "Status 0x%02lx is NOT processed\n",
> + i2c_status);
> + ma35d1_i2c_restore_idle(i2c);
> + return IRQ_NONE;
> + }
> + ma35d1_i2c_write_ctl(i2c, MA35_CTL_SI_AA);
> + return IRQ_HANDLED;
> +}
> +
> +/*
> + * ma35d1_i2c_irq_controller_trx - I2C Controller state machine handler
> + * @i2c: ma35d1 i2c instance
> + * @i2c_status: hardware status code from MA35_STATUS0
> + */
> +static irqreturn_t ma35d1_i2c_irq_controller_trx(struct ma35d1_i2c *i2c,
> + unsigned long i2c_status)
> +{
> + unsigned char byte;
> +
> + /* Ensuring messages are absolutely valid in Controller mode. */
> + if (unlikely(!i2c->msg)) {
> + dev_err(i2c->dev, "Spurious controller interrupt (status: 0x%02lx)\n", i2c_status);
> + ma35d1_i2c_restore_idle(i2c);
> + return IRQ_HANDLED;
> + }
> +
> + switch (i2c_status) {
> + case MA35_M_START:
> + case MA35_M_REPEAT_START:
> + ma35d1_i2c_set_addr(i2c);
> + ma35d1_i2c_write_ctl(i2c, MA35_CTL_SI);
> + break;
> +
> + case MA35_M_TRAN_ADDR_ACK:
> + case MA35_M_TRAN_DATA_ACK:
> + /* SLA+W has been transmitted and ACK has been received */
> + if (i2c_status == MA35_M_TRAN_ADDR_ACK) {
> + if (is_last_msg(i2c) && i2c->msg->len == 0) {
> + ma35d1_i2c_stop(i2c, 0);
> + return IRQ_HANDLED;
> + }
> + }
> +
> + if (!is_msgend(i2c)) {
> + byte = i2c->msg->buf[i2c->msg_ptr++];
> + writel(byte, i2c->regs + MA35_DAT);
> + ma35d1_i2c_write_ctl(i2c, MA35_CTL_SI);
> + } else if (!is_last_msg(i2c)) {
> + dev_dbg(i2c->dev, "WRITE: Next Message\n");
> +
> + i2c->msg_ptr = 0;
> + i2c->msg_idx++;
> + i2c->msg++;
> +
> + ma35d1_i2c_write_ctl(i2c, MA35_CTL_STA | MA35_CTL_SI);
> + } else {
> + ma35d1_i2c_stop(i2c, 0);
> + }
> + break;
> +
> + case MA35_M_TRAN_DATA_NACK:
> + ma35d1_i2c_stop(i2c, -EIO);
> + break;
> +
> + case MA35_M_TRAN_ADDR_NACK:
> + case MA35_M_RECE_ADDR_NACK:
> + /*
> + * Controller Transmit Address NACK
> + * 0x20: SLA+W has been transmitted and NACK has been received
> + * 0x48: SLA+R has been transmitted and NACK has been received
> + */
> + dev_dbg(i2c->dev, "\n i2c: ack was not received\n");
> + ma35d1_i2c_stop(i2c, -ENXIO);
> + break;
> +
> + case MA35_M_RECE_ADDR_ACK:
> + if (is_msgend(i2c)) {
> + if (is_last_msg(i2c)) {
> + ma35d1_i2c_stop(i2c, 0);
> + } else {
> + dev_dbg(i2c->dev, "READ: Next Transfer\n");
> +
> + i2c->msg_ptr = 0;
> + i2c->msg_idx++;
> + i2c->msg++;
> +
> + ma35d1_i2c_write_ctl(i2c, MA35_CTL_STA_SI);
> + }
> + } else if (i2c->msg->len == 1) {
> + ma35d1_i2c_write_ctl(i2c, MA35_CTL_SI);
> + } else {
> + ma35d1_i2c_write_ctl(i2c, MA35_CTL_SI_AA);
> + }
> + break;
> +
> + case MA35_M_RECE_DATA_ACK:
> + case MA35_M_RECE_DATA_NACK:
> + /* DATA has been received and ACK/NACK has been received */
> + byte = readb(i2c->regs + MA35_DAT);
> + i2c->msg->buf[i2c->msg_ptr++] = byte;
> +
> + if (is_last_byte(i2c)) {
> + ma35d1_i2c_write_ctl(i2c, MA35_CTL_SI);
> + } else if (is_msgend(i2c)) {
> + if (is_last_msg(i2c)) {
> + dev_dbg(i2c->dev, "READ: Send Stop\n");
> +
> + ma35d1_i2c_stop(i2c, 0);
> + } else {
> + dev_dbg(i2c->dev, "READ: Next Transfer\n");
> +
> + i2c->msg_ptr = 0;
> + i2c->msg_idx++;
> + i2c->msg++;
> +
> + ma35d1_i2c_write_ctl(i2c, MA35_CTL_STA_SI);
> + }
> + } else {
> + ma35d1_i2c_write_ctl(i2c, MA35_CTL_SI_AA);
> + }
> + break;
> +
> + case MA35_M_ARB_LOST:
> + /* Arbitration Lost */
> + i2c->err = -EAGAIN;
> + ma35d1_i2c_controller_complete(i2c);
> + ma35d1_i2c_write_ctl(i2c, MA35_CTL_SI);
> + break;
> +
> + default:
> + dev_err(i2c->dev, "Status 0x%02lx is NOT processed\n",
> + i2c_status);
> + ma35d1_i2c_restore_idle(i2c);
> + return IRQ_NONE;
> + }
> + return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t ma35d1_i2c_irq(int irqno, void *dev_id)
> +{
> + struct ma35d1_i2c *i2c = dev_id;
> + unsigned long status;
> +
> + status = readl(i2c->regs + MA35_STATUS0);
> +
> + if (status == MA35_BUS_ERROR) {
> + dev_err(i2c->dev, "bus error\n");
> + ma35d1_i2c_restore_idle(i2c);
> + return IRQ_HANDLED;
> + }
> +
> + if (ma35d1_is_controller_status(status))
> + return ma35d1_i2c_irq_controller_trx(i2c, status);
> + else
> + return ma35d1_i2c_irq_target_trx(i2c, status);
> +}
> +
> +static int ma35d1_i2c_doxfer(struct ma35d1_i2c *i2c, struct i2c_msg *msgs,
> + int num)
> +{
> + unsigned long timeout;
> + unsigned int val;
> + int ret, err;
> +
> + i2c->msg = msgs;
> + i2c->msg_num = num;
> + i2c->msg_ptr = 0;
> + i2c->msg_idx = 0;
> + i2c->err = 0;
> +
> + ma35d1_i2c_write_ctl(i2c, MA35_CTL_STA);
> +
> + timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);
> + ret = i2c->msg_idx;
> +
> + if (timeout == 0) {
> + dev_dbg(i2c->dev, "xfer timeout\n");
> + ret = -ETIMEDOUT;
> + goto reset;
> + }
> +
> + err = readl_poll_timeout(i2c->regs + MA35_CTL0, val,
> + !(val & MA35_CTL_STO), 100,
> + STOP_TIMEOUT_MS * 1000);
> + if (err) {
> + dev_err(i2c->dev, "bus idle timeout\n");
> + ret = -EBUSY;
> + goto reset;
> + } else if (i2c->err) {
> + dev_dbg(i2c->dev, "xfer error %d\n", i2c->err);
> + ret = i2c->err;
> + }
> + return ret;
> +
> +reset:
> + ma35d1_i2c_reset(i2c);
> + return ret;
> +}
> +
> +static int ma35d1_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
> + int num)
> +{
> + struct ma35d1_i2c *i2c = i2c_get_adapdata(adap);
> + int ret;
> +
> + ret = pm_runtime_resume_and_get(i2c->dev);
> + if (ret)
> + return ret;
> +
> + ret = ma35d1_i2c_doxfer(i2c, msgs, num);
> +
> + if (i2c->target)
> + ma35d1_i2c_write_ctl(i2c, MA35_CTL_AA);
> + pm_runtime_put_autosuspend(i2c->dev);
> +
> + return ret;
> +}
> +
> +static int ma35d1_reg_target(struct i2c_client *target)
> +{
> + struct ma35d1_i2c *i2c = i2c_get_adapdata(target->adapter);
> + unsigned int slvaddr;
> + int ret;
> +
> + if (i2c->target)
> + return -EBUSY;
> +
> + if (target->flags & I2C_CLIENT_TEN)
> + return -EAFNOSUPPORT;
> +
> + /* Keep device active for target mode listening. */
> + ret = pm_runtime_resume_and_get(i2c->dev);
> + if (ret) {
> + dev_err(i2c->dev, "failed to resume i2c controller\n");
> + return ret;
> + }
> +
> + i2c->target = target;
> +
> + slvaddr = target->addr << 1;
> + writel(slvaddr, i2c->regs + MA35_ADDR0);
> +
> + /* I2C enter SLV mode */
> + ma35d1_i2c_write_ctl(i2c, MA35_CTL_AA);
> +
> + return 0;
> +}
> +
> +static int ma35d1_unreg_target(struct i2c_client *target)
> +{
> + struct ma35d1_i2c *i2c = i2c_get_adapdata(target->adapter);
> + int ret;
> +
> + if (!i2c->target)
> + return -EINVAL;
> +
> + writel(0, i2c->regs + MA35_ADDR0);
> + /* Clear AA bit */
> + ma35d1_i2c_write_ctl(i2c, 0);
> +
> + synchronize_irq(i2c->irq);
> + i2c->target = NULL;
> +
> + ret = pm_runtime_put_sync(i2c->dev);
> + if (ret < 0)
> + dev_err(i2c->dev, "failed to suspend i2c controller");
> +
> + return 0;
> +}
> +
> +/* Declare Our I2C Functionality */
> +static u32 ma35d1_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
> +}
> +
> +/* I2C Bus Registration Info */
> +static const struct i2c_algorithm ma35d1_i2c_algorithm = {
> + .xfer = ma35d1_i2c_xfer,
> + .functionality = ma35d1_i2c_func,
> + .reg_target = ma35d1_reg_target,
> + .unreg_target = ma35d1_unreg_target,
> +};
> +
> +static const struct i2c_adapter_quirks ma35d1_i2c_quirks = {
> + .flags = I2C_AQ_NO_ZERO_LEN_READ,
> +};
> +
> +static int ma35d1_i2c_suspend(struct device *dev)
> +{
> + struct ma35d1_i2c *i2c = dev_get_drvdata(dev);
> + unsigned int val;
> +
> + /* Prepare for wake-up from I2C events if target mode is active */
> + if (i2c->target) {
> + ma35d1_i2c_write_ctl(i2c, MA35_CTL_AA);
> +
> + /* Setup wake-up control */
> + writel(0x1, i2c->regs + MA35_WKCTL);
> +
> + /* Clear pending wake-up flags */
> + val = readl(i2c->regs + MA35_WKSTS);
> + writel(val, i2c->regs + MA35_WKSTS);
> +
> + enable_irq_wake(i2c->irq);
> +
> + return 0;
> + }
> + return pm_runtime_force_suspend(dev);
> +}
> +
> +static int ma35d1_i2c_resume(struct device *dev)
> +{
> + struct ma35d1_i2c *i2c = dev_get_drvdata(dev);
> + unsigned int val;
> +
> + if (i2c->target) {
> + /* Disable wake-up */
> + writel(0x0, i2c->regs + MA35_WKCTL);
> +
> + /* Clear pending wake-up flags */
> + val = readl(i2c->regs + MA35_WKSTS);
> + writel(val, i2c->regs + MA35_WKSTS);
> +
> + disable_irq_wake(i2c->irq);
> + return 0;
> + }
> + return pm_runtime_force_resume(dev);
> +}
> +
> +static int ma35d1_i2c_runtime_suspend(struct device *dev)
> +{
> + struct ma35d1_i2c *i2c = dev_get_drvdata(dev);
> +
> + clk_disable(i2c->clk);
> +
> + return 0;
> +}
> +
> +static int ma35d1_i2c_runtime_resume(struct device *dev)
> +{
> + struct ma35d1_i2c *i2c = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = clk_enable(i2c->clk);
> + if (ret) {
> + dev_err(dev, "failed to enable clock in resume\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void ma35d1_i2c_pm_cleanup(void *data)
> +{
> + struct device *dev = data;
> +
> + pm_runtime_get_sync(dev);
> + pm_runtime_disable(dev);
> + pm_runtime_put_noidle(dev);
> +}
> +
> +static int ma35d1_i2c_probe(struct platform_device *pdev)
> +{
> + unsigned int busfreq = I2C_MAX_STANDARD_MODE_FREQ;
> + struct device *dev = &pdev->dev;
> + struct ma35d1_i2c *i2c;
> + struct resource *res;
> + int ret, clkdiv;
> + u32 val;
> +
> + i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL);
> + if (!i2c)
> + return -ENOMEM;
> +
> + init_waitqueue_head(&i2c->wait);
> + spin_lock_init(&i2c->lock);
> +
> + i2c->dev = dev;
> +
> + i2c->clk = devm_clk_get_enabled(dev, NULL);
> + if (IS_ERR(i2c->clk))
> + return dev_err_probe(dev, PTR_ERR(i2c->clk),
> + "failed to get core clk\n");
> +
> + i2c->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
> + if (IS_ERR(i2c->regs))
> + return PTR_ERR(i2c->regs);
> +
> + i2c->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL);
> + if (IS_ERR(i2c->rst))
> + return dev_err_probe(dev, PTR_ERR(i2c->rst),
> + "failed to get reset control\n");
> +
> + ret = reset_control_deassert(i2c->rst);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to deassert reset line\n");
> +
> + /* Setup info block for the I2C core */
> + strscpy(i2c->adap.name, "ma35d1-i2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &ma35d1_i2c_algorithm;
> + i2c->adap.quirks = &ma35d1_i2c_quirks;
> + i2c->adap.retries = 2;
> + i2c->adap.algo_data = i2c;
> + i2c->adap.dev.parent = &pdev->dev;
> + i2c->adap.dev.of_node = pdev->dev.of_node;
> + i2c_set_adapdata(&i2c->adap, i2c);
> +
> + if (!device_property_read_u32(dev, "clock-frequency", &val)) {
> + if (val != 0 && val <= MEGA)
> + busfreq = val;
> + }
> + /* Calculate divider based on the current peripheral clock rate */
> + clkdiv = DIV_ROUND_CLOSEST(clk_get_rate(i2c->clk), busfreq * 4) - 1;
> + if (clkdiv < 0 || clkdiv > 0xffff)
> + return dev_err_probe(dev, -EINVAL, "invalid clkdiv value: %d\n",
> + clkdiv);
> +
> + i2c->irq = platform_get_irq(pdev, 0);
> + if (i2c->irq < 0)
> + return dev_err_probe(dev, i2c->irq, "failed to get irq\n");
> +
> + platform_set_drvdata(pdev, i2c);
> +
> + pm_runtime_set_autosuspend_delay(dev, I2C_PM_TIMEOUT_MS);
> + pm_runtime_use_autosuspend(dev);
> + pm_runtime_set_active(dev);
> + pm_runtime_enable(dev);
> +
> + ret = devm_add_action_or_reset(dev, ma35d1_i2c_pm_cleanup, dev);
> + if (ret)
> + return ret;
> +
> + writel(MA35_CTL_I2CEN | MA35_CTL_INTEN, i2c->regs + MA35_CTL0);
> + writel(FIELD_PREP(MA35_CLKDIV_MSK, clkdiv), i2c->regs + MA35_CLKDIV);
> +
> + ret = devm_request_irq(dev, i2c->irq, ma35d1_i2c_irq, 0, dev_name(dev),
> + i2c);
> + if (ret) {
> + dev_err_probe(dev, ret, "cannot claim IRQ %d\n", i2c->irq);
> + return ret;
> + }
> +
> + ret = devm_i2c_add_adapter(dev, &i2c->adap);
> + if (ret) {
> + dev_err_probe(dev, ret, "failed to add bus to i2c core\n");
> + return ret;
> + }
> +
> + dev_info(&i2c->adap.dev, "%pa MA35D1 I2C adapter registered\n",
> + &res->start);
> + return 0;
> +}
> +
> +static const struct dev_pm_ops ma35d1_i2c_pmops = {
> + SYSTEM_SLEEP_PM_OPS(ma35d1_i2c_suspend, ma35d1_i2c_resume)
> + RUNTIME_PM_OPS(ma35d1_i2c_runtime_suspend,
> + ma35d1_i2c_runtime_resume, NULL)
> +};
> +
> +static const struct of_device_id ma35d1_i2c_of_match[] = {
> + { .compatible = "nuvoton,ma35d1-i2c" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, ma35d1_i2c_of_match);
> +
> +static struct platform_driver ma35d1_i2c_driver = {
> + .probe = ma35d1_i2c_probe,
> + .driver = {
> + .name = "ma35d1-i2c",
> + .of_match_table = ma35d1_i2c_of_match,
> + .pm = pm_ptr(&ma35d1_i2c_pmops),
> + },
> +};
> +module_platform_driver(ma35d1_i2c_driver);
> +
> +MODULE_AUTHOR("Zi-Yu Chen <zychennvt at gmail.com>");
> +MODULE_DESCRIPTION("MA35D1 I2C Bus Driver");
> +MODULE_LICENSE("GPL");
More information about the linux-arm-kernel
mailing list