Message ID | 20220321125855.3227057-1-conor.dooley@microchip.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | [v2] i2c: add support for microchip fpga i2c controllers | expand |
On 21/03/2022 12:58, Conor Dooley wrote: > EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe > > Add Microchip CoreI2C i2c controller support. This driver supports the > "hard" i2c controller on the Microchip PolarFire SoC & the basic feature > set for "soft" i2c controller implemtations in the FPGA fabric. > > Co-developed-by: Daire McNamara <daire.mcnamara@microchip.com> > Signed-off-by: Daire McNamara <daire.mcnamara@microchip.com> > Signed-off-by: Conor Dooley <conor.dooley@microchip.com> > --- > Changes from v1: > - Use byte write and read functions So it turns out that how I made this change, which worked in my test app, has issues. I will rework and send a v3. Thanks, Conor. > - Return IRQ_NONE and don't warn if not this drivers interrupt > - Add a clk_disable_unprepare to avoid leaking a clock in the probe > --- > drivers/i2c/busses/Kconfig | 11 + > drivers/i2c/busses/Makefile | 1 + > drivers/i2c/busses/i2c-microchip-core.c | 486 ++++++++++++++++++++++++ > 3 files changed, 498 insertions(+) > create mode 100644 drivers/i2c/busses/i2c-microchip-core.c > > diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig > index a1bae59208e3..3d4d8e0e9de7 100644 > --- a/drivers/i2c/busses/Kconfig > +++ b/drivers/i2c/busses/Kconfig > @@ -781,6 +781,17 @@ config I2C_MESON > If you say yes to this option, support will be included for the > I2C interface on the Amlogic Meson family of SoCs. > > +config I2C_MICROCHIP_CORE > + tristate "Microchip FPGA I2C controller" > + depends on SOC_MICROCHIP_POLARFIRE || COMPILE_TEST > + depends on OF > + help > + If you say yes to this option, support will be included for the > + I2C interface on Microchip FPGAs. > + > + This driver can also be built as a module. If so, the module will be > + called i2c-microchip-core. > + > config I2C_MPC > tristate "MPC107/824x/85xx/512x/52xx/83xx/86xx" > depends on PPC > diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile > index 479f60e4ee3d..75869b189e43 100644 > --- a/drivers/i2c/busses/Makefile > +++ b/drivers/i2c/busses/Makefile > @@ -78,6 +78,7 @@ obj-$(CONFIG_I2C_JZ4780) += i2c-jz4780.o > obj-$(CONFIG_I2C_KEMPLD) += i2c-kempld.o > obj-$(CONFIG_I2C_LPC2K) += i2c-lpc2k.o > obj-$(CONFIG_I2C_MESON) += i2c-meson.o > +obj-$(CONFIG_I2C_MICROCHIP_CORE) += i2c-microchip-core.o > obj-$(CONFIG_I2C_MPC) += i2c-mpc.o > obj-$(CONFIG_I2C_MT65XX) += i2c-mt65xx.o > obj-$(CONFIG_I2C_MT7621) += i2c-mt7621.o > diff --git a/drivers/i2c/busses/i2c-microchip-core.c b/drivers/i2c/busses/i2c-microchip-core.c > new file mode 100644 > index 000000000000..ea434f39e0cd > --- /dev/null > +++ b/drivers/i2c/busses/i2c-microchip-core.c > @@ -0,0 +1,486 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Microchip CoreI2C I2C controller driver > + * > + * Copyright (c) 2018 - 2022 Microchip Corporation. All rights reserved. > + * > + * Author: Daire McNamara <daire.mcnamara@microchip.com> > + * Author: Conor Dooley <conor.dooley@microchip.com> > + */ > +#include <linux/clk.h> > +#include <linux/clkdev.h> > +#include <linux/err.h> > +#include <linux/i2c.h> > +#include <linux/iopoll.h> > +#include <linux/interrupt.h> > +#include <linux/module.h> > +#include <linux/io.h> > +#include <linux/kernel.h> > +#include <linux/platform_device.h> > + > +#define MICROCHIP_I2C_TIMEOUT (msecs_to_jiffies(1000)) > + > +#define CORE_I2C_CTRL (0x00) > +#define CTRL_CR0 BIT(0) > +#define CTRL_CR1 BIT(1) > +#define CTRL_AA BIT(2) > +#define CTRL_SI BIT(3) > +#define CTRL_STO BIT(4) > +#define CTRL_STA BIT(5) > +#define CTRL_ENS1 BIT(6) > +#define CTRL_CR2 BIT(7) > +#define CORE_I2C_STATUS (0x04) > +#define STATUS_BUS_ERROR (0x00) > +#define STATUS_M_START_SENT (0x08) > +#define STATUS_M_REPEATED_START_SENT (0x10) > +#define STATUS_M_SLAW_ACK (0x18) > +#define STATUS_M_SLAW_NACK (0x20) > +#define STATUS_M_TX_DATA_ACK (0x28) > +#define STATUS_M_TX_DATA_NACK (0x30) > +#define STATUS_M_ARB_LOST (0x38) > +#define STATUS_M_SLAR_ACK (0x40) > +#define STATUS_M_SLAR_NACK (0x48) > +#define STATUS_M_RX_DATA_ACKED (0x50) > +#define STATUS_M_RX_DATA_NACKED (0x58) > +#define STATUS_S_SLAW_ACKED (0x60) > +#define STATUS_S_ARB_LOST_SLAW_ACKED (0x68) > +#define STATUS_S_GENERAL_CALL_ACKED (0x70) > +#define STATUS_S_ARB_LOST_GENERAL_CALL_ACKED (0x78) > +#define STATUS_S_RX_DATA_ACKED (0x80) > +#define STATUS_S_RX_DATA_NACKED (0x88) > +#define STATUS_S_GENERAL_CALL_RX_DATA_ACKED (0x90) > +#define STATUS_S_GENERAL_CALL_RX_DATA_NACKED (0x98) > +#define STATUS_S_RX_STOP (0xA0) > +#define STATUS_S_SLAR_ACKED (0xA8) > +#define STATUS_S_ARB_LOST_SLAR_ACKED (0xB0) > +#define STATUS_S_TX_DATA_ACK (0xB8) > +#define STATUS_S_TX_DATA_NACK (0xC0) > +#define STATUS_LAST_DATA_ACK (0xC8) > +#define STATUS_M_SMB_MASTER_RESET (0xD0) > +#define STATUS_S_SCL_LOW_TIMEOUT (0xD8) /* 25 ms */ > +#define STATUS_NO_STATE_INFO (0xF8) > +#define CORE_I2C_DATA (0x08) > +#define WRITE_BIT (0x0) > +#define READ_BIT (0x1) > +#define SLAVE_ADDR_SHIFT (1) > +#define CORE_I2C_SLAVE0_ADDR (0x0c) > +#define GENERAL_CALL_BIT (0x0) > +#define CORE_I2C_SMBUS (0x10) > +#define SMBALERT_INT_ENB (0x0) > +#define SMBSUS_INT_ENB (0x1) > +#define SMBUS_ENB (0x2) > +#define SMBALERT_NI_STATUS (0x3) > +#define SMBALERT_NO_CTRL (0x4) > +#define SMBSUS_NI_STATUS (0x5) > +#define SMBSUS_NO_CTRL (0x6) > +#define SMBUS_RESET (0x7) > +#define CORE_I2C_FREQ (0x14) > +#define CORE_I2C_GLITCHREG (0x18) > +#define CORE_I2C_SLAVE1_ADDR (0x1c) > + > +#define PCLK_DIV_960 (CTRL_CR2) > +#define PCLK_DIV_256 (0) > +#define PCLK_DIV_224 (CTRL_CR0) > +#define PCLK_DIV_192 (CTRL_CR1) > +#define PCLK_DIV_160 (CTRL_CR0 | CTRL_CR1) > +#define PCLK_DIV_120 (CTRL_CR0 | CTRL_CR2) > +#define PCLK_DIV_60 (CTRL_CR1 | CTRL_CR2) > +#define BCLK_DIV_8 (CTRL_CR0 | CTRL_CR1 | CTRL_CR2) > +#define CLK_MASK (CTRL_CR0 | CTRL_CR1 | CTRL_CR2) > + > +/* > + * mchp_corei2c_dev - I2C device context > + * @base: pointer to register struct > + * @msg: pointer to current message > + * @msg_len: number of bytes transferred in msg > + * @msg_err: error code for completed message > + * @msg_complete: xfer completion object > + * @dev: device reference > + * @adapter: core i2c abstraction > + * @i2c_clk: clock reference for i2c input clock > + * @bus_clk_rate: current i2c bus clock rate > + * @buf: ptr to msg buffer for easier use. > + * @isr_status: cached copy of local ISR status. > + * @lock: spinlock for IRQ synchronization. > + */ > +struct mchp_corei2c_dev { > + void __iomem *base; > + size_t msg_len; > + int msg_err; > + struct completion msg_complete; > + struct device *dev; > + struct i2c_adapter adapter; > + struct clk *i2c_clk; > + spinlock_t lock; /* IRQ synchronization */ > + u32 bus_clk_rate; > + u32 msg_read; > + u32 isr_status; > + u8 *buf; > + u8 addr; > +}; > + > +static void mchp_corei2c_core_disable(struct mchp_corei2c_dev *idev) > +{ > + u8 ctrl = readb(idev->base + CORE_I2C_CTRL); > + > + ctrl &= ~CTRL_ENS1; > + writeb(ctrl, idev->base + CORE_I2C_CTRL); > +} > + > +static void mchp_corei2c_core_enable(struct mchp_corei2c_dev *idev) > +{ > + u8 ctrl = readb(idev->base + CORE_I2C_CTRL); > + > + ctrl |= CTRL_ENS1; > + writeb(ctrl, idev->base + CORE_I2C_CTRL); > +} > + > +static void mchp_corei2c_reset(struct mchp_corei2c_dev *idev) > +{ > + mchp_corei2c_core_disable(idev); > + mchp_corei2c_core_enable(idev); > +} > + > +static inline void mchp_corei2c_stop(struct mchp_corei2c_dev *idev) > +{ > + u8 ctrl = readb(idev->base + CORE_I2C_CTRL); > + > + ctrl |= CTRL_STO; > + writeb(ctrl, idev->base + CORE_I2C_CTRL); > +} > + > +static inline int mchp_corei2c_set_divisor(u32 rate, > + struct mchp_corei2c_dev *idev) > +{ > + u8 clkval, ctrl; > + > + if (rate >= 960) > + clkval = PCLK_DIV_960; > + else if (rate >= 256) > + clkval = PCLK_DIV_256; > + else if (rate >= 224) > + clkval = PCLK_DIV_224; > + else if (rate >= 192) > + clkval = PCLK_DIV_192; > + else if (rate >= 160) > + clkval = PCLK_DIV_160; > + else if (rate >= 120) > + clkval = PCLK_DIV_120; > + else if (rate >= 60) > + clkval = PCLK_DIV_60; > + else if (rate >= 8) > + clkval = BCLK_DIV_8; > + else > + return -EINVAL; > + > + ctrl = readb(idev->base + CORE_I2C_CTRL); > + ctrl &= ~CLK_MASK; > + ctrl |= clkval; > + writeb(ctrl, idev->base + CORE_I2C_CTRL); > + > + ctrl = readb(idev->base + CORE_I2C_CTRL); > + if ((ctrl & CLK_MASK) != clkval) > + return -EIO; > + > + return 0; > +} > + > +static int mchp_corei2c_init(struct mchp_corei2c_dev *idev) > +{ > + u32 clk_rate = clk_get_rate(idev->i2c_clk); > + u32 divisor = clk_rate / idev->bus_clk_rate; > + int ret; > + > + ret = mchp_corei2c_set_divisor(divisor, idev); > + if (ret) > + return ret; > + > + mchp_corei2c_reset(idev); > + > + return 0; > +} > + > +static void mchp_corei2c_transfer(struct mchp_corei2c_dev *idev, u32 data) > +{ > + if (idev->msg_len > 0) > + writeb(data, idev->base + CORE_I2C_DATA); > +} > + > +static void mchp_corei2c_empty_rx(struct mchp_corei2c_dev *idev) > +{ > + u8 ctrl; > + > + if (idev->msg_len > 0) { > + *idev->buf++ = readb(idev->base + CORE_I2C_DATA); > + idev->msg_len--; > + } > + > + if (idev->msg_len == 0) { > + ctrl = readb(idev->base + CORE_I2C_CTRL); > + ctrl &= ~CTRL_AA; > + writeb(ctrl, idev->base + CORE_I2C_CTRL); > + } > +} > + > +static int mchp_corei2c_fill_tx(struct mchp_corei2c_dev *idev) > +{ > + mchp_corei2c_transfer(idev, *idev->buf++); > + idev->msg_len--; > + > + return 0; > +} > + > +static irqreturn_t mchp_corei2c_handle_isr(struct mchp_corei2c_dev *idev) > +{ > + u32 status = idev->isr_status; > + u8 ctrl; > + > + if (!idev->buf) > + return IRQ_NONE; > + > + switch (status) { > + case STATUS_M_START_SENT: > + case STATUS_M_REPEATED_START_SENT: > + ctrl = readb(idev->base + CORE_I2C_CTRL); > + ctrl &= ~CTRL_STA; > + writeb(idev->addr, idev->base + CORE_I2C_DATA); > + writeb(ctrl, idev->base + CORE_I2C_CTRL); > + if (idev->msg_len <= 0) > + goto finished; > + break; > + case STATUS_M_ARB_LOST: > + idev->msg_err = -EAGAIN; > + goto finished; > + case STATUS_M_SLAW_ACK: > + case STATUS_M_TX_DATA_ACK: > + if (idev->msg_len > 0) > + mchp_corei2c_fill_tx(idev); > + else > + goto last_byte; > + break; > + case STATUS_M_TX_DATA_NACK: > + case STATUS_M_SLAR_NACK: > + case STATUS_M_SLAW_NACK: > + idev->msg_err = -ENXIO; > + goto last_byte; > + case STATUS_M_SLAR_ACK: > + ctrl = readb(idev->base + CORE_I2C_CTRL); > + if (idev->msg_len == 1u) { > + ctrl &= ~CTRL_AA; > + writeb(ctrl, idev->base + CORE_I2C_CTRL); > + } else { > + ctrl |= CTRL_AA; > + writeb(ctrl, idev->base + CORE_I2C_CTRL); > + } > + if (idev->msg_len < 1u) > + goto last_byte; > + break; > + case STATUS_M_RX_DATA_ACKED: > + mchp_corei2c_empty_rx(idev); > + break; > + case STATUS_M_RX_DATA_NACKED: > + mchp_corei2c_empty_rx(idev); > + if (idev->msg_len == 0) > + goto last_byte; > + break; > + default: > + break; > + } > + > + return IRQ_HANDLED; > + > +last_byte: > + /* On the last byte to be transmitted, send STOP */ > + mchp_corei2c_stop(idev); > +finished: > + complete(&idev->msg_complete); > + return IRQ_HANDLED; > +} > + > +static irqreturn_t mchp_corei2c_isr(int irq, void *_dev) > +{ > + struct mchp_corei2c_dev *idev = _dev; > + irqreturn_t ret = IRQ_NONE; > + u8 ctrl; > + > + ctrl = readb(idev->base + CORE_I2C_CTRL); > + if (ctrl & CTRL_SI) { > + idev->isr_status = readb(idev->base + CORE_I2C_STATUS); > + ret = mchp_corei2c_handle_isr(idev); > + } > + > + /* Clear the si flag */ > + ctrl = readb(idev->base + CORE_I2C_CTRL); > + ctrl &= ~CTRL_SI; > + writeb(ctrl, idev->base + CORE_I2C_CTRL); > + > + return ret; > +} > + > +static int mchp_corei2c_xfer_msg(struct mchp_corei2c_dev *idev, > + struct i2c_msg *msg) > +{ > + u8 ctrl; > + unsigned long time_left; > + > + if (msg->len == 0) > + return -EINVAL; > + > + idev->addr = i2c_8bit_addr_from_msg(msg); > + idev->msg_len = msg->len; > + idev->buf = msg->buf; > + idev->msg_err = 0; > + idev->msg_read = (msg->flags & I2C_M_RD); > + > + reinit_completion(&idev->msg_complete); > + > + mchp_corei2c_core_enable(idev); > + > + ctrl = readb(idev->base + CORE_I2C_CTRL); > + ctrl |= CTRL_STA; > + writeb(ctrl, idev->base + CORE_I2C_CTRL); > + > + time_left = wait_for_completion_timeout(&idev->msg_complete, > + MICROCHIP_I2C_TIMEOUT); > + if (!time_left) > + return -ETIMEDOUT; > + > + return idev->msg_err; > +} > + > +static int mchp_corei2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, > + int num) > +{ > + struct mchp_corei2c_dev *idev = i2c_get_adapdata(adap); > + int i, ret; > + > + for (i = 0; i < num; i++) { > + ret = mchp_corei2c_xfer_msg(idev, msgs++); > + if (ret) > + return ret; > + } > + > + return num; > +} > + > +static u32 mchp_corei2c_func(struct i2c_adapter *adap) > +{ > + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; > +} > + > +static const struct i2c_algorithm mchp_corei2c_algo = { > + .master_xfer = mchp_corei2c_xfer, > + .functionality = mchp_corei2c_func, > +}; > + > +static int mchp_corei2c_probe(struct platform_device *pdev) > +{ > + struct mchp_corei2c_dev *idev = NULL; > + struct resource *res; > + int irq, ret; > + u32 val; > + > + idev = devm_kzalloc(&pdev->dev, sizeof(*idev), GFP_KERNEL); > + if (!idev) > + return -ENOMEM; > + > + idev->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); > + if (IS_ERR(idev->base)) > + return PTR_ERR(idev->base); > + > + irq = platform_get_irq(pdev, 0); > + if (irq < 0) > + return dev_err_probe(&pdev->dev, irq, > + "missing interrupt resource\n"); > + > + idev->i2c_clk = devm_clk_get(&pdev->dev, NULL); > + if (IS_ERR(idev->i2c_clk)) > + return dev_err_probe(&pdev->dev, PTR_ERR(idev->i2c_clk), > + "missing clock\n"); > + > + idev->dev = &pdev->dev; > + init_completion(&idev->msg_complete); > + spin_lock_init(&idev->lock); > + > + val = device_property_read_u32(idev->dev, "clock-frequency", > + &idev->bus_clk_rate); > + if (val) { > + dev_info(&pdev->dev, "default to 100kHz\n"); > + idev->bus_clk_rate = 100000; > + } > + > + if (idev->bus_clk_rate > 400000) > + return dev_err_probe(&pdev->dev, -EINVAL, > + "clock-frequency too high: %d\n", > + idev->bus_clk_rate); > + > + ret = devm_request_irq(&pdev->dev, irq, mchp_corei2c_isr, IRQF_SHARED, > + pdev->name, idev); > + if (ret) > + return dev_err_probe(&pdev->dev, ret, > + "failed to claim irq %d\n", irq); > + > + ret = clk_prepare_enable(idev->i2c_clk); > + if (ret) > + return dev_err_probe(&pdev->dev, ret, > + "failed to enable clock\n"); > + > + ret = mchp_corei2c_init(idev); > + if (ret) { > + clk_disable_unprepare(idev->i2c_clk); > + return dev_err_probe(&pdev->dev, ret, "failed to program clock divider\n"); > + } > + > + i2c_set_adapdata(&idev->adapter, idev); > + snprintf(idev->adapter.name, sizeof(idev->adapter.name), > + "Microchip I2C hw bus"); > + idev->adapter.owner = THIS_MODULE; > + idev->adapter.algo = &mchp_corei2c_algo; > + idev->adapter.dev.parent = &pdev->dev; > + idev->adapter.dev.of_node = pdev->dev.of_node; > + > + platform_set_drvdata(pdev, idev); > + > + ret = i2c_add_adapter(&idev->adapter); > + if (ret) { > + clk_disable_unprepare(idev->i2c_clk); > + return ret; > + } > + > + dev_info(&pdev->dev, "Microchip I2C Probe Complete\n"); > + > + return 0; > +} > + > +static int mchp_corei2c_remove(struct platform_device *pdev) > +{ > + struct mchp_corei2c_dev *idev = platform_get_drvdata(pdev); > + > + clk_disable_unprepare(idev->i2c_clk); > + i2c_del_adapter(&idev->adapter); > + > + return 0; > +} > + > +static const struct of_device_id mchp_corei2c_of_match[] = { > + { .compatible = "microchip,mpfs-i2c" }, > + { .compatible = "microchip,corei2c-rtl-v7" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, mchp_corei2c_of_match); > + > +static struct platform_driver mchp_corei2c_driver = { > + .probe = mchp_corei2c_probe, > + .remove = mchp_corei2c_remove, > + .driver = { > + .name = "microchip-corei2c", > + .of_match_table = mchp_corei2c_of_match, > + }, > +}; > + > +module_platform_driver(mchp_corei2c_driver); > + > +MODULE_DESCRIPTION("Microchip CoreI2C bus driver"); > +MODULE_AUTHOR("Daire McNamara <daire.mcnamara@microchip.com>"); > +MODULE_AUTHOR("Conor Dooley <conor.dooley@microchip.com>"); > +MODULE_LICENSE("GPL v2"); > -- > 2.35.1 > > > _______________________________________________ > linux-riscv mailing list > linux-riscv@lists.infradead.org > http://lists.infradead.org/mailman/listinfo/linux-riscv
On Mon, 21 Mar 2022 12:58:56 +0000 Conor Dooley wrote: > + > +static irqreturn_t mchp_corei2c_handle_isr(struct mchp_corei2c_dev *idev) > +{ > + u32 status = idev->isr_status; > + u8 ctrl; > + > + if (!idev->buf) > + return IRQ_NONE; > + > + switch (status) { > + case STATUS_M_START_SENT: > + case STATUS_M_REPEATED_START_SENT: > + ctrl = readb(idev->base + CORE_I2C_CTRL); > + ctrl &= ~CTRL_STA; > + writeb(idev->addr, idev->base + CORE_I2C_DATA); > + writeb(ctrl, idev->base + CORE_I2C_CTRL); > + if (idev->msg_len <= 0) > + goto finished; > + break; > + case STATUS_M_ARB_LOST: > + idev->msg_err = -EAGAIN; > + goto finished; > + case STATUS_M_SLAW_ACK: > + case STATUS_M_TX_DATA_ACK: > + if (idev->msg_len > 0) > + mchp_corei2c_fill_tx(idev); > + else > + goto last_byte; > + break; > + case STATUS_M_TX_DATA_NACK: > + case STATUS_M_SLAR_NACK: > + case STATUS_M_SLAW_NACK: > + idev->msg_err = -ENXIO; > + goto last_byte; > + case STATUS_M_SLAR_ACK: > + ctrl = readb(idev->base + CORE_I2C_CTRL); > + if (idev->msg_len == 1u) { > + ctrl &= ~CTRL_AA; > + writeb(ctrl, idev->base + CORE_I2C_CTRL); > + } else { > + ctrl |= CTRL_AA; > + writeb(ctrl, idev->base + CORE_I2C_CTRL); > + } > + if (idev->msg_len < 1u) > + goto last_byte; > + break; > + case STATUS_M_RX_DATA_ACKED: > + mchp_corei2c_empty_rx(idev); > + break; > + case STATUS_M_RX_DATA_NACKED: > + mchp_corei2c_empty_rx(idev); > + if (idev->msg_len == 0) > + goto last_byte; > + break; > + default: > + break; > + } > + > + return IRQ_HANDLED; > + > +last_byte: > + /* On the last byte to be transmitted, send STOP */ > + mchp_corei2c_stop(idev); > +finished: > + complete(&idev->msg_complete); > + return IRQ_HANDLED; > +} > + > +static irqreturn_t mchp_corei2c_isr(int irq, void *_dev) > +{ > + struct mchp_corei2c_dev *idev = _dev; > + irqreturn_t ret = IRQ_NONE; > + u8 ctrl; > + > + ctrl = readb(idev->base + CORE_I2C_CTRL); > + if (ctrl & CTRL_SI) { > + idev->isr_status = readb(idev->base + CORE_I2C_STATUS); > + ret = mchp_corei2c_handle_isr(idev); > + } > + > + /* Clear the si flag */ > + ctrl = readb(idev->base + CORE_I2C_CTRL); > + ctrl &= ~CTRL_SI; > + writeb(ctrl, idev->base + CORE_I2C_CTRL); > + > + return ret; > +} > + > +static int mchp_corei2c_xfer_msg(struct mchp_corei2c_dev *idev, > + struct i2c_msg *msg) > +{ > + u8 ctrl; > + unsigned long time_left; > + > + if (msg->len == 0) > + return -EINVAL; > + > + idev->addr = i2c_8bit_addr_from_msg(msg); > + idev->msg_len = msg->len; > + idev->buf = msg->buf; > + idev->msg_err = 0; > + idev->msg_read = (msg->flags & I2C_M_RD); > + > + reinit_completion(&idev->msg_complete); Would you specify why you need reinit completion? Is it likely to race with the complete() above in mchp_corei2c_handle_isr()? Hillf > + > + mchp_corei2c_core_enable(idev); > + > + ctrl = readb(idev->base + CORE_I2C_CTRL); > + ctrl |= CTRL_STA; > + writeb(ctrl, idev->base + CORE_I2C_CTRL); > + > + time_left = wait_for_completion_timeout(&idev->msg_complete, > + MICROCHIP_I2C_TIMEOUT); > + if (!time_left) > + return -ETIMEDOUT; > + > + return idev->msg_err; > +}
On 04/05/2022 10:16, Hillf Danton wrote: > [You don't often get email from hdanton@sina.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification.] > > EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe > > On Mon, 21 Mar 2022 12:58:56 +0000 Conor Dooley wrote: >> + >> +static irqreturn_t mchp_corei2c_handle_isr(struct mchp_corei2c_dev *idev) >> +{ >> + u32 status = idev->isr_status; >> + u8 ctrl; >> + >> + if (!idev->buf) >> + return IRQ_NONE; >> + >> + switch (status) { >> + case STATUS_M_START_SENT: >> + case STATUS_M_REPEATED_START_SENT: >> + ctrl = readb(idev->base + CORE_I2C_CTRL); >> + ctrl &= ~CTRL_STA; >> + writeb(idev->addr, idev->base + CORE_I2C_DATA); >> + writeb(ctrl, idev->base + CORE_I2C_CTRL); >> + if (idev->msg_len <= 0) >> + goto finished; >> + break; >> + case STATUS_M_ARB_LOST: >> + idev->msg_err = -EAGAIN; >> + goto finished; >> + case STATUS_M_SLAW_ACK: >> + case STATUS_M_TX_DATA_ACK: >> + if (idev->msg_len > 0) >> + mchp_corei2c_fill_tx(idev); >> + else >> + goto last_byte; >> + break; >> + case STATUS_M_TX_DATA_NACK: >> + case STATUS_M_SLAR_NACK: >> + case STATUS_M_SLAW_NACK: >> + idev->msg_err = -ENXIO; >> + goto last_byte; >> + case STATUS_M_SLAR_ACK: >> + ctrl = readb(idev->base + CORE_I2C_CTRL); >> + if (idev->msg_len == 1u) { >> + ctrl &= ~CTRL_AA; >> + writeb(ctrl, idev->base + CORE_I2C_CTRL); >> + } else { >> + ctrl |= CTRL_AA; >> + writeb(ctrl, idev->base + CORE_I2C_CTRL); >> + } >> + if (idev->msg_len < 1u) >> + goto last_byte; >> + break; >> + case STATUS_M_RX_DATA_ACKED: >> + mchp_corei2c_empty_rx(idev); >> + break; >> + case STATUS_M_RX_DATA_NACKED: >> + mchp_corei2c_empty_rx(idev); >> + if (idev->msg_len == 0) >> + goto last_byte; >> + break; >> + default: >> + break; >> + } >> + >> + return IRQ_HANDLED; >> + >> +last_byte: >> + /* On the last byte to be transmitted, send STOP */ >> + mchp_corei2c_stop(idev); >> +finished: >> + complete(&idev->msg_complete); >> + return IRQ_HANDLED; >> +} >> + >> +static irqreturn_t mchp_corei2c_isr(int irq, void *_dev) >> +{ >> + struct mchp_corei2c_dev *idev = _dev; >> + irqreturn_t ret = IRQ_NONE; >> + u8 ctrl; >> + >> + ctrl = readb(idev->base + CORE_I2C_CTRL); >> + if (ctrl & CTRL_SI) { >> + idev->isr_status = readb(idev->base + CORE_I2C_STATUS); >> + ret = mchp_corei2c_handle_isr(idev); >> + } >> + >> + /* Clear the si flag */ >> + ctrl = readb(idev->base + CORE_I2C_CTRL); >> + ctrl &= ~CTRL_SI; >> + writeb(ctrl, idev->base + CORE_I2C_CTRL); >> + >> + return ret; >> +} >> + >> +static int mchp_corei2c_xfer_msg(struct mchp_corei2c_dev *idev, >> + struct i2c_msg *msg) >> +{ >> + u8 ctrl; >> + unsigned long time_left; >> + >> + if (msg->len == 0) >> + return -EINVAL; >> + >> + idev->addr = i2c_8bit_addr_from_msg(msg); >> + idev->msg_len = msg->len; >> + idev->buf = msg->buf; >> + idev->msg_err = 0; >> + idev->msg_read = (msg->flags & I2C_M_RD); >> + >> + reinit_completion(&idev->msg_complete); > > Would you specify why you need reinit completion? Is it likely to race > with the complete() above in mchp_corei2c_handle_isr()? Is it not fairly common in i2c drivers to use a reinit completion()/ wait_for_completion_timeout() in the xfer function with a complete() in the isr? I can certainly add a comment explaining why this is being done? Thanks, Conor.
On 04/05/2022 09:40, Conor.Dooley@microchip.com wrote: > On 21/03/2022 12:58, Conor Dooley wrote: >> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe >> >> Add Microchip CoreI2C i2c controller support. This driver supports the >> "hard" i2c controller on the Microchip PolarFire SoC & the basic feature >> set for "soft" i2c controller implemtations in the FPGA fabric. >> >> Co-developed-by: Daire McNamara <daire.mcnamara@microchip.com> >> Signed-off-by: Daire McNamara <daire.mcnamara@microchip.com> >> Signed-off-by: Conor Dooley <conor.dooley@microchip.com> >> --- >> Changes from v1: >> - Use byte write and read functions > > So it turns out that how I made this change, which worked in my > test app, has issues. > I will rework and send a v3. > Welp, as it turns out we can chalk the issues up to user error, aka me entirely forgetting what the correct i2cdetect options were - d'oh. Apologies for the noise & unless something else changes, don't expect a V3. Sorry, Conor.
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index a1bae59208e3..3d4d8e0e9de7 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -781,6 +781,17 @@ config I2C_MESON If you say yes to this option, support will be included for the I2C interface on the Amlogic Meson family of SoCs. +config I2C_MICROCHIP_CORE + tristate "Microchip FPGA I2C controller" + depends on SOC_MICROCHIP_POLARFIRE || COMPILE_TEST + depends on OF + help + If you say yes to this option, support will be included for the + I2C interface on Microchip FPGAs. + + This driver can also be built as a module. If so, the module will be + called i2c-microchip-core. + config I2C_MPC tristate "MPC107/824x/85xx/512x/52xx/83xx/86xx" depends on PPC diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 479f60e4ee3d..75869b189e43 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -78,6 +78,7 @@ obj-$(CONFIG_I2C_JZ4780) += i2c-jz4780.o obj-$(CONFIG_I2C_KEMPLD) += i2c-kempld.o obj-$(CONFIG_I2C_LPC2K) += i2c-lpc2k.o obj-$(CONFIG_I2C_MESON) += i2c-meson.o +obj-$(CONFIG_I2C_MICROCHIP_CORE) += i2c-microchip-core.o obj-$(CONFIG_I2C_MPC) += i2c-mpc.o obj-$(CONFIG_I2C_MT65XX) += i2c-mt65xx.o obj-$(CONFIG_I2C_MT7621) += i2c-mt7621.o diff --git a/drivers/i2c/busses/i2c-microchip-core.c b/drivers/i2c/busses/i2c-microchip-core.c new file mode 100644 index 000000000000..ea434f39e0cd --- /dev/null +++ b/drivers/i2c/busses/i2c-microchip-core.c @@ -0,0 +1,486 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Microchip CoreI2C I2C controller driver + * + * Copyright (c) 2018 - 2022 Microchip Corporation. All rights reserved. + * + * Author: Daire McNamara <daire.mcnamara@microchip.com> + * Author: Conor Dooley <conor.dooley@microchip.com> + */ +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/iopoll.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> + +#define MICROCHIP_I2C_TIMEOUT (msecs_to_jiffies(1000)) + +#define CORE_I2C_CTRL (0x00) +#define CTRL_CR0 BIT(0) +#define CTRL_CR1 BIT(1) +#define CTRL_AA BIT(2) +#define CTRL_SI BIT(3) +#define CTRL_STO BIT(4) +#define CTRL_STA BIT(5) +#define CTRL_ENS1 BIT(6) +#define CTRL_CR2 BIT(7) +#define CORE_I2C_STATUS (0x04) +#define STATUS_BUS_ERROR (0x00) +#define STATUS_M_START_SENT (0x08) +#define STATUS_M_REPEATED_START_SENT (0x10) +#define STATUS_M_SLAW_ACK (0x18) +#define STATUS_M_SLAW_NACK (0x20) +#define STATUS_M_TX_DATA_ACK (0x28) +#define STATUS_M_TX_DATA_NACK (0x30) +#define STATUS_M_ARB_LOST (0x38) +#define STATUS_M_SLAR_ACK (0x40) +#define STATUS_M_SLAR_NACK (0x48) +#define STATUS_M_RX_DATA_ACKED (0x50) +#define STATUS_M_RX_DATA_NACKED (0x58) +#define STATUS_S_SLAW_ACKED (0x60) +#define STATUS_S_ARB_LOST_SLAW_ACKED (0x68) +#define STATUS_S_GENERAL_CALL_ACKED (0x70) +#define STATUS_S_ARB_LOST_GENERAL_CALL_ACKED (0x78) +#define STATUS_S_RX_DATA_ACKED (0x80) +#define STATUS_S_RX_DATA_NACKED (0x88) +#define STATUS_S_GENERAL_CALL_RX_DATA_ACKED (0x90) +#define STATUS_S_GENERAL_CALL_RX_DATA_NACKED (0x98) +#define STATUS_S_RX_STOP (0xA0) +#define STATUS_S_SLAR_ACKED (0xA8) +#define STATUS_S_ARB_LOST_SLAR_ACKED (0xB0) +#define STATUS_S_TX_DATA_ACK (0xB8) +#define STATUS_S_TX_DATA_NACK (0xC0) +#define STATUS_LAST_DATA_ACK (0xC8) +#define STATUS_M_SMB_MASTER_RESET (0xD0) +#define STATUS_S_SCL_LOW_TIMEOUT (0xD8) /* 25 ms */ +#define STATUS_NO_STATE_INFO (0xF8) +#define CORE_I2C_DATA (0x08) +#define WRITE_BIT (0x0) +#define READ_BIT (0x1) +#define SLAVE_ADDR_SHIFT (1) +#define CORE_I2C_SLAVE0_ADDR (0x0c) +#define GENERAL_CALL_BIT (0x0) +#define CORE_I2C_SMBUS (0x10) +#define SMBALERT_INT_ENB (0x0) +#define SMBSUS_INT_ENB (0x1) +#define SMBUS_ENB (0x2) +#define SMBALERT_NI_STATUS (0x3) +#define SMBALERT_NO_CTRL (0x4) +#define SMBSUS_NI_STATUS (0x5) +#define SMBSUS_NO_CTRL (0x6) +#define SMBUS_RESET (0x7) +#define CORE_I2C_FREQ (0x14) +#define CORE_I2C_GLITCHREG (0x18) +#define CORE_I2C_SLAVE1_ADDR (0x1c) + +#define PCLK_DIV_960 (CTRL_CR2) +#define PCLK_DIV_256 (0) +#define PCLK_DIV_224 (CTRL_CR0) +#define PCLK_DIV_192 (CTRL_CR1) +#define PCLK_DIV_160 (CTRL_CR0 | CTRL_CR1) +#define PCLK_DIV_120 (CTRL_CR0 | CTRL_CR2) +#define PCLK_DIV_60 (CTRL_CR1 | CTRL_CR2) +#define BCLK_DIV_8 (CTRL_CR0 | CTRL_CR1 | CTRL_CR2) +#define CLK_MASK (CTRL_CR0 | CTRL_CR1 | CTRL_CR2) + +/* + * mchp_corei2c_dev - I2C device context + * @base: pointer to register struct + * @msg: pointer to current message + * @msg_len: number of bytes transferred in msg + * @msg_err: error code for completed message + * @msg_complete: xfer completion object + * @dev: device reference + * @adapter: core i2c abstraction + * @i2c_clk: clock reference for i2c input clock + * @bus_clk_rate: current i2c bus clock rate + * @buf: ptr to msg buffer for easier use. + * @isr_status: cached copy of local ISR status. + * @lock: spinlock for IRQ synchronization. + */ +struct mchp_corei2c_dev { + void __iomem *base; + size_t msg_len; + int msg_err; + struct completion msg_complete; + struct device *dev; + struct i2c_adapter adapter; + struct clk *i2c_clk; + spinlock_t lock; /* IRQ synchronization */ + u32 bus_clk_rate; + u32 msg_read; + u32 isr_status; + u8 *buf; + u8 addr; +}; + +static void mchp_corei2c_core_disable(struct mchp_corei2c_dev *idev) +{ + u8 ctrl = readb(idev->base + CORE_I2C_CTRL); + + ctrl &= ~CTRL_ENS1; + writeb(ctrl, idev->base + CORE_I2C_CTRL); +} + +static void mchp_corei2c_core_enable(struct mchp_corei2c_dev *idev) +{ + u8 ctrl = readb(idev->base + CORE_I2C_CTRL); + + ctrl |= CTRL_ENS1; + writeb(ctrl, idev->base + CORE_I2C_CTRL); +} + +static void mchp_corei2c_reset(struct mchp_corei2c_dev *idev) +{ + mchp_corei2c_core_disable(idev); + mchp_corei2c_core_enable(idev); +} + +static inline void mchp_corei2c_stop(struct mchp_corei2c_dev *idev) +{ + u8 ctrl = readb(idev->base + CORE_I2C_CTRL); + + ctrl |= CTRL_STO; + writeb(ctrl, idev->base + CORE_I2C_CTRL); +} + +static inline int mchp_corei2c_set_divisor(u32 rate, + struct mchp_corei2c_dev *idev) +{ + u8 clkval, ctrl; + + if (rate >= 960) + clkval = PCLK_DIV_960; + else if (rate >= 256) + clkval = PCLK_DIV_256; + else if (rate >= 224) + clkval = PCLK_DIV_224; + else if (rate >= 192) + clkval = PCLK_DIV_192; + else if (rate >= 160) + clkval = PCLK_DIV_160; + else if (rate >= 120) + clkval = PCLK_DIV_120; + else if (rate >= 60) + clkval = PCLK_DIV_60; + else if (rate >= 8) + clkval = BCLK_DIV_8; + else + return -EINVAL; + + ctrl = readb(idev->base + CORE_I2C_CTRL); + ctrl &= ~CLK_MASK; + ctrl |= clkval; + writeb(ctrl, idev->base + CORE_I2C_CTRL); + + ctrl = readb(idev->base + CORE_I2C_CTRL); + if ((ctrl & CLK_MASK) != clkval) + return -EIO; + + return 0; +} + +static int mchp_corei2c_init(struct mchp_corei2c_dev *idev) +{ + u32 clk_rate = clk_get_rate(idev->i2c_clk); + u32 divisor = clk_rate / idev->bus_clk_rate; + int ret; + + ret = mchp_corei2c_set_divisor(divisor, idev); + if (ret) + return ret; + + mchp_corei2c_reset(idev); + + return 0; +} + +static void mchp_corei2c_transfer(struct mchp_corei2c_dev *idev, u32 data) +{ + if (idev->msg_len > 0) + writeb(data, idev->base + CORE_I2C_DATA); +} + +static void mchp_corei2c_empty_rx(struct mchp_corei2c_dev *idev) +{ + u8 ctrl; + + if (idev->msg_len > 0) { + *idev->buf++ = readb(idev->base + CORE_I2C_DATA); + idev->msg_len--; + } + + if (idev->msg_len == 0) { + ctrl = readb(idev->base + CORE_I2C_CTRL); + ctrl &= ~CTRL_AA; + writeb(ctrl, idev->base + CORE_I2C_CTRL); + } +} + +static int mchp_corei2c_fill_tx(struct mchp_corei2c_dev *idev) +{ + mchp_corei2c_transfer(idev, *idev->buf++); + idev->msg_len--; + + return 0; +} + +static irqreturn_t mchp_corei2c_handle_isr(struct mchp_corei2c_dev *idev) +{ + u32 status = idev->isr_status; + u8 ctrl; + + if (!idev->buf) + return IRQ_NONE; + + switch (status) { + case STATUS_M_START_SENT: + case STATUS_M_REPEATED_START_SENT: + ctrl = readb(idev->base + CORE_I2C_CTRL); + ctrl &= ~CTRL_STA; + writeb(idev->addr, idev->base + CORE_I2C_DATA); + writeb(ctrl, idev->base + CORE_I2C_CTRL); + if (idev->msg_len <= 0) + goto finished; + break; + case STATUS_M_ARB_LOST: + idev->msg_err = -EAGAIN; + goto finished; + case STATUS_M_SLAW_ACK: + case STATUS_M_TX_DATA_ACK: + if (idev->msg_len > 0) + mchp_corei2c_fill_tx(idev); + else + goto last_byte; + break; + case STATUS_M_TX_DATA_NACK: + case STATUS_M_SLAR_NACK: + case STATUS_M_SLAW_NACK: + idev->msg_err = -ENXIO; + goto last_byte; + case STATUS_M_SLAR_ACK: + ctrl = readb(idev->base + CORE_I2C_CTRL); + if (idev->msg_len == 1u) { + ctrl &= ~CTRL_AA; + writeb(ctrl, idev->base + CORE_I2C_CTRL); + } else { + ctrl |= CTRL_AA; + writeb(ctrl, idev->base + CORE_I2C_CTRL); + } + if (idev->msg_len < 1u) + goto last_byte; + break; + case STATUS_M_RX_DATA_ACKED: + mchp_corei2c_empty_rx(idev); + break; + case STATUS_M_RX_DATA_NACKED: + mchp_corei2c_empty_rx(idev); + if (idev->msg_len == 0) + goto last_byte; + break; + default: + break; + } + + return IRQ_HANDLED; + +last_byte: + /* On the last byte to be transmitted, send STOP */ + mchp_corei2c_stop(idev); +finished: + complete(&idev->msg_complete); + return IRQ_HANDLED; +} + +static irqreturn_t mchp_corei2c_isr(int irq, void *_dev) +{ + struct mchp_corei2c_dev *idev = _dev; + irqreturn_t ret = IRQ_NONE; + u8 ctrl; + + ctrl = readb(idev->base + CORE_I2C_CTRL); + if (ctrl & CTRL_SI) { + idev->isr_status = readb(idev->base + CORE_I2C_STATUS); + ret = mchp_corei2c_handle_isr(idev); + } + + /* Clear the si flag */ + ctrl = readb(idev->base + CORE_I2C_CTRL); + ctrl &= ~CTRL_SI; + writeb(ctrl, idev->base + CORE_I2C_CTRL); + + return ret; +} + +static int mchp_corei2c_xfer_msg(struct mchp_corei2c_dev *idev, + struct i2c_msg *msg) +{ + u8 ctrl; + unsigned long time_left; + + if (msg->len == 0) + return -EINVAL; + + idev->addr = i2c_8bit_addr_from_msg(msg); + idev->msg_len = msg->len; + idev->buf = msg->buf; + idev->msg_err = 0; + idev->msg_read = (msg->flags & I2C_M_RD); + + reinit_completion(&idev->msg_complete); + + mchp_corei2c_core_enable(idev); + + ctrl = readb(idev->base + CORE_I2C_CTRL); + ctrl |= CTRL_STA; + writeb(ctrl, idev->base + CORE_I2C_CTRL); + + time_left = wait_for_completion_timeout(&idev->msg_complete, + MICROCHIP_I2C_TIMEOUT); + if (!time_left) + return -ETIMEDOUT; + + return idev->msg_err; +} + +static int mchp_corei2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, + int num) +{ + struct mchp_corei2c_dev *idev = i2c_get_adapdata(adap); + int i, ret; + + for (i = 0; i < num; i++) { + ret = mchp_corei2c_xfer_msg(idev, msgs++); + if (ret) + return ret; + } + + return num; +} + +static u32 mchp_corei2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm mchp_corei2c_algo = { + .master_xfer = mchp_corei2c_xfer, + .functionality = mchp_corei2c_func, +}; + +static int mchp_corei2c_probe(struct platform_device *pdev) +{ + struct mchp_corei2c_dev *idev = NULL; + struct resource *res; + int irq, ret; + u32 val; + + idev = devm_kzalloc(&pdev->dev, sizeof(*idev), GFP_KERNEL); + if (!idev) + return -ENOMEM; + + idev->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(idev->base)) + return PTR_ERR(idev->base); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return dev_err_probe(&pdev->dev, irq, + "missing interrupt resource\n"); + + idev->i2c_clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(idev->i2c_clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(idev->i2c_clk), + "missing clock\n"); + + idev->dev = &pdev->dev; + init_completion(&idev->msg_complete); + spin_lock_init(&idev->lock); + + val = device_property_read_u32(idev->dev, "clock-frequency", + &idev->bus_clk_rate); + if (val) { + dev_info(&pdev->dev, "default to 100kHz\n"); + idev->bus_clk_rate = 100000; + } + + if (idev->bus_clk_rate > 400000) + return dev_err_probe(&pdev->dev, -EINVAL, + "clock-frequency too high: %d\n", + idev->bus_clk_rate); + + ret = devm_request_irq(&pdev->dev, irq, mchp_corei2c_isr, IRQF_SHARED, + pdev->name, idev); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to claim irq %d\n", irq); + + ret = clk_prepare_enable(idev->i2c_clk); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to enable clock\n"); + + ret = mchp_corei2c_init(idev); + if (ret) { + clk_disable_unprepare(idev->i2c_clk); + return dev_err_probe(&pdev->dev, ret, "failed to program clock divider\n"); + } + + i2c_set_adapdata(&idev->adapter, idev); + snprintf(idev->adapter.name, sizeof(idev->adapter.name), + "Microchip I2C hw bus"); + idev->adapter.owner = THIS_MODULE; + idev->adapter.algo = &mchp_corei2c_algo; + idev->adapter.dev.parent = &pdev->dev; + idev->adapter.dev.of_node = pdev->dev.of_node; + + platform_set_drvdata(pdev, idev); + + ret = i2c_add_adapter(&idev->adapter); + if (ret) { + clk_disable_unprepare(idev->i2c_clk); + return ret; + } + + dev_info(&pdev->dev, "Microchip I2C Probe Complete\n"); + + return 0; +} + +static int mchp_corei2c_remove(struct platform_device *pdev) +{ + struct mchp_corei2c_dev *idev = platform_get_drvdata(pdev); + + clk_disable_unprepare(idev->i2c_clk); + i2c_del_adapter(&idev->adapter); + + return 0; +} + +static const struct of_device_id mchp_corei2c_of_match[] = { + { .compatible = "microchip,mpfs-i2c" }, + { .compatible = "microchip,corei2c-rtl-v7" }, + {}, +}; +MODULE_DEVICE_TABLE(of, mchp_corei2c_of_match); + +static struct platform_driver mchp_corei2c_driver = { + .probe = mchp_corei2c_probe, + .remove = mchp_corei2c_remove, + .driver = { + .name = "microchip-corei2c", + .of_match_table = mchp_corei2c_of_match, + }, +}; + +module_platform_driver(mchp_corei2c_driver); + +MODULE_DESCRIPTION("Microchip CoreI2C bus driver"); +MODULE_AUTHOR("Daire McNamara <daire.mcnamara@microchip.com>"); +MODULE_AUTHOR("Conor Dooley <conor.dooley@microchip.com>"); +MODULE_LICENSE("GPL v2");