HunZI a462d2b757
target: add phytium support (#11798)
* target: add phytium support

* kernel/video: add phytium platform ARM GPU support

* config: add EFI support to phytium armv8

* target: phytium: remove rtl8821cs driver

* target: phytium: refresh dts
2024-01-18 15:16:24 +08:00

204 lines
4.9 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Phytium I2C adapter driver.
*
* Based on the TI DAVINCI I2C adapter driver.
*
* Copyright (c) 2021-2023 Phytium Technology Co., Ltd.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/swab.h>
#include "i2c-phytium-core.h"
static char *abort_sources[] = {
[ABRT_7B_ADDR_NOACK] =
"slave address not acknowledged (7bit mode)",
[ABRT_10ADDR1_NOACK] =
"first address byte not acknowledged (10bit mode)",
[ABRT_10ADDR2_NOACK] =
"second address byte not acknowledged (10bit mode)",
[ABRT_TXDATA_NOACK] =
"data not acknowledged",
[ABRT_GCALL_NOACK] =
"no acknowledgment for a general call",
[ABRT_GCALL_READ] =
"read after general call",
[ABRT_SBYTE_ACKDET] =
"start byte acknowledged",
[ABRT_SBYTE_NORSTRT] =
"trying to send start byte when restart is disabled",
[ABRT_10B_RD_NORSTRT] =
"trying to read when restart is disabled (10bit mode)",
[ABRT_MASTER_DIS] =
"trying to use disabled adapter",
[ARB_LOST] =
"lost arbitration",
[ABRT_SLAVE_FLUSH_TXFIFO] =
"read command so flush old data in the TX FIFO",
[ABRT_SLAVE_ARBLOST] =
"slave lost the bus while transmitting data to a remote master",
[ABRT_SLAVE_RD_INTX] =
"incorrect slave-transmitter mode configuration",
};
u32 phytium_readl(struct phytium_i2c_dev *dev, int offset)
{
return readl_relaxed(dev->base + offset);
}
void phytium_writel(struct phytium_i2c_dev *dev, u32 b, int offset)
{
writel_relaxed(b, dev->base + offset);
}
u32 i2c_phytium_scl_hcnt(u32 ic_clk, u32 tSYMBOL, u32 tf, int cond, int offset)
{
if (cond)
return (ic_clk * tSYMBOL + 500000) / 1000000 - 8 + offset;
else
return (ic_clk * (tSYMBOL + tf) + 500000) / 1000000 - 3 + offset;
}
u32 i2c_phytium_scl_lcnt(u32 ic_clk, u32 tLOW, u32 tf, int offset)
{
return ((ic_clk * (tLOW + tf) + 500000) / 1000000) - 1 + offset;
}
int i2c_phytium_set_sda_hold(struct phytium_i2c_dev *dev)
{
if (!dev->sda_hold_time) {
/* Keep previous hold time setting if no one set it */
dev->sda_hold_time = phytium_readl(dev, IC_SDA_HOLD);
}
if (!(dev->sda_hold_time & IC_SDA_HOLD_RX_MASK))
dev->sda_hold_time |= 1 << IC_SDA_HOLD_RX_SHIFT;
dev_dbg(dev->dev, "SDA Hold Time TX:RX = %d:%d\n",
dev->sda_hold_time & ~(u32)IC_SDA_HOLD_RX_MASK,
dev->sda_hold_time >> IC_SDA_HOLD_RX_SHIFT);
return 0;
}
void __i2c_phytium_disable(struct phytium_i2c_dev *dev)
{
int timeout = 100;
do {
__i2c_phytium_disable_nowait(dev);
if ((phytium_readl(dev, IC_ENABLE_STATUS) & 1) == 0)
return;
/*
* Wait 10 times the signaling period of the highest I2C
* transfer supported by the driver (for 400KHz this is
* 25us).
*/
usleep_range(25, 250);
} while (timeout--);
dev_warn(dev->dev, "timeout in disabling adapter\n");
}
unsigned long i2c_phytium_clk_rate(struct phytium_i2c_dev *dev)
{
if (WARN_ON_ONCE(!dev->get_clk_rate_khz))
return 0;
return dev->get_clk_rate_khz(dev);
}
int i2c_phytium_prepare_clk(struct phytium_i2c_dev *dev, bool prepare)
{
if (IS_ERR(dev->clk))
return PTR_ERR(dev->clk);
if (prepare)
return clk_prepare_enable(dev->clk);
clk_disable_unprepare(dev->clk);
return 0;
}
EXPORT_SYMBOL_GPL(i2c_phytium_prepare_clk);
int i2c_phytium_wait_bus_not_busy(struct phytium_i2c_dev *dev)
{
int timeout = 20; /* 20 ms */
while (phytium_readl(dev, IC_STATUS) & IC_STATUS_ACTIVITY) {
if (timeout <= 0) {
dev_warn(dev->dev, "timeout waiting for bus ready\n");
i2c_recover_bus(&dev->adapter);
if (phytium_readl(dev, IC_STATUS) & IC_STATUS_ACTIVITY)
return -ETIMEDOUT;
return 0;
}
timeout--;
usleep_range(1000, 1100);
}
return 0;
}
int i2c_phytium_handle_tx_abort(struct phytium_i2c_dev *dev)
{
unsigned long abort_source = dev->abort_source;
int i;
if (abort_source & IC_TX_ABRT_NOACK) {
for_each_set_bit(i, &abort_source, ARRAY_SIZE(abort_sources))
dev_dbg(dev->dev,
"%s: %s\n", __func__, abort_sources[i]);
return -EREMOTEIO;
}
for_each_set_bit(i, &abort_source, ARRAY_SIZE(abort_sources))
dev_err(dev->dev, "%s: %s\n", __func__, abort_sources[i]);
if (abort_source & IC_TX_ARB_LOST)
return -EAGAIN;
else if (abort_source & IC_TX_ABRT_GCALL_READ)
return -EINVAL;
else
return -EIO;
return 0;
}
u32 i2c_phytium_func(struct i2c_adapter *adapter)
{
struct phytium_i2c_dev *dev = i2c_get_adapdata(adapter);
return dev->functionality;
}
void i2c_phytium_disable(struct phytium_i2c_dev *dev)
{
/* Disable controller */
__i2c_phytium_disable(dev);
/* Disable all interrupts */
phytium_writel(dev, 0, IC_INTR_MASK);
phytium_readl(dev, IC_CLR_INTR);
}
void i2c_phytium_disable_int(struct phytium_i2c_dev *dev)
{
phytium_writel(dev, 0, IC_INTR_MASK);
}
MODULE_AUTHOR("Cheng Quan <chengquan@phytium.com.cn>");
MODULE_DESCRIPTION("Phytium I2C bus adapter core");
MODULE_LICENSE("GPL");