linuxOS_D21X/source/linux-5.10/drivers/i2c/busses/i2c-artinchip-common.c
2024-11-29 16:33:21 +08:00

354 lines
8.3 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* I2C driver of ArtInChip SoC
*
* Copyright (C) 2020-2023 ArtInChip Technology Co., Ltd.
* Authors: dwj <weijie.ding@artinchip.com>
*/
#include <linux/clk.h>
#include <linux/reset.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/time64.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/swab.h>
#include <linux/iopoll.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include "i2c-artinchip.h"
#define FS_MIN_SCL_HIGH 600
#define FS_MIN_SCL_LOW 1300
#define SS_MIN_SCL_HIGH 4000
#define SS_MIN_SCL_LOW 4700
static char *abort_sources[] = {
[ABRT_7BIT_ADDR_NOACK] =
"slave address not acknowledged (7bit mode)",
[ABRT_10BIT_ADDR1_NOACK] =
"first address byte not acknowledged (10bit mode)",
[ABRT_10BIT_ADDR2_NOACK] =
"second address byte not acknowledged (10bit mode)",
[ABRT_TXDATA_NOACK] =
"the first data byte after address byte 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_10BIT_RD_NORSTRT] =
"trying to read when restart is disabled (10bit mode)",
[ABRT_MASTER_DIS] =
"trying to use disabled adapter",
[ABRT_LOST] =
"lost arbitration",
[ABRT_SLVFLUSH_TXFIFO] =
"read command so flush old data in the TX FIFO",
[ABRT_SLV_ARBLOST] =
"slave lost the bus while transmitting data to a remote master",
[ABRT_SLVRD_INTX] =
"incorrect slave-transmitter mode configuration",
[ABRT_USER_ABRT] =
"detect transfer abort in master mode",
[ABRT_SDA_STUCK_AT_LOW] =
"SDA held low level timeout",
};
int i2c_handle_tx_abort(struct aic_i2c_dev *i2c_dev)
{
unsigned long abort_source = i2c_dev->abort_source;
int i;
if (abort_source & I2C_TX_ABRT_NOACK) {
for_each_set_bit(i, &abort_source, ARRAY_SIZE(abort_sources))
dev_dbg(i2c_dev->dev, "%s: %s\n",
__func__, abort_sources[i]);
return -EREMOTEIO;
}
for_each_set_bit(i, &abort_source, ARRAY_SIZE(abort_sources))
dev_err(i2c_dev->dev, "%s: %s\n", __func__, abort_sources[i]);
if (abort_source & I2C_ABRT_LOST)
return -EAGAIN;
else if (abort_source & I2C_ABRT_GCALL_READ)
return -EINVAL;
else
return -EIO;
}
int i2c_wait_bus_free(struct aic_i2c_dev *i2c_dev)
{
u32 status;
int ret;
ret = readl_relaxed_poll_timeout(i2c_dev->base + I2C_STATUS, status,
!(status & I2C_STATUS_ACTIVITY), 10, 1000);
if (ret) {
dev_dbg(i2c_dev->dev, "I2C bus not free!\n");
ret = -EBUSY;
}
return ret;
}
u32 i2c_readl(struct aic_i2c_dev *i2c_dev, int offset)
{
return readl(i2c_dev->base + offset);
}
void i2c_writel(struct aic_i2c_dev *i2c_dev, u32 val, int offset)
{
writel(val, i2c_dev->base + offset);
}
void i2c_disable_interrupts(struct aic_i2c_dev *i2c_dev)
{
writel(0, i2c_dev->base + I2C_INTR_MASK);
}
void i2c_enable(struct aic_i2c_dev *i2c_dev)
{
int ret;
ret = readl(i2c_dev->base + I2C_ENABLE);
ret |= I2C_SDA_STUCK_RECOVERY_ENABLE | I2C_ENABLE_BIT;
writel(ret, i2c_dev->base + I2C_ENABLE);
}
void i2c_disable(struct aic_i2c_dev *i2c_dev)
{
int ret = 0;
ret = i2c_wait_bus_free(i2c_dev);
if (ret) {
dev_err(i2c_dev->dev, "I2C disable failed\n");
return;
}
ret = readl(i2c_dev->base + I2C_ENABLE);
ret &= ~I2C_ENABLE_BIT;
writel(ret, i2c_dev->base + I2C_ENABLE);
}
int i2c_scl_cnt(u32 module_clk, u32 rate, u16 *hcnt, u16 *lcnt)
{
u16 hcnt_tmp;
u16 lcnt_tmp;
u32 temp;
u32 duty_cycle = 50; /* low/(low + high) time */
if (rate > 384000) {
/* When the i2c rate is greater than 384KHz,
* it is necessary to ensure that lcnt is not less than 1300ns,
* so the duty cycle needs to be adjusted.
*/
duty_cycle = (13 * rate) / 100000 + (((13 * rate) % 100000) ? 1 : 0);
}
temp = module_clk / rate;
if (!hcnt || !lcnt)
return -EINVAL;
lcnt_tmp = temp * duty_cycle / 100 + ((temp * duty_cycle % 100) ? 1 : 0) - 1;
hcnt_tmp = temp - lcnt_tmp - 8 - 2;
*hcnt = hcnt_tmp;
*lcnt = lcnt_tmp;
return 0;
}
static int i2c_probe(struct platform_device *pdev)
{
struct aic_i2c_dev *i2c_dev;
struct i2c_timings *t;
struct resource *mem;
int irq, ret;
u32 module_clk, hold_time;
u8 setup_time;
i2c_dev = devm_kzalloc(&pdev->dev,
sizeof(struct aic_i2c_dev), GFP_KERNEL);
if (!i2c_dev)
return -ENOMEM;
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
i2c_dev->base = devm_ioremap_resource(&pdev->dev, mem);
if (IS_ERR(i2c_dev->base)) {
ret = PTR_ERR(i2c_dev->base);
goto exit_done;
}
i2c_dev->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(i2c_dev->clk)) {
dev_err(i2c_dev->dev, "Failed to get I2C clock\n");
return PTR_ERR(i2c_dev->clk);
}
module_clk = clk_get_rate(i2c_dev->clk);
if (!module_clk) {
dev_err(i2c_dev->dev, "get i2c clock rate failed\n");
return -EINVAL;
}
if (clk_prepare_enable(i2c_dev->clk)) {
dev_err(&pdev->dev, "try to enable I2C clock failed\n");
return -EINVAL;
}
i2c_dev->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL);
if (IS_ERR(i2c_dev->rst))
return PTR_ERR(i2c_dev->rst);
ret = reset_control_deassert(i2c_dev->rst);
if (ret)
goto exit_clk_disable;
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
ret = irq;
goto exit_reset_assert;
}
i2c_dev->dev = &pdev->dev;
i2c_dev->irq = irq;
i2c_dev->adap.owner = THIS_MODULE;
i2c_dev->adap.class = I2C_CLASS_DEPRECATED;
i2c_dev->adap.dev.parent = i2c_dev->dev;
i2c_dev->adap.nr = -1;
i2c_dev->adap.dev.of_node = pdev->dev.of_node;
i2c_dev->adap.timeout = HZ;
platform_set_drvdata(pdev, i2c_dev);
t = &i2c_dev->timings;
i2c_parse_fw_timings(&pdev->dev, t, false);
if (t->bus_freq_hz >= 200 && t->bus_freq_hz <= 100000) {
i2c_dev->i2c_speed = I2C_SPEED_STD;
i2c_dev->target_rate = t->bus_freq_hz;
} else if (t->bus_freq_hz > 100000 && t->bus_freq_hz <= 400000) {
i2c_dev->i2c_speed = I2C_SPEED_FAST;
i2c_dev->target_rate = t->bus_freq_hz;
} else {
i2c_dev->i2c_speed = I2C_SPEED_FAST;
i2c_dev->target_rate = 400000;
}
if (t->sda_hold_ns != 0) {
hold_time = t->sda_hold_ns / (1000000000 / module_clk) + 1;
hold_time |= hold_time << 16;
i2c_writel(i2c_dev, hold_time, I2C_SDA_HOLD);
}
if (t->scl_int_delay_ns != 0) {
setup_time = t->scl_int_delay_ns / (1000000000 / module_clk) + 1;
i2c_writel(i2c_dev, setup_time, I2C_SDA_SETUP);
}
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 (i2c_detect_slave_mode(&pdev->dev))
ret = i2c_probe_slave(i2c_dev);
else
ret = i2c_probe_master(i2c_dev);
if (ret)
return ret;
dev_info(&pdev->dev, "I2C initialized\n");
return 0;
exit_reset_assert:
reset_control_assert(i2c_dev->rst);
exit_clk_disable:
clk_disable_unprepare(i2c_dev->clk);
exit_done:
return ret;
}
static int i2c_remove(struct platform_device *pdev)
{
struct aic_i2c_dev *i2c_dev = platform_get_drvdata(pdev);
pm_runtime_get_sync(&pdev->dev);
i2c_del_adapter(&i2c_dev->adap);
i2c_disable(i2c_dev);
pm_runtime_put_sync(&pdev->dev);
reset_control_assert(i2c_dev->rst);
return 0;
}
#ifdef CONFIG_PM
static int i2c_runtime_suspend(struct device *dev)
{
struct aic_i2c_dev *i2c_dev = dev_get_drvdata(dev);
clk_disable_unprepare(i2c_dev->clk);
return 0;
}
static int i2c_runtime_resume(struct device *dev)
{
struct aic_i2c_dev *i2c_dev = dev_get_drvdata(dev);
return clk_prepare_enable(i2c_dev->clk);
}
static const struct dev_pm_ops i2c_pm_ops = {
SET_RUNTIME_PM_OPS(i2c_runtime_suspend, i2c_runtime_resume, NULL)
};
#define I2C_DEV_PM_OPS (&i2c_pm_ops)
#else
#define I2C_DEV_PM_OPS NULL
#endif
static const struct of_device_id aic_i2c_match[] = {
{
.compatible = "artinchip,aic-i2c",
},
{}
};
static struct platform_driver aic_i2c_driver = {
.probe = i2c_probe,
.remove = i2c_remove,
.driver = {
.name = "aic-i2c",
.of_match_table = aic_i2c_match,
.pm = I2C_DEV_PM_OPS,
},
};
module_platform_driver(aic_i2c_driver);
MODULE_DESCRIPTION("ArtInChip I2C controller driver");
MODULE_AUTHOR("dwj <weijie.ding@artinchip.com>");
MODULE_LICENSE("GPL");