Skip to content

Commit

Permalink
i2c: phytium: Add i2c driver for Phytium I2C adapter
Browse files Browse the repository at this point in the history
Add a driver for Phytium I2C controller with SMBus alert interrupt support.

Signed-off-by: Cheng Quan <[email protected]>
Signed-off-by: Feng Jun <[email protected]>
Signed-off-by: Chen Baozi <[email protected]>
Signed-off-by: Wang Yinfeng <[email protected]>
Signed-off-by: Jiakun Shuai <[email protected]>
  • Loading branch information
JiakunShuai authored and Avenger-285714 committed May 13, 2024
1 parent 9437fe7 commit 71507aa
Show file tree
Hide file tree
Showing 8 changed files with 1,955 additions and 0 deletions.
28 changes: 28 additions & 0 deletions drivers/i2c/busses/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,34 @@ config I2C_PCA_PLATFORM
This driver can also be built as a module. If so, the module
will be called i2c-pca-platform.

config I2C_PHYTIUM_CORE
tristate

config I2C_PHYTIUM_PCI
tristate "Phytium I2C PCI"
depends on PCI && ARCH_PHYTIUM
select I2C_PHYTIUM_CORE
select I2C_SMBUS
help
If you say yes to this option, support will be included for the
Phytium I2C adapter. Only master mode is supported.

This driver can also be built as a module. If so, the module
will be called i2c-phytium-pci.

config I2C_PHYTIUM_PLATFORM
tristate "Phytium I2C Platform"
depends on (ACPI && COMMON_CLK) || !ACPI
select I2C_SLAVE
select I2C_PHYTIUM_CORE
select I2C_SMBUS
help
If you say yes to this option, support will be included for the
Phytium I2C adapter. Only master mode is supported.

This driver can also be built as a module. If so, the module
will be called i2c-phytium-platform.

config I2C_PNX
tristate "I2C bus support for Philips PNX and NXP LPC targets"
depends on ARCH_LPC32XX || COMPILE_TEST
Expand Down
4 changes: 4 additions & 0 deletions drivers/i2c/busses/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ obj-$(CONFIG_I2C_OWL) += i2c-owl.o
obj-$(CONFIG_I2C_PASEMI) += i2c-pasemi-core.o i2c-pasemi-pci.o
obj-$(CONFIG_I2C_APPLE) += i2c-pasemi-core.o i2c-pasemi-platform.o
obj-$(CONFIG_I2C_PCA_PLATFORM) += i2c-pca-platform.o
obj-$(CONFIG_I2C_PHYTIUM_CORE) += i2c-phytium-core.o
i2c-phytium-core-objs := i2c-phytium-common.o i2c-phytium-master.o i2c-phytium-slave.o
obj-$(CONFIG_I2C_PHYTIUM_PCI) += i2c-phytium-pci.o
obj-$(CONFIG_I2C_PHYTIUM_PLATFORM) += i2c-phytium-platform.o
obj-$(CONFIG_I2C_PNX) += i2c-pnx.o
obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
Expand Down
206 changes: 206 additions & 0 deletions drivers/i2c/busses/i2c-phytium-common.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Phytium I2C adapter driver.
*
* Derived from Synopysys I2C driver.
* Copyright (C) 2006 Texas Instruments.
* Copyright (C) 2007 MontaVista Software Inc.
* Copyright (C) 2009 Provigent Ltd.
*
* Copyright (C) 2021-2023, Phytium Technology Co., Ltd.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/swab.h>

#include "i2c-phytium-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",
};

u32 phytium_readl(struct phytium_i2c_dev *dev, int offset)
{
return readl_relaxed(dev->base + offset);
}

void phytium_writel(struct phytium_i2c_dev *dev, u32 b, int offset)
{
writel_relaxed(b, dev->base + offset);
}

u32 i2c_phytium_scl_hcnt(u32 ic_clk, u32 tSYMBOL, u32 tf, int cond, int offset)
{
if (cond)
return (ic_clk * tSYMBOL + 500000) / 1000000 - 8 + offset;
else
return (ic_clk * (tSYMBOL + tf) + 500000) / 1000000 - 3 + offset;
}

u32 i2c_phytium_scl_lcnt(u32 ic_clk, u32 tLOW, u32 tf, int offset)
{
return ((ic_clk * (tLOW + tf) + 500000) / 1000000) - 1 + offset;
}

int i2c_phytium_set_sda_hold(struct phytium_i2c_dev *dev)
{
if (!dev->sda_hold_time) {
/* Keep previous hold time setting if no one set it */
dev->sda_hold_time = phytium_readl(dev, IC_SDA_HOLD);
}

if (!(dev->sda_hold_time & IC_SDA_HOLD_RX_MASK))
dev->sda_hold_time |= 1 << IC_SDA_HOLD_RX_SHIFT;

dev_dbg(dev->dev, "SDA Hold Time TX:RX = %d:%d\n",
dev->sda_hold_time & ~(u32)IC_SDA_HOLD_RX_MASK,
dev->sda_hold_time >> IC_SDA_HOLD_RX_SHIFT);

return 0;
}

void __i2c_phytium_disable(struct phytium_i2c_dev *dev)
{
int timeout = 100;

do {
__i2c_phytium_disable_nowait(dev);
if ((phytium_readl(dev, IC_ENABLE_STATUS) & 1) == 0)
return;

/*
* Wait 10 times the signaling period of the highest I2C
* transfer supported by the driver (for 400KHz this is
* 25us).
*/
usleep_range(25, 250);
} while (timeout--);

dev_warn(dev->dev, "timeout in disabling adapter\n");
}

unsigned long i2c_phytium_clk_rate(struct phytium_i2c_dev *dev)
{
if (WARN_ON_ONCE(!dev->get_clk_rate_khz))
return 0;
return dev->get_clk_rate_khz(dev);
}

int i2c_phytium_prepare_clk(struct phytium_i2c_dev *dev, bool prepare)
{
if (IS_ERR(dev->clk))
return PTR_ERR(dev->clk);

if (prepare)
return clk_prepare_enable(dev->clk);

clk_disable_unprepare(dev->clk);
return 0;
}
EXPORT_SYMBOL_GPL(i2c_phytium_prepare_clk);

int i2c_phytium_wait_bus_not_busy(struct phytium_i2c_dev *dev)
{
int timeout = 20; /* 20 ms */

while (phytium_readl(dev, IC_STATUS) & IC_STATUS_ACTIVITY) {
if (timeout <= 0) {
dev_warn(dev->dev, "timeout waiting for bus ready\n");
i2c_recover_bus(&dev->adapter);

if (phytium_readl(dev, IC_STATUS) & IC_STATUS_ACTIVITY)
return -ETIMEDOUT;
return 0;
}
timeout--;
usleep_range(1000, 1100);
}

return 0;
}

int i2c_phytium_handle_tx_abort(struct phytium_i2c_dev *dev)
{
unsigned long abort_source = dev->abort_source;
int i;

if (abort_source & 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 & IC_TX_ARB_LOST)
return -EAGAIN;
else if (abort_source & IC_TX_ABRT_GCALL_READ)
return -EINVAL;
else
return -EIO;

return 0;
}

u32 i2c_phytium_func(struct i2c_adapter *adapter)
{
struct phytium_i2c_dev *dev = i2c_get_adapdata(adapter);

return dev->functionality;
}

void i2c_phytium_disable(struct phytium_i2c_dev *dev)
{
/* Disable controller */
__i2c_phytium_disable(dev);

/* Disable all interrupts */
phytium_writel(dev, 0, IC_INTR_MASK);
phytium_readl(dev, IC_CLR_INTR);
}

void i2c_phytium_disable_int(struct phytium_i2c_dev *dev)
{
phytium_writel(dev, 0, IC_INTR_MASK);
}

MODULE_AUTHOR("Cheng Quan <[email protected]>");
MODULE_DESCRIPTION("Phytium I2C bus adapter core");
MODULE_LICENSE("GPL");
Loading

0 comments on commit 71507aa

Please sign in to comment.