Commit 777b12af authored by Jiakun Shuai's avatar Jiakun Shuai
Browse files

i2c: add Phytium i2c driver

phytium inclusion
category: feature
bugzilla: https://gitee.com/openeuler/kernel/issues/I8AXAT


CVE: NA

----------------------------------

Add Phytium i2c driver for Phytium desktop and embedded platforms.

By the way, the ACPI support of this driver is also added
in this commit.

Signed-off-by: default avatarJiakun Shuai <shuaijiakun1288@phytium.com.cn>
Signed-off-by: default avatarCheng Quan <chengquan@phytium.com.cn>
Signed-off-by: default avatarFeng Jun <fengjun@phytium.com.cn>
Signed-off-by: default avatarChen Baozi <chenbaozi@phytium.com.cn>
parent 8e8fe3ac
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -165,6 +165,16 @@ static const struct apd_device_desc hip08_i2c_desc = {
	.fixed_clk_rate = 250000000,
};

static const struct apd_device_desc phytium_i2c_desc = {
	.setup = acpi_apd_setup,
	.fixed_clk_rate = 200000000,
};

static const struct apd_device_desc phytium_pe220x_i2c_desc = {
	.setup = acpi_apd_setup,
	.fixed_clk_rate = 50000000,
};

static const struct apd_device_desc hip08_lite_i2c_desc = {
	.setup = acpi_apd_setup,
	.fixed_clk_rate = 125000000,
@@ -250,6 +260,8 @@ static const struct acpi_device_id acpi_apd_device_ids[] = {
	{ "HISI02A2", APD_ADDR(hip08_i2c_desc) },
	{ "HISI02A3", APD_ADDR(hip08_lite_i2c_desc) },
	{ "HISI0173", APD_ADDR(hip08_spi_desc) },
	{ "PHYT0003", APD_ADDR(phytium_i2c_desc) },
	{ "PHYT0038", APD_ADDR(phytium_pe220x_i2c_desc) },
#endif
	{ }
};
+28 −0
Original line number Diff line number Diff line
@@ -544,6 +544,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
+4 −0
Original line number Diff line number Diff line
@@ -54,6 +54,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
+1 −0
Original line number Diff line number Diff line
@@ -152,6 +152,7 @@ static const struct acpi_device_id dw_i2c_acpi_match[] = {
	{ "HISI02A1", 0 },
	{ "HISI02A2", 0 },
	{ "HISI02A3", 0 },
	{ "PHYT0003", 0 },
	{ "HYGO0010", ACCESS_INTR_MASK },
	{ }
};
+207 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Phytium I2C adapter driver.
 *
 * Derived from Synopysys I2C driver.
 *   Copyright (C) 2006 Texas Instruments.
 *   Copyright (C) 2007 MontaVista Software Inc.
 *   Copyright (C) 2009 Provigent Ltd.
 *
 * Copyright (C) 2021-2023, Phytium Technology Co., Ltd.
 */
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/swab.h>

#include "i2c-phytium-core.h"

static char *abort_sources[] = {
	[ABRT_7B_ADDR_NOACK] =
		"slave address not acknowledged (7bit mode)",
	[ABRT_10ADDR1_NOACK] =
		"first address byte not acknowledged (10bit mode)",
	[ABRT_10ADDR2_NOACK] =
		"second address byte not acknowledged (10bit mode)",
	[ABRT_TXDATA_NOACK] =
		"data not acknowledged",
	[ABRT_GCALL_NOACK] =
		"no acknowledgment for a general call",
	[ABRT_GCALL_READ] =
		"read after general call",
	[ABRT_SBYTE_ACKDET] =
		"start byte acknowledged",
	[ABRT_SBYTE_NORSTRT] =
		"trying to send start byte when restart is disabled",
	[ABRT_10B_RD_NORSTRT] =
		"trying to read when restart is disabled (10bit mode)",
	[ABRT_MASTER_DIS] =
		"trying to use disabled adapter",
	[ARB_LOST] =
		"lost arbitration",
	[ABRT_SLAVE_FLUSH_TXFIFO] =
		"read command so flush old data in the TX FIFO",
	[ABRT_SLAVE_ARBLOST] =
		"slave lost the bus while transmitting data to a remote master",
	[ABRT_SLAVE_RD_INTX] =
		"incorrect slave-transmitter mode configuration",
};

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

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

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

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

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

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

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

	return 0;
}

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

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

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

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

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

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

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

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

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

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

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

	return 0;
}

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

	if (abort_source & IC_TX_ABRT_NOACK) {
		for_each_set_bit(i, &abort_source, ARRAY_SIZE(abort_sources))
			dev_dbg(dev->dev,
				"%s: %s\n", __func__, abort_sources[i]);
		return -EREMOTEIO;
	}

	for_each_set_bit(i, &abort_source, ARRAY_SIZE(abort_sources))
		dev_err(dev->dev, "%s: %s\n", __func__, abort_sources[i]);

	if (abort_source & IC_TX_ARB_LOST)
		return -EAGAIN;
	else if (abort_source & IC_TX_ABRT_GCALL_READ)
		return -EINVAL;
	else
		return -EIO;

	return 0;
}

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

	return dev->functionality;
}

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

	/* Disable all 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");
Loading