Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Conor Dooley | 2030 | 99.85% | 2 | 50.00% |
Uwe Kleine-König | 2 | 0.10% | 1 | 25.00% |
Zhu Wang | 1 | 0.05% | 1 | 25.00% |
Total | 2033 | 4 |
// 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/interrupt.h> #include <linux/io.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> #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 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_STATUS (0x04) #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) /** * struct mchp_corei2c_dev - Microchip CoreI2C device private data * * @base: pointer to register struct * @dev: device reference * @i2c_clk: clock reference for i2c input clock * @buf: pointer to msg buffer for easier use * @msg_complete: xfer completion object * @adapter: core i2c abstraction * @msg_err: error code for completed message * @bus_clk_rate: current i2c bus clock rate * @isr_status: cached copy of local ISR status * @msg_len: number of bytes transferred in msg * @addr: address of the current slave */ struct mchp_corei2c_dev { void __iomem *base; struct device *dev; struct clk *i2c_clk; u8 *buf; struct completion msg_complete; struct i2c_adapter adapter; int msg_err; u32 bus_clk_rate; u32 isr_status; u16 msg_len; 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_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 <= 1) { 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) { if (idev->msg_len > 0) writeb(*idev->buf++, idev->base + CORE_I2C_DATA); idev->msg_len--; return 0; } static irqreturn_t mchp_corei2c_handle_isr(struct mchp_corei2c_dev *idev) { u32 status = idev->isr_status; u8 ctrl; bool last_byte = false, finished = false; 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) finished = true; break; case STATUS_M_ARB_LOST: idev->msg_err = -EAGAIN; finished = true; break; case STATUS_M_SLAW_ACK: case STATUS_M_TX_DATA_ACK: if (idev->msg_len > 0) mchp_corei2c_fill_tx(idev); else last_byte = true; break; case STATUS_M_TX_DATA_NACK: case STATUS_M_SLAR_NACK: case STATUS_M_SLAW_NACK: idev->msg_err = -ENXIO; last_byte = true; break; 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) last_byte = true; 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) last_byte = true; break; default: break; } /* On the last byte to be transmitted, send STOP */ if (last_byte) mchp_corei2c_stop(idev); if (last_byte || 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); } 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; idev->addr = i2c_8bit_addr_from_msg(msg); idev->msg_len = msg->len; idev->buf = msg->buf; idev->msg_err = 0; 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, idev->adapter.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; struct resource *res; int irq, ret; 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 irq; 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); ret = device_property_read_u32(idev->dev, "clock-frequency", &idev->bus_clk_rate); if (ret || !idev->bus_clk_rate) { 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); /* * This driver supports both the hard peripherals & soft FPGA cores. * The hard peripherals do not have shared IRQs, but we don't have * control over what way the interrupts are wired for the soft cores. */ 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 at %08lx", (unsigned long)res->start); 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; idev->adapter.timeout = HZ; 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, "registered CoreI2C bus driver\n"); return 0; } static void 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); } 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_new = 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");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1