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:
parent
22599f9f53
commit
80ed68fc2e
|
@ -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>;
|
||||
};
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
|
@ -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);
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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);
|
Loading…
Reference in New Issue