I2C driver support for Phytium Desktop CPUs

I2C driver function fix patch. Support Phytium desktop and embedded processors, such as D2000 and E2000.

Reviewed-by: Hongbo Mao <maohongbo@phytium.com.cn>
(cherry picked from commit 5ec3d87d62)
Signed-off-by: Alex Shi <alexsshi@tencent.com>
This commit is contained in:
Jiakun Shuai 2023-03-30 15:47:37 +08:00 committed by Jianping Liu
parent 22599f9f53
commit 80ed68fc2e
10 changed files with 1954 additions and 0 deletions

View File

@ -0,0 +1,24 @@
* Phytium I2C/SMBus controller
Required properties :
- compatible : should be "phytium,i2c"
- reg : Offset and length of the register set for the device
- interrupts : <IRQ> where IRQ is the interrupt number.
- clock-frequency : desired I2C bus clock frequency in Hz.
Optional properties:
- interrupt-names: should be "smbus_alert" if SMBus alert
interrupt is supported.
Examples :
i2c0: i2c@28011000 {
compatible = "phytium,i2c";
reg = <0x0 0x28011000 0x0 0x1000>;
interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "smbus_alert";
clocks = <&sysclk_48mhz>;
};

View File

@ -569,6 +569,34 @@ config I2C_DESIGNWARE_BAYTRAIL
the platform firmware controlling it. You should say Y if running on
a BayTrail system using the AXP288.
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_DIGICOLOR
tristate "Conexant Digicolor I2C driver"
depends on ARCH_DIGICOLOR

View File

@ -59,6 +59,10 @@ i2c-designware-platform-$(CONFIG_I2C_DESIGNWARE_BAYTRAIL) += i2c-designware-bayt
obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o
i2c-designware-pci-objs := i2c-designware-pcidrv.o
obj-$(CONFIG_I2C_DIGICOLOR) += i2c-digicolor.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_EFM32) += i2c-efm32.o
obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
obj-$(CONFIG_I2C_EMEV2) += i2c-emev2.o

View File

@ -130,6 +130,7 @@ static const struct acpi_device_id dw_i2c_acpi_match[] = {
{ "APMC0D0F", 0 },
{ "HISI02A1", 0 },
{ "HISI02A2", 0 },
{ "PHYT0003", 0 },
{ }
};
MODULE_DEVICE_TABLE(acpi, dw_i2c_acpi_match);

View File

@ -0,0 +1,203 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Phytium I2C adapter driver.
*
* Based on the TI DAVINCI I2C adapter driver.
*
* Copyright (C) 2021,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 acknowledgement 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 interupts */
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 <chengquan@phytium.com.cn>");
MODULE_DESCRIPTION("Phytium I2C bus adapter core");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,254 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Phytium I2C adapter driver.
*
* Copyright (C) 2021, Phytium Technology Co.,Ltd.
*/
#include <linux/i2c.h>
#include <linux/pm_qos.h>
#include <linux/i2c-smbus.h>
#define 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 IC_CON_MASTER 0x1
#define IC_CON_SPEED_STD 0x2
#define IC_CON_SPEED_FAST 0x4
#define IC_CON_SPEED_HIGH 0x6
#define IC_CON_SPEED_MASK 0x6
#define IC_CON_10BITADDR_SLAVE 0x8
#define IC_CON_10BITADDR_MASTER 0x10
#define IC_CON_RESTART_EN 0x20
#define IC_CON_SLAVE_DISABLE 0x40
#define IC_CON_STOP_DET_IFADDRESSED 0x80
#define IC_CON_TX_EMPTY_CTRL 0x100
#define IC_CON_RX_FIFO_FULL_HLD_CTRL 0x200
#define IC_CON 0x0
#define IC_TAR 0x4
#define IC_SAR 0x8
#define IC_DATA_CMD 0x10
#define IC_SS_SCL_HCNT 0x14
#define IC_SS_SCL_LCNT 0x18
#define IC_FS_SCL_HCNT 0x1c
#define IC_FS_SCL_LCNT 0x20
#define IC_HS_SCL_HCNT 0x24
#define IC_HS_SCL_LCNT 0x28
#define IC_INTR_STAT 0x2c
#define IC_INTR_MASK 0x30
#define IC_RAW_INTR_STAT 0x34
#define IC_RX_TL 0x38
#define IC_TX_TL 0x3c
#define IC_CLR_INTR 0x40
#define IC_CLR_RX_UNDER 0x44
#define IC_CLR_RX_OVER 0x48
#define IC_CLR_TX_OVER 0x4c
#define IC_CLR_RD_REQ 0x50
#define IC_CLR_TX_ABRT 0x54
#define IC_CLR_RX_DONE 0x58
#define IC_CLR_ACTIVITY 0x5c
#define IC_CLR_STOP_DET 0x60
#define IC_CLR_START_DET 0x64
#define IC_CLR_GEN_CALL 0x68
#define IC_ENABLE 0x6c
#define IC_STATUS 0x70
#define IC_TXFLR 0x74
#define IC_RXFLR 0x78
#define IC_SDA_HOLD 0x7c
#define IC_TX_ABRT_SOURCE 0x80
#define IC_ENABLE_STATUS 0x9c
#define IC_SMBCLK_LOW_MEXT 0xa8
#define IC_SMBCLK_LOW_TIMEOUT 0xac
#define IC_SMBDAT_STUCK_TIMEOUT 0xb4
#define IC_CLR_SMBCLK_EXT_LOW_TIMEOUT 0xbc
#define IC_CLR_SMBCLK_TMO_LOW_TIMEOUT 0xc0
#define IC_CLR_SMBDAT_LOW_TIMEOUT 0xc4
#define IC_CLR_SMBALERT_IN_N 0xd0
#define IC_INTR_RX_UNDER 0x001
#define IC_INTR_RX_OVER 0x002
#define IC_INTR_RX_FULL 0x004
#define IC_INTR_TX_OVER 0x008
#define IC_INTR_TX_EMPTY 0x010
#define IC_INTR_RD_REQ 0x020
#define IC_INTR_TX_ABRT 0x040
#define IC_INTR_RX_DONE 0x080
#define IC_INTR_ACTIVITY 0x100
#define IC_INTR_STOP_DET 0x200
#define IC_INTR_START_DET 0x400
#define IC_INTR_GEN_CALL 0x800
#define IC_INTR_SMBCLK_EXT_LOW_TIMEOUT 0x1000
#define IC_INTR_SMBCLK_TMO_LOW_TIMEOUT 0x2000
#define IC_INTR_SMBSDA_LOW_TIMEOUT 0x4000
#define IC_INTR_SMBALERT_IN_N 0x20000
#define IC_INTR_DEFAULT_MASK (IC_INTR_RX_FULL | \
IC_INTR_TX_ABRT | \
IC_INTR_STOP_DET)
#define IC_INTR_MASTER_MASK (IC_INTR_DEFAULT_MASK | \
IC_INTR_TX_EMPTY)
#define IC_INTR_SLAVE_MASK (IC_INTR_DEFAULT_MASK | \
IC_INTR_RX_DONE | \
IC_INTR_RX_UNDER | \
IC_INTR_RD_REQ)
#define IC_INTR_SMBUS_MASK (IC_INTR_MASTER_MASK | \
IC_INTR_SMBCLK_EXT_LOW_TIMEOUT | \
IC_INTR_SMBCLK_TMO_LOW_TIMEOUT | \
IC_INTR_SMBSDA_LOW_TIMEOUT)
#define IC_STATUS_ACTIVITY 0x1
#define IC_STATUS_TFE BIT(2)
#define IC_STATUS_MASTER_ACTIVITY BIT(5)
#define IC_STATUS_SLAVE_ACTIVITY BIT(6)
#define IC_SDA_HOLD_RX_SHIFT 16
#define IC_SDA_HOLD_RX_MASK GENMASK(23, IC_SDA_HOLD_RX_SHIFT)
#define IC_ERR_TX_ABRT 0x1
#define IC_TAR_10BITADDR_MASTER BIT(12)
#define IC_COMP_PARAM_1_SPEED_MODE_HIGH (BIT(2) | BIT(3))
#define IC_COMP_PARAM_1_SPEED_MODE_MASK GENMASK(3, 2)
#define STATUS_IDLE 0x0
#define STATUS_WRITE_IN_PROGRESS 0x1
#define STATUS_READ_IN_PROGRESS 0x2
/*
* operation modes
*/
#define PHYTIUM_IC_MASTER 0
#define PHYTIUM_IC_SLAVE 1
#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_SBYTE_ACKDET 7
#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 IC_TX_ABRT_7B_ADDR_NOACK (1UL << ABRT_7B_ADDR_NOACK)
#define IC_TX_ABRT_10ADDR1_NOACK (1UL << ABRT_10ADDR1_NOACK)
#define IC_TX_ABRT_10ADDR2_NOACK (1UL << ABRT_10ADDR2_NOACK)
#define IC_TX_ABRT_TXDATA_NOACK (1UL << ABRT_TXDATA_NOACK)
#define IC_TX_ABRT_GCALL_NOACK (1UL << ABRT_GCALL_NOACK)
#define IC_TX_ABRT_GCALL_READ (1UL << ABRT_GCALL_READ)
#define IC_TX_ABRT_SBYTE_ACKDET (1UL << ABRT_SBYTE_ACKDET)
#define IC_TX_ABRT_SBYTE_NORSTRT (1UL << ABRT_SBYTE_NORSTRT)
#define IC_TX_ABRT_10B_RD_NORSTRT (1UL << ABRT_10B_RD_NORSTRT)
#define IC_TX_ABRT_MASTER_DIS (1UL << ABRT_MASTER_DIS)
#define IC_TX_ARB_LOST (1UL << ARB_LOST)
#define IC_RX_ABRT_SLAVE_RD_INTX (1UL << ABRT_SLAVE_RD_INTX)
#define IC_RX_ABRT_SLAVE_ARBLOST (1UL << ABRT_SLAVE_ARBLOST)
#define IC_RX_ABRT_SLAVE_FLUSH_TXFIFO (1UL << ABRT_SLAVE_FLUSH_TXFIFO)
#define IC_TX_ABRT_NOACK (IC_TX_ABRT_7B_ADDR_NOACK | \
IC_TX_ABRT_10ADDR1_NOACK | \
IC_TX_ABRT_10ADDR2_NOACK | \
IC_TX_ABRT_TXDATA_NOACK | \
IC_TX_ABRT_GCALL_NOACK)
#define CONTROLLER_TYPE_IIC 0
#define CONTROLLER_TYPE_SMBUS 1
struct phytium_i2c_dev {
struct device *dev;
void __iomem *base;
int irq;
u32 flags;
struct completion cmd_complete;
struct clk *clk;
struct reset_control *rst;
int mode;
struct i2c_client *slave;
u32 (*get_clk_rate_khz)(struct phytium_i2c_dev *dev);
struct i2c_adapter adapter;
struct i2c_client *ara;
struct i2c_smbus_alert_setup alert_data;
struct phytium_pci_i2c *controller;
unsigned int status;
int cmd_err;
u32 abort_source;
struct i2c_msg *msgs;
int msgs_num;
int msg_write_idx;
int msg_read_idx;
int msg_err;
u32 tx_buf_len;
u8 *tx_buf;
u32 rx_buf_len;
u8 *rx_buf;
u32 master_cfg;
u32 slave_cfg;
u32 functionality;
unsigned int tx_fifo_depth;
unsigned int rx_fifo_depth;
int rx_outstanding;
struct i2c_timings timings;
u32 sda_hold_time;
u16 ss_hcnt;
u16 ss_lcnt;
u16 fs_hcnt;
u16 fs_lcnt;
u16 fp_hcnt;
u16 fp_lcnt;
u16 hs_hcnt;
u16 hs_lcnt;
bool pm_disabled;
void (*disable)(struct phytium_i2c_dev *dev);
void (*disable_int)(struct phytium_i2c_dev *dev);
int (*init)(struct phytium_i2c_dev *dev);
};
#define ACCESS_INTR_MASK 0x00000004
#define DEFAULT_CLOCK_FREQUENCY 48000000
u32 phytium_readl(struct phytium_i2c_dev *dev, int offset);
void phytium_writel(struct phytium_i2c_dev *dev, u32 b, int offset);
unsigned long i2c_phytium_clk_rate(struct phytium_i2c_dev *dev);
int i2c_phytium_prepare_clk(struct phytium_i2c_dev *dev, bool prepare);
int i2c_phytium_wait_bus_not_busy(struct phytium_i2c_dev *dev);
int i2c_phytium_handle_tx_abort(struct phytium_i2c_dev *dev);
u32 i2c_phytium_func(struct i2c_adapter *adap);
void i2c_phytium_disable(struct phytium_i2c_dev *dev);
void i2c_phytium_disable_int(struct phytium_i2c_dev *dev);
int i2c_phytium_set_sda_hold(struct phytium_i2c_dev *dev);
u32 i2c_phytium_scl_hcnt(u32 ic_clk, u32 tSYMBOL, u32 tf, int cond, int offset);
u32 i2c_phytium_scl_lcnt(u32 ic_clk, u32 tLOW, u32 tf, int offset);
static inline void __i2c_phytium_enable(struct phytium_i2c_dev *dev)
{
phytium_writel(dev, 1, IC_ENABLE);
}
static inline void __i2c_phytium_disable_nowait(struct phytium_i2c_dev *dev)
{
phytium_writel(dev, 0, IC_ENABLE);
}
void __i2c_phytium_disable(struct phytium_i2c_dev *dev);
extern int i2c_phytium_probe(struct phytium_i2c_dev *dev);
extern int i2c_phytium_probe_slave(struct phytium_i2c_dev *dev);

View File

@ -0,0 +1,577 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Phytium I2C adapter driver.
*
* Copyright (C) 2021, Phytium Technology Co., Ltd.
*/
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include "i2c-phytium-core.h"
static int i2c_phytium_init_master(struct phytium_i2c_dev *dev)
{
/* Disable the adapter */
__i2c_phytium_disable(dev);
/* Write standard speed timing parameters */
phytium_writel(dev, dev->ss_hcnt, IC_SS_SCL_HCNT);
phytium_writel(dev, dev->ss_lcnt, IC_SS_SCL_LCNT);
/* Write fast mode/fast mode plus timing parameters */
phytium_writel(dev, dev->fs_hcnt, IC_FS_SCL_HCNT);
phytium_writel(dev, dev->fs_lcnt, IC_FS_SCL_LCNT);
/* Write high speed timing parameters if supported */
if (dev->hs_hcnt && dev->hs_hcnt) {
phytium_writel(dev, dev->hs_hcnt, IC_HS_SCL_HCNT);
phytium_writel(dev, dev->hs_lcnt, IC_HS_SCL_LCNT);
}
/* Write SDA hold time if supported */
if (dev->sda_hold_time)
phytium_writel(dev, dev->sda_hold_time, IC_SDA_HOLD);
/* Configure Tx/Rx FIFO threshold levels */
phytium_writel(dev, dev->tx_fifo_depth >> 1, IC_TX_TL);
phytium_writel(dev, 0, IC_RX_TL);
/* Configure the I2C master */
phytium_writel(dev, dev->master_cfg, IC_CON);
return 0;
}
static void i2c_phytium_xfer_init(struct phytium_i2c_dev *dev)
{
struct i2c_msg *msgs = dev->msgs;
u32 ic_con, ic_tar = 0;
/* Disable the adapter */
__i2c_phytium_disable(dev);
/* If the slave address is 10-bit address, enable 10BITADDR */
ic_con = phytium_readl(dev, IC_CON);
if (msgs[dev->msg_write_idx].flags & I2C_M_TEN) {
ic_con |= IC_CON_10BITADDR_MASTER;
ic_tar = IC_TAR_10BITADDR_MASTER;
} else {
ic_con &= ~IC_CON_10BITADDR_MASTER;
}
phytium_writel(dev, ic_con, IC_CON);
/*
* Set the slave (target) address and enable 10-bit addressing mode
* if applicable.
*/
phytium_writel(dev, msgs[dev->msg_write_idx].addr | ic_tar, IC_TAR);
/* Enforce disabled interrupts */
i2c_phytium_disable_int(dev);
/* Enable the adapter */
__i2c_phytium_enable(dev);
/* Dummy read */
phytium_readl(dev, IC_ENABLE_STATUS);
/* Clear and enable interrupts */
phytium_readl(dev, IC_CLR_INTR);
phytium_writel(dev, IC_INTR_SMBUS_MASK, IC_INTR_MASK);
}
static void i2c_phytium_xfer_msg(struct phytium_i2c_dev *dev)
{
struct i2c_msg *msgs = dev->msgs;
u32 intr_mask;
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;
intr_mask = 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)) {
/* new i2c_msg */
buf = msgs[dev->msg_write_idx].buf;
buf_len = msgs[dev->msg_write_idx].len;
if ((dev->master_cfg & IC_CON_RESTART_EN) &&
(dev->msg_write_idx > 0))
need_restart = true;
}
tx_limit = dev->tx_fifo_depth - phytium_readl(dev, IC_TXFLR);
rx_limit = dev->tx_fifo_depth - phytium_readl(dev, IC_RXFLR);
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) {
/* avoid rx buffer overrun */
if (dev->rx_outstanding >= dev->rx_fifo_depth)
break;
phytium_writel(dev, cmd | 0x100, IC_DATA_CMD);
rx_limit--;
dev->rx_outstanding++;
} else {
phytium_writel(dev, cmd | *buf++, IC_DATA_CMD);
}
tx_limit--;
buf_len--;
}
dev->tx_buf = buf;
dev->tx_buf_len = buf_len;
/*
* Because we don't know the buffer length in the
* I2C_FUNC_SMBUS_BLOCK_DATA case, we can't stop
* the transaction here.
*/
if (buf_len > 0 || flags & I2C_M_RECV_LEN) {
/* more bytes to be written */
dev->status |= STATUS_WRITE_IN_PROGRESS;
break;
} else {
dev->status &= ~STATUS_WRITE_IN_PROGRESS;
}
}
if (dev->msg_write_idx == dev->msgs_num)
intr_mask &= ~IC_INTR_TX_EMPTY;
if (dev->msg_err)
intr_mask = 0;
phytium_writel(dev, intr_mask, IC_INTR_MASK);
}
static u8 i2c_phytium_recv_len(struct phytium_i2c_dev *dev, u8 len)
{
struct i2c_msg *msgs = dev->msgs;
u32 flags = msgs[dev->msg_read_idx].flags;
/*
* Adjust the buffer length and mask the flag
* after receiving the first byte.
*/
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;
return len;
}
static void i2c_phytium_read(struct phytium_i2c_dev *dev)
{
struct i2c_msg *msgs = dev->msgs;
int rx_valid;
for (; dev->msg_read_idx < dev->msgs_num; dev->msg_read_idx++) {
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;
}
rx_valid = phytium_readl(dev, IC_RXFLR);
for (; len > 0 && rx_valid > 0; len--, rx_valid--) {
u32 flags = msgs[dev->msg_read_idx].flags;
*buf = phytium_readl(dev, IC_DATA_CMD);
/* Ensure length byte is a valid value */
if (flags & I2C_M_RECV_LEN &&
*buf <= I2C_SMBUS_BLOCK_MAX && *buf > 0) {
len = i2c_phytium_recv_len(dev, *buf);
}
buf++;
dev->rx_outstanding--;
}
if (len > 0) {
dev->status |= STATUS_READ_IN_PROGRESS;
dev->rx_buf_len = len;
dev->rx_buf = buf;
return;
} else
dev->status &= ~STATUS_READ_IN_PROGRESS;
}
}
static int i2c_phytium_xfer(struct i2c_adapter *adapter, struct i2c_msg msgs[], int num)
{
struct phytium_i2c_dev *dev = i2c_get_adapdata(adapter);
int ret;
dev_dbg(dev->dev, "%s: msgs: %d\n", __func__, num);
pm_runtime_get_sync(dev->dev);
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 = STATUS_IDLE;
dev->abort_source = 0;
dev->rx_outstanding = 0;
ret = i2c_phytium_wait_bus_not_busy(dev);
if (ret < 0)
goto done;
/* Start the transfers */
i2c_phytium_xfer_init(dev);
/* Wait for tx to complete */
if (!wait_for_completion_timeout(&dev->cmd_complete, adapter->timeout)) {
dev_err(dev->dev, "controller timed out\n");
i2c_recover_bus(&dev->adapter);
i2c_phytium_init_master(dev);
ret = -ETIMEDOUT;
goto done;
}
__i2c_phytium_disable_nowait(dev);
if (dev->msg_err) {
ret = dev->msg_err;
goto done;
}
if (likely(!dev->cmd_err && !dev->status)) {
ret = num;
goto done;
}
/* We have got an error */
if (dev->cmd_err == IC_ERR_TX_ABRT) {
ret = i2c_phytium_handle_tx_abort(dev);
goto done;
}
if (dev->status)
dev_err(dev->dev, "transfer terminated early.\n");
ret = -EIO;
done:
pm_runtime_mark_last_busy(dev->dev);
pm_runtime_put_autosuspend(dev->dev);
return ret;
}
static const struct i2c_algorithm i2c_phytium_algo = {
.master_xfer = i2c_phytium_xfer,
.functionality = i2c_phytium_func,
};
static const struct i2c_adapter_quirks i2c_phytium_quirks = {
.flags = I2C_AQ_NO_ZERO_LEN,
};
static u32 i2c_phytium_read_clear_intrbits(struct phytium_i2c_dev *dev)
{
u32 stat;
stat = phytium_readl(dev, IC_INTR_STAT);
if (stat & IC_INTR_RX_UNDER)
phytium_readl(dev, IC_CLR_RX_UNDER);
if (stat & IC_INTR_RX_OVER)
phytium_readl(dev, IC_CLR_RX_OVER);
if (stat & IC_INTR_TX_OVER)
phytium_readl(dev, IC_CLR_TX_OVER);
if (stat & IC_INTR_RD_REQ)
phytium_readl(dev, IC_CLR_RD_REQ);
if (stat & IC_INTR_TX_ABRT) {
dev->abort_source = phytium_readl(dev, IC_TX_ABRT_SOURCE);
phytium_readl(dev, IC_CLR_TX_ABRT);
}
if (stat & IC_INTR_RX_DONE)
phytium_readl(dev, IC_CLR_RX_DONE);
if (stat & IC_INTR_ACTIVITY)
phytium_readl(dev, IC_CLR_ACTIVITY);
if (stat & IC_INTR_STOP_DET)
phytium_readl(dev, IC_CLR_STOP_DET);
if (stat & IC_INTR_START_DET)
phytium_readl(dev, IC_CLR_START_DET);
if (stat & IC_INTR_GEN_CALL)
phytium_readl(dev, IC_CLR_GEN_CALL);
if (stat & IC_INTR_SMBCLK_EXT_LOW_TIMEOUT)
phytium_readl(dev, IC_CLR_SMBCLK_EXT_LOW_TIMEOUT);
if (stat & IC_INTR_SMBCLK_TMO_LOW_TIMEOUT)
phytium_readl(dev, IC_CLR_SMBCLK_TMO_LOW_TIMEOUT);
if (stat & IC_INTR_SMBSDA_LOW_TIMEOUT)
phytium_readl(dev, IC_CLR_SMBDAT_LOW_TIMEOUT);
if (stat & IC_INTR_SMBALERT_IN_N)
phytium_readl(dev, IC_CLR_SMBALERT_IN_N);
return stat;
}
static int i2c_phytium_irq_handler_master(struct phytium_i2c_dev *dev)
{
u32 stat;
stat = i2c_phytium_read_clear_intrbits(dev);
/* SMBus interrupt */
if (stat & (IC_INTR_SMBCLK_EXT_LOW_TIMEOUT | IC_INTR_SMBCLK_TMO_LOW_TIMEOUT)) {
phytium_writel(dev, phytium_readl(dev, IC_ENABLE) & (~BIT(6)),
IC_ENABLE);
phytium_writel(dev, phytium_readl(dev, IC_ENABLE) | BIT(4),
IC_ENABLE);
goto abort;
}
if (stat & IC_INTR_SMBSDA_LOW_TIMEOUT) {
phytium_writel(dev, phytium_readl(dev, IC_ENABLE) | BIT(6),
IC_ENABLE);
goto abort;
}
if (stat & IC_INTR_SMBALERT_IN_N && dev->ara)
i2c_handle_smbus_alert(dev->ara);
if (stat & IC_INTR_TX_ABRT) {
dev->cmd_err |= IC_ERR_TX_ABRT;
dev->status = STATUS_IDLE;
/* Anytime TX_ABRT is set, the contents of the tx/rx
* buffers are flushed. Make sure to skip them.
*/
phytium_writel(dev, 0, IC_INTR_MASK);
goto abort;
}
if (stat & IC_INTR_RX_FULL)
i2c_phytium_read(dev);
if (stat & IC_INTR_TX_EMPTY)
i2c_phytium_xfer_msg(dev);
abort:
if ((stat & (IC_INTR_TX_ABRT | IC_INTR_STOP_DET)) ||
dev->msg_err)
complete(&dev->cmd_complete);
else if (unlikely(dev->flags & ACCESS_INTR_MASK)) {
/* Workaround to trigger pending interrupt */
stat = phytium_readl(dev, IC_INTR_MASK);
i2c_phytium_disable_int(dev);
phytium_writel(dev, stat, IC_INTR_MASK);
}
return 0;
}
static int i2c_phytium_set_timings_master(struct phytium_i2c_dev *dev)
{
const char *mode_str, *fp_str = "";
u32 sda_falling_time, scl_falling_time;
struct i2c_timings *t = &dev->timings;
u32 ic_clk;
int ret;
/* Set standard and fast speed dividers for high/low periods */
sda_falling_time = t->sda_fall_ns ?: 300; /* ns */
scl_falling_time = t->scl_fall_ns ?: 300; /* ns */
/* Calculate SCL timing parameters for standard mode if not set */
if (!dev->ss_hcnt || !dev->ss_lcnt) {
ic_clk = i2c_phytium_clk_rate(dev);
dev->ss_hcnt =
i2c_phytium_scl_hcnt(ic_clk,
4000, /* tHD;STA = tHIGH = 4.0 us */
sda_falling_time,
0, /* 0: DW default, 1: Ideal */
0); /* No offset */
dev->ss_lcnt =
i2c_phytium_scl_lcnt(ic_clk,
4700, /* tLOW = 4.7 us */
scl_falling_time,
0); /* No offset */
}
dev_dbg(dev->dev, "Standard Mode HCNT:LCNT = %d:%d\n",
dev->ss_hcnt, dev->ss_lcnt);
/*
* Set SCL timing parameters for fast mode or fast mode plus. Only
* difference is the timing parameter values since the registers are
* the same.
*/
if (t->bus_freq_hz == 1000000) {
/*
* Check are fast mode plus parameters available and use
* fast mode if not.
*/
if (dev->fp_hcnt && dev->fp_lcnt) {
dev->fs_hcnt = dev->fp_hcnt;
dev->fs_lcnt = dev->fp_lcnt;
fp_str = " Plus";
}
}
/*
* Calculate SCL timing parameters for fast mode if not set. They are
* needed also in high speed mode.
*/
if (!dev->fs_hcnt || !dev->fs_lcnt) {
ic_clk = i2c_phytium_clk_rate(dev);
dev->fs_hcnt =
i2c_phytium_scl_hcnt(ic_clk,
600, /* tHD;STA = tHIGH = 0.6 us */
sda_falling_time,
0, /* 0: DW default, 1: Ideal */
0); /* No offset */
dev->fs_lcnt =
i2c_phytium_scl_lcnt(ic_clk,
1300, /* tLOW = 1.3 us */
scl_falling_time,
0); /* No offset */
}
dev_dbg(dev->dev, "Fast Mode%s HCNT:LCNT = %d:%d\n",
fp_str, dev->fs_hcnt, dev->fs_lcnt);
if (dev->hs_hcnt && dev->hs_lcnt)
dev_dbg(dev->dev, "High Speed Mode HCNT:LCNT = %d:%d\n",
dev->hs_hcnt, dev->hs_lcnt);
ret = i2c_phytium_set_sda_hold(dev);
if (ret)
goto out;
switch (dev->master_cfg & IC_CON_SPEED_MASK) {
case IC_CON_SPEED_STD:
mode_str = "Standard Mode";
break;
case IC_CON_SPEED_HIGH:
mode_str = "High Speed Mode";
break;
default:
mode_str = "Fast Mode";
}
dev_dbg(dev->dev, "Bus speed: %s%s\n", mode_str, fp_str);
out:
return ret;
}
static irqreturn_t i2c_phytium_isr(int this_irq, void *dev_id)
{
struct phytium_i2c_dev *dev = dev_id;
u32 stat, enabled;
enabled = phytium_readl(dev, IC_ENABLE);
stat = phytium_readl(dev, IC_RAW_INTR_STAT);
if (!enabled || !(stat & ~IC_INTR_ACTIVITY))
return IRQ_NONE;
i2c_phytium_irq_handler_master(dev);
return IRQ_HANDLED;
}
int i2c_phytium_probe(struct phytium_i2c_dev *dev)
{
struct i2c_adapter *adapter = &dev->adapter;
unsigned long irq_flags;
int ret;
init_completion(&dev->cmd_complete);
dev->init = i2c_phytium_init_master;
dev->disable = i2c_phytium_disable;
dev->disable_int = i2c_phytium_disable_int;
ret = i2c_phytium_set_timings_master(dev);
if (ret)
return ret;
ret = dev->init(dev);
if (ret)
return ret;
/* XXX: should be initialized in firmware, remove it in future */
#define DEFAULT_TIMEOUT (DEFAULT_CLOCK_FREQUENCY / 1000 * 35)
phytium_writel(dev, DEFAULT_TIMEOUT, IC_SMBCLK_LOW_MEXT);
phytium_writel(dev, DEFAULT_TIMEOUT, IC_SMBCLK_LOW_TIMEOUT);
phytium_writel(dev, DEFAULT_TIMEOUT, IC_SMBDAT_STUCK_TIMEOUT);
snprintf(adapter->name, sizeof(adapter->name), "Phytium I2C adapter");
adapter->retries = 3;
adapter->algo = &i2c_phytium_algo;
adapter->quirks = &i2c_phytium_quirks;
adapter->dev.parent = dev->dev;
i2c_set_adapdata(adapter, dev);
irq_flags = IRQF_SHARED | IRQF_COND_SUSPEND;
i2c_phytium_disable_int(dev);
ret = devm_request_irq(dev->dev, dev->irq, i2c_phytium_isr, irq_flags,
dev_name(dev->dev), dev);
if (ret) {
dev_err(dev->dev, "failed to request irq %i: %d\n", dev->irq, ret);
return ret;
}
/*
* Increment PM usage count during adapter registration in order to
* avoid possible spurious runtime suspend when adapter device is
* registered to the device core and immediate resume in case bus has
* registered I2C slaves that do I2C transfers in their probe.
*/
pm_runtime_get_noresume(dev->dev);
ret = i2c_add_numbered_adapter(adapter);
if (ret)
dev_err(dev->dev, "fail to add adapter: %d\n", ret);
pm_runtime_put_noidle(dev->dev);
return ret;
}
EXPORT_SYMBOL_GPL(i2c_phytium_probe);
MODULE_DESCRIPTION("Phytium I2C bus master adapter");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,237 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* PCI driver for Phytium I2C adapter.
*
* Copyright (C) 2021,Phytium Technology Co.,Ltd.
*/
#include <linux/acpi.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/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include "i2c-phytium-core.h"
#define DRV_NAME "i2c-phytium-pci"
enum phytium_pci_ctl_id_t {
octopus_i2c,
};
struct scl_sda_cfg {
u32 ss_hcnt;
u32 fs_hcnt;
u32 ss_lcnt;
u32 fs_lcnt;
u32 sda_hold;
};
struct phytium_pci_i2c {
u32 bus_num;
u32 bus_cfg;
u32 tx_fifo_depth;
u32 rx_fifo_depth;
u32 clk_khz;
u32 functionality;
u32 flags;
struct scl_sda_cfg *scl_sda_cfg;
int (*setup)(struct pci_dev *pdev, struct phytium_pci_i2c *c);
};
/* Octopus HCNT/LCNT/SDA hold time */
static struct scl_sda_cfg octopus_config = {
.ss_hcnt = 0x190,
.ss_lcnt = 0x1d6,
.fs_hcnt = 0x3c,
.fs_lcnt = 0x82,
.sda_hold = 0x0, // XXX
};
static int octopus_setup(struct pci_dev *pdev, struct phytium_pci_i2c *c)
{
struct phytium_i2c_dev *i2c = pci_get_drvdata(pdev);
if (pdev->device == 0xdc32) {
/*
* Since we have already register the adapter, the dev->irq
* must be valid.
*/
i2c->alert_data.irq = i2c->irq;
i2c->ara = i2c_setup_smbus_alert(&i2c->adapter, &i2c->alert_data);
if (!i2c->ara)
return -ENODEV;
}
return 0;
}
static struct phytium_pci_i2c pci_ctrl_info[] = {
[octopus_i2c] = {
.bus_num = -1,
.bus_cfg = IC_CON_MASTER | IC_CON_SLAVE_DISABLE |
IC_CON_RESTART_EN | IC_CON_SPEED_FAST,
.tx_fifo_depth = 7,
.rx_fifo_depth = 7,
.functionality = I2C_FUNC_10BIT_ADDR,
.clk_khz = 48000000,
.scl_sda_cfg = &octopus_config,
.setup = octopus_setup,
},
};
#ifdef CONFIG_PM
static int i2c_phytium_pci_suspend(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct phytium_i2c_dev *i_dev = pci_get_drvdata(pdev);
i_dev->disable(i_dev);
return 0;
}
static int i2c_phytium_pci_resume(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct phytium_i2c_dev *i_dev = pci_get_drvdata(pdev);
return i_dev->init(i_dev);
}
#endif
static UNIVERSAL_DEV_PM_OPS(i2c_phytium_pm_ops, i2c_phytium_pci_suspend,
i2c_phytium_pci_resume, NULL);
static u32 i2c_phytium_get_clk_rate_khz(struct phytium_i2c_dev *dev)
{
return dev->controller->clk_khz;
}
static int i2c_phytium_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct phytium_i2c_dev *dev;
struct i2c_adapter *adapter;
struct phytium_pci_i2c *controller;
struct scl_sda_cfg *cfg;
int ret;
if (id->driver_data >= ARRAY_SIZE(pci_ctrl_info)) {
dev_err(&pdev->dev, "%s: invalid driver data %ld\n", __func__,
id->driver_data);
ret = -EINVAL;
goto out;
}
controller = &pci_ctrl_info[id->driver_data];
ret = pcim_enable_device(pdev);
if (ret) {
dev_err(&pdev->dev, "Failed to enable I2C PCI device (%d)\n", ret);
goto out;
}
ret = pcim_iomap_regions(pdev, 0x1, pci_name(pdev));
if (ret) {
dev_err(&pdev->dev, "I/O memory remapping failed\n");
goto out;
}
dev = devm_kzalloc(&pdev->dev, sizeof(struct phytium_i2c_dev), GFP_KERNEL);
if (!dev) {
ret = -ENOMEM;
goto out;
}
dev->controller = controller;
dev->get_clk_rate_khz = i2c_phytium_get_clk_rate_khz;
dev->base = pcim_iomap_table(pdev)[0];
dev->dev = &pdev->dev;
dev->irq = pdev->irq;
dev->flags |= controller->flags;
dev->functionality = controller->functionality | IC_DEFAULT_FUNCTIONALITY;
dev->master_cfg = controller->bus_cfg;
if (controller->scl_sda_cfg) {
cfg = controller->scl_sda_cfg;
dev->ss_hcnt = cfg->ss_hcnt;
dev->fs_hcnt = cfg->fs_hcnt;
dev->ss_lcnt = cfg->ss_lcnt;
dev->fs_lcnt = cfg->fs_lcnt;
dev->sda_hold_time = cfg->sda_hold;
}
pci_set_drvdata(pdev, dev);
dev->tx_fifo_depth = controller->tx_fifo_depth;
dev->rx_fifo_depth = controller->rx_fifo_depth;
adapter = &dev->adapter;
adapter->owner = THIS_MODULE;
adapter->class = 0;
ACPI_COMPANION_SET(&adapter->dev, ACPI_COMPANION(&pdev->dev));
adapter->nr = controller->bus_num;
ret = i2c_phytium_probe(dev);
if (ret)
goto out;
if (controller->setup) {
ret = controller->setup(pdev, controller);
if (ret)
goto out;
}
pm_runtime_set_autosuspend_delay(&pdev->dev, 1000);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_put_autosuspend(&pdev->dev);
pm_runtime_allow(&pdev->dev);
out:
return ret;
}
static void i2c_phytium_pci_remove(struct pci_dev *pdev)
{
struct phytium_i2c_dev *dev = pci_get_drvdata(pdev);
dev->disable(dev);
pm_runtime_forbid(&pdev->dev);
pm_runtime_get_noresume(&pdev->dev);
i2c_del_adapter(&dev->adapter);
}
static const struct pci_device_id i2_phytium_pci_ids[] = {
{ PCI_VDEVICE(PHYTIUM, 0xdc32), octopus_i2c },
{ PCI_VDEVICE(PHYTIUM, 0xdc30), octopus_i2c },
{ }
};
MODULE_DEVICE_TABLE(pci, i2_phytium_pci_ids);
static struct pci_driver phytium_i2c_driver = {
.name = DRV_NAME,
.id_table = i2_phytium_pci_ids,
.probe = i2c_phytium_pci_probe,
.remove = i2c_phytium_pci_remove,
.driver = {
.pm = &i2c_phytium_pm_ops,
},
};
module_pci_driver(phytium_i2c_driver);
MODULE_ALIAS("i2c-phytium-pci");
MODULE_AUTHOR("Cheng Quan <chengquan@phytium.com.cn>");
MODULE_DESCRIPTION("Phytium PCI I2C bus adapter");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,364 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Phytium I2C adapter driver.
*
* Copyright (C) 2021, Phytium Technology Co.,Ltd.
*/
#include <linux/acpi.h>
#include <linux/clk-provider.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/dmi.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
#include <linux/reset.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/suspend.h>
#include "i2c-phytium-core.h"
#define DRV_NAME "i2c-phytium-platform"
static u32 i2c_phytium_get_clk_rate_khz(struct phytium_i2c_dev *dev)
{
return clk_get_rate(dev->clk)/1000;
}
#ifdef CONFIG_ACPI
static void phytium_i2c_acpi_params(struct platform_device *pdev, char method[],
u16 *hcnt, u16 *lcnt, u32 *sda_hold)
{
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER };
acpi_handle handle = ACPI_HANDLE(&pdev->dev);
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 == 3) {
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;
}
kfree(buf.pointer);
}
static int phytium_i2c_acpi_configure(struct platform_device *pdev)
{
struct phytium_i2c_dev *dev = platform_get_drvdata(pdev);
struct i2c_timings *t = &dev->timings;
u32 ss_ht = 0, fp_ht = 0, hs_ht = 0, fs_ht = 0;
acpi_handle handle = ACPI_HANDLE(&pdev->dev);
const struct acpi_device_id *id;
struct acpi_device *adev;
dev->adapter.nr = -1;
dev->tx_fifo_depth = 32;
dev->rx_fifo_depth = 32;
/*
* Try to get SDA hold time and *CNT values from an ACPI method for
* selected speed modes.
*/
phytium_i2c_acpi_params(pdev, "SSCN", &dev->ss_hcnt, &dev->ss_lcnt, &ss_ht);
phytium_i2c_acpi_params(pdev, "FPCN", &dev->fp_hcnt, &dev->fp_lcnt, &fp_ht);
phytium_i2c_acpi_params(pdev, "HSCN", &dev->hs_hcnt, &dev->hs_lcnt, &hs_ht);
phytium_i2c_acpi_params(pdev, "FMCN", &dev->fs_hcnt, &dev->fs_lcnt, &fs_ht);
switch (t->bus_freq_hz) {
case 100000:
dev->sda_hold_time = ss_ht;
break;
case 1000000:
dev->sda_hold_time = fp_ht;
break;
case 3400000:
dev->sda_hold_time = hs_ht;
break;
case 400000:
default:
dev->sda_hold_time = fs_ht;
break;
}
id = acpi_match_device(pdev->dev.driver->acpi_match_table, &pdev->dev);
if (id && id->driver_data)
dev->flags |= (u32)id->driver_data;
if (acpi_bus_get_device(handle, &adev))
return -ENODEV;
return 0;
}
static const struct acpi_device_id phytium_i2c_acpi_match[] = {
{ "PHYT0038", 0 },
{ }
};
MODULE_DEVICE_TABLE(acpi, phytium_i2c_acpi_match);
#else
static inline int phytium_i2c_acpi_configure(struct platform_device *pdev)
{
return -ENODEV;
}
#endif
static void i2c_phytium_configure_master(struct phytium_i2c_dev *dev)
{
struct i2c_timings *t = &dev->timings;
dev->functionality = I2C_FUNC_10BIT_ADDR | IC_DEFAULT_FUNCTIONALITY;
dev->master_cfg = IC_CON_MASTER | IC_CON_SLAVE_DISABLE |
IC_CON_RESTART_EN;
dev->mode = PHYTIUM_IC_MASTER;
switch (t->bus_freq_hz) {
case 100000:
dev->master_cfg |= IC_CON_SPEED_STD;
break;
case 3400000:
dev->master_cfg |= IC_CON_SPEED_HIGH;
break;
default:
dev->master_cfg |= IC_CON_SPEED_FAST;
}
}
static void i2c_phytium_configure_slave(struct phytium_i2c_dev *dev)
{
dev->functionality = I2C_FUNC_SLAVE | IC_DEFAULT_FUNCTIONALITY;
dev->slave_cfg = IC_CON_RX_FIFO_FULL_HLD_CTRL |
IC_CON_RESTART_EN | IC_CON_STOP_DET_IFADDRESSED;
dev->mode = PHYTIUM_IC_SLAVE;
}
static int phytium_i2c_plat_probe(struct platform_device *pdev)
{
struct i2c_adapter *adap;
struct phytium_i2c_dev *dev;
struct i2c_timings *t;
u32 acpi_speed;
struct resource *mem;
int irq, ret, i;
static const int supported_speeds[] = {
0, 100000, 400000, 1000000, 3400000
};
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
dev = devm_kzalloc(&pdev->dev, sizeof(struct phytium_i2c_dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
dev->base = devm_ioremap_resource(&pdev->dev, mem);
if (IS_ERR(dev->base))
return PTR_ERR(dev->base);
dev->dev = &pdev->dev;
dev->irq = irq;
platform_set_drvdata(pdev, dev);
dev->rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL);
if (IS_ERR(dev->rst)) {
if (PTR_ERR(dev->rst) == -EPROBE_DEFER)
return -EPROBE_DEFER;
} else {
reset_control_deassert(dev->rst);
}
t = &dev->timings;
i2c_parse_fw_timings(&pdev->dev, t, false);
acpi_speed = i2c_acpi_find_bus_speed(&pdev->dev);
/*
* Some DSTDs use a non standard speed, round down to the lowest
* standard speed.
*/
for (i = 1; i < ARRAY_SIZE(supported_speeds); i++) {
if (acpi_speed < supported_speeds[i])
break;
}
acpi_speed = supported_speeds[i - 1];
/*
* Find bus speed from the "clock-frequency" device property, ACPI
* or by using fast mode if neither is set.
*/
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 = 400000;
if (has_acpi_companion(&pdev->dev))
phytium_i2c_acpi_configure(pdev);
/*
* Only standard mode at 100kHz, fast mode at 400kHz,
* fast mode plus at 1MHz and high speed mode at 3.4MHz are supported.
*/
if (t->bus_freq_hz != 100000 && t->bus_freq_hz != 400000 &&
t->bus_freq_hz != 1000000 && t->bus_freq_hz != 3400000) {
dev_err(&pdev->dev,
"%d Hz is unsupported, only 100kHz, 400kHz, 1MHz and 3.4MHz are supported\n",
t->bus_freq_hz);
ret = -EINVAL;
goto exit_reset;
}
if (i2c_detect_slave_mode(&pdev->dev))
i2c_phytium_configure_slave(dev);
else
i2c_phytium_configure_master(dev);
dev->clk = devm_clk_get(&pdev->dev, NULL);
if (!i2c_phytium_prepare_clk(dev, true)) {
u64 clk_khz;
dev->get_clk_rate_khz = i2c_phytium_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);
}
dev->tx_fifo_depth = 7;
dev->rx_fifo_depth = 7;
dev->adapter.nr = pdev->id;
adap = &dev->adapter;
adap->owner = THIS_MODULE;
adap->class = I2C_CLASS_DEPRECATED;
ACPI_COMPANION_SET(&adap->dev, ACPI_COMPANION(&pdev->dev));
adap->dev.of_node = pdev->dev.of_node;
dev_pm_set_driver_flags(&pdev->dev,
DPM_FLAG_SMART_PREPARE |
DPM_FLAG_SMART_SUSPEND |
DPM_FLAG_LEAVE_SUSPENDED);
/* The code below assumes runtime PM to be disabled. */
WARN_ON(pm_runtime_enabled(&pdev->dev));
pm_runtime_set_autosuspend_delay(&pdev->dev, 1000);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
if (dev->mode == PHYTIUM_IC_SLAVE)
ret = i2c_phytium_probe_slave(dev);
else
ret = i2c_phytium_probe(dev);
if (ret)
goto exit_probe;
return ret;
exit_probe:
pm_runtime_disable(dev->dev);
exit_reset:
if (!IS_ERR_OR_NULL(dev->rst))
reset_control_assert(dev->rst);
return ret;
}
static int phytium_i2c_plat_remove(struct platform_device *pdev)
{
struct phytium_i2c_dev *dev = platform_get_drvdata(pdev);
pm_runtime_get_sync(&pdev->dev);
i2c_del_adapter(&dev->adapter);
dev->disable(dev);
pm_runtime_dont_use_autosuspend(&pdev->dev);
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(dev->dev);
if (!IS_ERR_OR_NULL(dev->rst))
reset_control_assert(dev->rst);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id phytium_i2c_of_match[] = {
{ .compatible = "phytium,i2c", },
{},
};
MODULE_DEVICE_TABLE(of, phytium_i2c_of_match);
#endif
static int __maybe_unused phytium_i2c_plat_suspend(struct device *dev)
{
struct phytium_i2c_dev *idev = dev_get_drvdata(dev);
idev->disable(idev);
i2c_phytium_prepare_clk(idev, false);
return 0;
}
static int __maybe_unused phytium_i2c_plat_resume(struct device *dev)
{
struct phytium_i2c_dev *idev = dev_get_drvdata(dev);
i2c_phytium_prepare_clk(idev, true);
idev->init(idev);
return 0;
}
static const struct dev_pm_ops phytium_i2c_dev_pm_ops = {
SET_LATE_SYSTEM_SLEEP_PM_OPS(phytium_i2c_plat_suspend,
phytium_i2c_plat_resume)
SET_RUNTIME_PM_OPS(phytium_i2c_plat_suspend,
phytium_i2c_plat_resume, NULL)
};
static struct platform_driver phytium_i2c_driver = {
.probe = phytium_i2c_plat_probe,
.remove = phytium_i2c_plat_remove,
.driver = {
.name = DRV_NAME,
.of_match_table = of_match_ptr(phytium_i2c_of_match),
.acpi_match_table = ACPI_PTR(phytium_i2c_acpi_match),
.pm = &phytium_i2c_dev_pm_ops,
},
};
module_platform_driver(phytium_i2c_driver);
MODULE_ALIAS("platform:i2c-phytium");
MODULE_AUTHOR("Chen Baozi <chenbaozi@phytium.com.cn>");
MODULE_DESCRIPTION("Phytium I2C bus adapter");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,262 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Phytium I2C adapter driver (slave only).
*
* Copyright (C) 2021, Phytium Technology Co.,Ltd.
*/
#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/module.h>
#include <linux/pm_runtime.h>
#include "i2c-phytium-core.h"
static void i2c_phytium_configure_fifo_slave(struct phytium_i2c_dev *dev)
{
/* Configure Tx/Rx FIFO threshold levels. */
phytium_writel(dev, 0, IC_TX_TL);
phytium_writel(dev, 0, IC_RX_TL);
/* Configure the I2C slave. */
phytium_writel(dev, dev->slave_cfg, IC_CON);
phytium_writel(dev, IC_INTR_SLAVE_MASK, IC_INTR_MASK);
}
static int i2c_phytium_init_slave(struct phytium_i2c_dev *dev)
{
/* Disable the adapter. */
__i2c_phytium_disable(dev);
/* Write SDA hold time if supported */
if (dev->sda_hold_time)
phytium_writel(dev, dev->sda_hold_time, IC_SDA_HOLD);
i2c_phytium_configure_fifo_slave(dev);
return 0;
}
static int i2c_phytium_reg_slave(struct i2c_client *slave)
{
struct phytium_i2c_dev *dev = i2c_get_adapdata(slave->adapter);
if (dev->slave)
return -EBUSY;
if (slave->flags & I2C_CLIENT_TEN)
return -EAFNOSUPPORT;
pm_runtime_get_sync(dev->dev);
/*
* Set slave address in the IC_SAR register,
* the address to which the i2c responds.
*/
__i2c_phytium_disable_nowait(dev);
phytium_writel(dev, slave->addr, IC_SAR);
dev->slave = slave;
__i2c_phytium_enable(dev);
dev->cmd_err = 0;
dev->msg_write_idx = 0;
dev->msg_read_idx = 0;
dev->msg_err = 0;
dev->status = STATUS_IDLE;
dev->abort_source = 0;
dev->rx_outstanding = 0;
return 0;
}
static int i2c_phytium_unreg_slave(struct i2c_client *slave)
{
struct phytium_i2c_dev *dev = i2c_get_adapdata(slave->adapter);
dev->disable_int(dev);
dev->disable(dev);
dev->slave = NULL;
pm_runtime_put(dev->dev);
return 0;
}
static u32 i2c_phytium_read_clear_intrbits_slave(struct phytium_i2c_dev *dev)
{
u32 stat;
/*
* The IC_INTR_STAT register just indicates "enabled" interrupts.
* Ths unmasked raw version of interrupt status bits are available
* in the IC_RAW_INTR_STAT register.
*
* That is,
* stat = phytium_readl(IC_INTR_STAT);
* equals to,
* stat = phytium_readl(IC_RAW_INTR_STAT) & phytium_readl(IC_INTR_MASK);
*
* The raw version might be useful for debugging purposes.
*/
stat = phytium_readl(dev, IC_INTR_STAT);
/*
* Do not use the IC_CLR_INTR register to clear interrupts, or
* you'll miss some interrupts, triggered during the period from
* phytium_readl(IC_INTR_STAT) to phytium_readl(IC_CLR_INTR).
*
* Instead, use the separately-prepared IC_CLR_* registers.
*/
if (stat & IC_INTR_TX_ABRT)
phytium_readl(dev, IC_CLR_TX_ABRT);
if (stat & IC_INTR_RX_UNDER)
phytium_readl(dev, IC_CLR_RX_UNDER);
if (stat & IC_INTR_RX_OVER)
phytium_readl(dev, IC_CLR_RX_OVER);
if (stat & IC_INTR_TX_OVER)
phytium_readl(dev, IC_CLR_TX_OVER);
if (stat & IC_INTR_RX_DONE)
phytium_readl(dev, IC_CLR_RX_DONE);
if (stat & IC_INTR_ACTIVITY)
phytium_readl(dev, IC_CLR_ACTIVITY);
if (stat & IC_INTR_STOP_DET)
phytium_readl(dev, IC_CLR_STOP_DET);
if (stat & IC_INTR_START_DET)
phytium_readl(dev, IC_CLR_START_DET);
if (stat & IC_INTR_GEN_CALL)
phytium_readl(dev, IC_CLR_GEN_CALL);
return stat;
}
/*
* Interrupt service routine. This gets called whenever an I2C slave interrupt
* occurs.
*/
static int i2c_phytium_irq_handler_slave(struct phytium_i2c_dev *dev)
{
u32 raw_stat, stat, enabled;
u8 val, slave_activity;
stat = phytium_readl(dev, IC_INTR_STAT);
enabled = phytium_readl(dev, IC_ENABLE);
raw_stat = phytium_readl(dev, IC_RAW_INTR_STAT);
slave_activity = ((phytium_readl(dev, IC_STATUS) &
IC_STATUS_SLAVE_ACTIVITY) >> 6);
if (!enabled || !(raw_stat & ~IC_INTR_ACTIVITY) || !dev->slave)
return 0;
dev_dbg(dev->dev,
"%#x STATUS SLAVE_ACTIVITY=%#x : RAW_INTR_STAT=%#x : INTR_STAT=%#x\n",
enabled, slave_activity, raw_stat, stat);
if ((stat & IC_INTR_RX_FULL) && (stat & IC_INTR_STOP_DET))
i2c_slave_event(dev->slave, I2C_SLAVE_WRITE_REQUESTED, &val);
if (stat & IC_INTR_RD_REQ) {
if (slave_activity) {
if (stat & IC_INTR_RX_FULL) {
val = phytium_readl(dev, IC_DATA_CMD);
if (!i2c_slave_event(dev->slave,
I2C_SLAVE_WRITE_RECEIVED,
&val)) {
dev_vdbg(dev->dev, "Byte %X acked!",
val);
}
phytium_readl(dev, IC_CLR_RD_REQ);
stat = i2c_phytium_read_clear_intrbits_slave(dev);
} else {
phytium_readl(dev, IC_CLR_RD_REQ);
phytium_readl(dev, IC_CLR_RX_UNDER);
stat = i2c_phytium_read_clear_intrbits_slave(dev);
}
if (!i2c_slave_event(dev->slave,
I2C_SLAVE_READ_REQUESTED,
&val))
phytium_writel(dev, val, IC_DATA_CMD);
}
}
if (stat & IC_INTR_RX_DONE) {
if (!i2c_slave_event(dev->slave, I2C_SLAVE_READ_PROCESSED,
&val))
phytium_readl(dev, IC_CLR_RX_DONE);
i2c_slave_event(dev->slave, I2C_SLAVE_STOP, &val);
stat = i2c_phytium_read_clear_intrbits_slave(dev);
return 1;
}
if (stat & IC_INTR_RX_FULL) {
val = phytium_readl(dev, IC_DATA_CMD);
if (!i2c_slave_event(dev->slave, I2C_SLAVE_WRITE_RECEIVED,
&val))
dev_vdbg(dev->dev, "Byte %X acked!", val);
} else {
i2c_slave_event(dev->slave, I2C_SLAVE_STOP, &val);
stat = i2c_phytium_read_clear_intrbits_slave(dev);
}
return 1;
}
static irqreturn_t i2c_phytium_isr_slave(int this_irq, void *dev_id)
{
struct phytium_i2c_dev *dev = dev_id;
int ret;
i2c_phytium_read_clear_intrbits_slave(dev);
ret = i2c_phytium_irq_handler_slave(dev);
if (ret > 0)
complete(&dev->cmd_complete);
return IRQ_RETVAL(ret);
}
static const struct i2c_algorithm i2c_phytium_algo = {
.functionality = i2c_phytium_func,
.reg_slave = i2c_phytium_reg_slave,
.unreg_slave = i2c_phytium_unreg_slave,
};
int i2c_phytium_probe_slave(struct phytium_i2c_dev *dev)
{
struct i2c_adapter *adap = &dev->adapter;
int ret;
init_completion(&dev->cmd_complete);
dev->init = i2c_phytium_init_slave;
dev->disable = i2c_phytium_disable;
dev->disable_int = i2c_phytium_disable_int;
ret = dev->init(dev);
if (ret)
return ret;
snprintf(adap->name, sizeof(adap->name),
"Synopsys DesignWare I2C Slave adapter");
adap->retries = 3;
adap->algo = &i2c_phytium_algo;
adap->dev.parent = dev->dev;
i2c_set_adapdata(adap, dev);
ret = devm_request_irq(dev->dev, dev->irq, i2c_phytium_isr_slave,
IRQF_SHARED, dev_name(dev->dev), dev);
if (ret) {
dev_err(dev->dev, "failure requesting irq %i: %d\n",
dev->irq, 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_phytium_probe_slave);