On Mon, Mar 02, 2015 at 04:24:43PM +0800, Chen-Yu Tsai wrote: > The RSB controller looks like an SMBus controller which only supports byte > and word data transfers. It can also do double-word data transfers, but the > I2C subsystem does not support this, nor have we seen devices using this. > > The RSB differs from standard SMBus protocol on several aspects: > - it uses addresses set at runtime to address slaves. Runtime addresses > are sent to slaves using their 12bit hardware addresses. Up to 15 > runtime addresses are available. > - it adds a parity bit every 8bits of data and address for read and > write accesses; this replaces the ack bit > - only one read access is required to read a byte (instead of a write > followed by a read access in standard SMBus protocol) > - there's no Ack bit after each read access > > This means this bus cannot be used to interface with standard SMBus > devices (known devices supporting this interface are the AXP223, AXP806, > AXP809 PMICs and the AC100 codec/RTC). However the RSB protocol is an > extension of P2WI, which was close enough to SMBus to be integrated into > the I2C subsystem in commit 3e833490fae5 ("i2c: sunxi: add P2WI (Push/Pull > 2 Wire Interface) controller support"). > > Signed-off-by: Chen-Yu Tsai <w...@csie.org>
I don't have the bandwidth for a full review right now. However, I already wanted to tell you guys that my gut feeling is that this protocol is quite far away from I2C. P2WI was already at the edge. Maybe there is a better place for such custom stuff? I dunno yet. Thanks, Wolfram > --- > drivers/i2c/busses/Kconfig | 12 + > drivers/i2c/busses/Makefile | 1 + > drivers/i2c/busses/i2c-sunxi-rsb.c | 458 > +++++++++++++++++++++++++++++++++++++ > 3 files changed, 471 insertions(+) > create mode 100644 drivers/i2c/busses/i2c-sunxi-rsb.c > > diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig > index 22da9c2ffa22..cf9337877181 100644 > --- a/drivers/i2c/busses/Kconfig > +++ b/drivers/i2c/busses/Kconfig > @@ -840,6 +840,18 @@ config I2C_SUN6I_P2WI > This interface is used to connect to specific PMIC devices (like the > AXP221). > > +config I2C_SUNXI_RSB > + tristate "Allwinner Reduced Serial Bus controller" > + depends on RESET_CONTROLLER > + depends on MACH_SUN8I || MACH_SUN9I || COMPILE_TEST > + help > + If you say yes to this option, support will be included for the > + Reduced Serial Bus controller embedded in some sunxi SOCs. > + The RSB looks like an SMBus controller (which supports only byte > + accesses), but requires setting runtime addresses for slave devices. > + This interface is used to connect to specific PMIC devices (like the > + AXP223) or peripherals (like the AC100). > + > config I2C_TEGRA > tristate "NVIDIA Tegra internal I2C controller" > depends on ARCH_TEGRA > diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile > index 3638feb6677e..f95d50315003 100644 > --- a/drivers/i2c/busses/Makefile > +++ b/drivers/i2c/busses/Makefile > @@ -81,6 +81,7 @@ obj-$(CONFIG_I2C_SIRF) += i2c-sirf.o > obj-$(CONFIG_I2C_ST) += i2c-st.o > obj-$(CONFIG_I2C_STU300) += i2c-stu300.o > obj-$(CONFIG_I2C_SUN6I_P2WI) += i2c-sun6i-p2wi.o > +obj-$(CONFIG_I2C_SUNXI_RSB) += i2c-sunxi-rsb.o > obj-$(CONFIG_I2C_TEGRA) += i2c-tegra.o > obj-$(CONFIG_I2C_VERSATILE) += i2c-versatile.o > obj-$(CONFIG_I2C_WMT) += i2c-wmt.o > diff --git a/drivers/i2c/busses/i2c-sunxi-rsb.c > b/drivers/i2c/busses/i2c-sunxi-rsb.c > new file mode 100644 > index 000000000000..7e9be3e14b8a > --- /dev/null > +++ b/drivers/i2c/busses/i2c-sunxi-rsb.c > @@ -0,0 +1,458 @@ > +/* > + * RSB (Reduced Serial Bus) driver. > + * > + * Author: Chen-Yu Tsai <w...@csie.org> > + * > + * This file is licensed under the terms of the GNU General Public License > + * version 2. This program is licensed "as is" without any warranty of any > + * kind, whether express or implied. > + * > + * The RSB controller looks like an SMBus controller which only supports > + * byte and word data transfers. But, it differs from standard SMBus > + * protocol on several aspects: > + * - it uses addresses set at runtime to address slaves. Runtime addresses > + * are sent to slaves using their 12bit hardware addresses. Up to 15 > + * runtime addresses are available. > + * - it adds a parity bit every 8bits of data and address for read and > + * write accesses; this replaces the ack bit > + * - only one read access is required to read a byte (instead of a write > + * followed by a read access in standard SMBus protocol) > + * - there's no Ack bit after each read access > + * > + * This means this bus cannot be used to interface with standard SMBus > + * devices. Devices known to support this interface include the AXP223, > + * AXP809, and AXP806 PMICs, and the AC100 audio codec, all from X-Powers. > + * > + * A description of the operation and wire protocol can be found in the > + * RSB section of Allwinner's A80 user manual, which can be found at > + * > + * https://github.com/allwinner-zh/documents/tree/master/A80 > + * > + * This document is officially released by Allwinner. > + * > + * This driver is based on i2c-sun6i-p2wi.c, the P2WI bus driver. > + * > + */ > +#include <linux/clk.h> > +#include <linux/i2c.h> > +#include <linux/io.h> > +#include <linux/interrupt.h> > +#include <linux/jiffies.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/platform_device.h> > +#include <linux/reset.h> > + > + > +/* RSB registers */ > +#define RSB_CTRL 0x0 /* Global control */ > +#define RSB_CCR 0x4 /* Clock control */ > +#define RSB_INTE 0x8 /* Interrupt controls */ > +#define RSB_INTS 0xc /* Interrupt status */ > +#define RSB_ADDR 0x10 /* Address to send with read/write command */ > +#define RSB_DATA 0x1c /* Data to read/write */ > +#define RSB_LCR 0x24 /* Line control */ > +#define RSB_DMCR 0x28 /* Device mode (init) control */ > +#define RSB_CMD 0x2c /* RSB Command */ > +#define RSB_DAR 0x30 /* Device address / runtime address */ > + > +/* CTRL fields */ > +#define RSB_CTRL_START_TRANS BIT(7) > +#define RSB_CTRL_ABORT_TRANS BIT(6) > +#define RSB_CTRL_GLOBAL_INT_ENB BIT(1) > +#define RSB_CTRL_SOFT_RST BIT(0) > + > +/* CLK CTRL fields */ > +#define RSB_CCR_SDA_OUT_DELAY(v) (((v) & 0x7) << 8) > +#define RSB_CCR_MAX_CLK_DIV 0xff > +#define RSB_CCR_CLK_DIV(v) ((v) & RSB_CCR_MAX_CLK_DIV) > + > +/* STATUS fields */ > +#define RSB_INTS_TRANS_ERR_ACK BIT(16) > +#define RSB_INTS_TRANS_ERR_ID(v) (((v) >> 8) & 0xf) > +#define RSB_INTS_LOAD_BSY BIT(2) > +#define RSB_INTS_TRANS_ERR BIT(1) > +#define RSB_INTS_TRANS_OVER BIT(0) > + > +/* LINE CTRL fields*/ > +#define RSB_LCR_SCL_STATE BIT(5) > +#define RSB_LCR_SDA_STATE BIT(4) > +#define RSB_LCR_SCL_CTL BIT(3) > +#define RSB_LCR_SCL_CTL_EN BIT(2) > +#define RSB_LCR_SDA_CTL BIT(1) > +#define RSB_LCR_SDA_CTL_EN BIT(0) > + > +/* DEVICE MODE CTRL field values */ > +#define RSB_DMCR_DEVICE_START BIT(31) > +#define RSB_DMCR_MODE_DATA (0x7c << 16) > +#define RSB_DMCR_MODE_REG (0x3e << 8) > +#define RSB_DMCR_DEV_ADDR 0x00 > + > +/* CMD values */ > +#define RSB_CMD_RD8 0x8b > +#define RSB_CMD_RD16 0x9c > +#define RSB_CMD_RD32 0xa6 > +#define RSB_CMD_WR8 0x4e > +#define RSB_CMD_WR16 0x59 > +#define RSB_CMD_WR32 0x63 > +#define RSB_CMD_STRA 0xe8 > + > +/* DAR fields */ > +#define RSB_DAR_RTA(v) (((v) & 0xff) << 16) > +#define RSB_DAR_DA(v) ((v) & 0xffff) > + > +#define RSB_MAX_FREQ 20000000 > + > +#define RSB_CTRL_NAME "sunxi-rsb" > + > +struct rsb { > + struct i2c_adapter adapter; > + struct completion complete; > + unsigned int status; > + void __iomem *regs; > + struct clk *clk; > + struct reset_control *rstc; > +}; > + > +static irqreturn_t rsb_interrupt(int irq, void *dev_id) > +{ > + struct rsb *rsb = dev_id; > + unsigned long status; > + > + status = readl(rsb->regs + RSB_INTS); > + rsb->status = status; > + > + /* Clear interrupts */ > + status &= (RSB_INTS_LOAD_BSY | RSB_INTS_TRANS_ERR | > + RSB_INTS_TRANS_OVER); > + writel(status, rsb->regs + RSB_INTS); > + > + complete(&rsb->complete); > + > + return IRQ_HANDLED; > +} > + > +/* common code that starts a transfer */ > +static int rsb_run_xfer(struct rsb *rsb) > +{ > + if (readl(rsb->regs + RSB_CTRL) & RSB_CTRL_START_TRANS) { > + dev_dbg(&rsb->adapter.dev, "RSB bus busy\n"); > + return -EBUSY; > + } > + > + reinit_completion(&rsb->complete); > + > + writel(RSB_INTS_LOAD_BSY | RSB_INTS_TRANS_ERR | RSB_INTS_TRANS_OVER, > + rsb->regs + RSB_INTE); > + > + writel(RSB_CTRL_START_TRANS | RSB_CTRL_GLOBAL_INT_ENB, > + rsb->regs + RSB_CTRL); > + > + wait_for_completion(&rsb->complete); > + > + if (rsb->status & RSB_INTS_LOAD_BSY) { > + dev_dbg(&rsb->adapter.dev, "RSB bus busy\n"); > + return -EBUSY; > + } > + > + if (rsb->status & RSB_INTS_TRANS_ERR) { > + dev_dbg(&rsb->adapter.dev, "RSB bus xfer error\n"); > + return -ENXIO; > + } > + > + return 0; > +} > + > +static int rsb_smbus_xfer(struct i2c_adapter *adap, u16 addr, > + unsigned short flags, char read_write, > + u8 command, int size, union i2c_smbus_data *data) > +{ > + struct rsb *rsb = i2c_get_adapdata(adap); > + int ret; > + > + if (!data) > + return -EINVAL; > + > + writel(command, rsb->regs + RSB_ADDR); > + writel(RSB_DAR_RTA(addr), rsb->regs + RSB_DAR); > + > + if (read_write == I2C_SMBUS_READ) { > + if (size == I2C_SMBUS_BYTE_DATA) > + writel(RSB_CMD_RD8, rsb->regs + RSB_CMD); > + else > + writel(RSB_CMD_RD16, rsb->regs + RSB_CMD); > + } else { > + if (size == I2C_SMBUS_BYTE_DATA) { > + writel(RSB_CMD_WR8, rsb->regs + RSB_CMD); > + writel(data->byte, rsb->regs + RSB_DATA); > + } else { > + writel(RSB_CMD_WR16, rsb->regs + RSB_CMD); > + writel(data->word, rsb->regs + RSB_DATA); > + } > + } > + > + ret = rsb_run_xfer(rsb); > + if (ret) > + return ret; > + > + if (read_write == I2C_SMBUS_READ) { > + if (size == I2C_SMBUS_BYTE_DATA) > + data->byte = readl(rsb->regs + RSB_DATA); > + else > + data->word = readl(rsb->regs + RSB_DATA); > + } > + > + return 0; > +} > + > +static u32 rsb_functionality(struct i2c_adapter *adap) > +{ > + return I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA; > +} > + > +static const struct i2c_algorithm rsb_algo = { > + .smbus_xfer = rsb_smbus_xfer, > + .functionality = rsb_functionality, > +}; > + > +/* following functions are used strictly during device probe */ > +static void rsb_init_device_mode(struct rsb *rsb) > +{ > + unsigned long expire = jiffies + msecs_to_jiffies(250); > + u32 reg; > + > + /* send init sequence */ > + writel(RSB_DMCR_DEVICE_START | RSB_DMCR_MODE_DATA | > + RSB_DMCR_MODE_REG | RSB_DMCR_DEV_ADDR, rsb->regs + RSB_DMCR); > + do { > + reg = readl(rsb->regs + RSB_DMCR); > + } while (time_before(jiffies, expire) && (reg & RSB_DMCR_DEVICE_START)); > + > + if (reg & RSB_DMCR_DEVICE_START) > + dev_warn(&rsb->adapter.dev, "send init sequence timeout\n"); > + > + /* clear interrupt status bits */ > + writel(readl(rsb->regs + RSB_INTS), rsb->regs + RSB_INTS); > +} > + > +/* 15 valid runtime addresses for RSB slaves */ > +static const u8 rsb_valid_rtaddr[] = { > + 0x17, 0x2d, 0x3a, 0x4e, 0x59, 0x63, 0x74, > + /* > + * Currently this driver only supports 7-bit addresses. > + * The following addresses will be blocked by the i2c core. > + */ > + 0x8b, 0x9c, 0xa6, 0xb1, 0xc5, 0xd2, 0xe8, 0xff, > +}; > + > +static int rsb_check_rt_addr(u8 addr) > +{ > + int i; > + > + for (i = 0; i < ARRAY_SIZE(rsb_valid_rtaddr); i++) > + if (addr == rsb_valid_rtaddr[i]) > + return 0; > + > + return -EINVAL; > +} > + > +/* Scan the device tree for child nodes and set runtime address for them. */ > +static int rsb_set_rt_addrs(struct rsb *rsb) > +{ > + struct device *dev = rsb->adapter.dev.parent; > + struct device_node *child, *np = dev->of_node; > + u32 rt_addr, hw_addr; > + int ret; > + > + if (!np) > + return -EINVAL; > + > + for_each_child_of_node(np, child) { > + /* get hardware address */ > + ret = of_property_read_u32(child, "allwinner,rsb-hw-addr", > + &hw_addr); > + if (ret) { > + dev_warn(dev, "runtime address not given for %s\n", > + of_node_full_name(child)); > + continue; > + } > + > + /* get runtime address */ > + ret = of_property_read_u32(child, "reg", &rt_addr); > + if (ret) { > + dev_warn(dev, "runtime address not given for %s\n", > + of_node_full_name(child)); > + continue; > + } > + > + /* check runtime address */ > + ret = rsb_check_rt_addr(rt_addr); > + if (ret) { > + dev_warn(dev, "runtime address for %s is invalid\n", > + of_node_full_name(child)); > + continue; > + } > + > + /* setup command parameters */ > + writel(RSB_CMD_STRA, rsb->regs + RSB_CMD); > + writel(RSB_DAR_RTA(rt_addr) | RSB_DAR_DA(hw_addr), > + rsb->regs + RSB_DAR); > + > + /* send command */ > + ret = rsb_run_xfer(rsb); > + if (ret) > + dev_warn(dev, "set runtime address failed for %s\n", > + of_node_full_name(child)); > + } > + > + return 0; > +} > + > + > +static const struct of_device_id rsb_of_match_table[] = { > + { .compatible = "allwinner,sun8i-a23-rsb" }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, rsb_of_match_table); > + > +static int rsb_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct device_node *np = dev->of_node; > + unsigned long parent_clk_freq; > + u32 clk_freq = 100000; > + struct resource *r; > + struct rsb *rsb; > + int clk_div; > + int irq; > + int ret; > + > + of_property_read_u32(np, "clock-frequency", &clk_freq); > + if (clk_freq > RSB_MAX_FREQ) { > + dev_err(dev, > + "clock-frequency (%u Hz) is too high (max = 20MHz)", > + clk_freq); > + return -EINVAL; > + } > + > + rsb = devm_kzalloc(dev, sizeof(struct rsb), GFP_KERNEL); > + if (!rsb) > + return -ENOMEM; > + > + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + rsb->regs = devm_ioremap_resource(dev, r); > + if (IS_ERR(rsb->regs)) > + return PTR_ERR(rsb->regs); > + > + strlcpy(rsb->adapter.name, pdev->name, sizeof(rsb->adapter.name)); > + irq = platform_get_irq(pdev, 0); > + if (irq < 0) { > + dev_err(dev, "failed to retrieve irq: %d\n", irq); > + return irq; > + } > + > + rsb->clk = devm_clk_get(dev, NULL); > + if (IS_ERR(rsb->clk)) { > + ret = PTR_ERR(rsb->clk); > + dev_err(dev, "failed to retrieve clk: %d\n", ret); > + return ret; > + } > + > + ret = clk_prepare_enable(rsb->clk); > + if (ret) { > + dev_err(dev, "failed to enable clk: %d\n", ret); > + return ret; > + } > + > + parent_clk_freq = clk_get_rate(rsb->clk); > + > + rsb->rstc = devm_reset_control_get(dev, NULL); > + if (IS_ERR(rsb->rstc)) { > + ret = PTR_ERR(rsb->rstc); > + dev_err(dev, "failed to retrieve reset controller: %d\n", ret); > + goto err_clk_disable; > + } > + > + ret = reset_control_deassert(rsb->rstc); > + if (ret) { > + dev_err(dev, "failed to deassert reset line: %d\n", ret); > + goto err_clk_disable; > + } > + > + init_completion(&rsb->complete); > + strlcpy(rsb->adapter.name, RSB_CTRL_NAME, sizeof(rsb->adapter.name)); > + rsb->adapter.dev.parent = dev; > + rsb->adapter.algo = &rsb_algo; > + rsb->adapter.owner = THIS_MODULE; > + rsb->adapter.dev.of_node = pdev->dev.of_node; > + platform_set_drvdata(pdev, rsb); > + i2c_set_adapdata(&rsb->adapter, rsb); > + > + writel(RSB_CTRL_SOFT_RST, rsb->regs + RSB_CTRL); > + > + clk_div = parent_clk_freq / clk_freq; > + if (!clk_div) { > + dev_warn(dev, > + "clock-frequency is too high, setting it to %lu Hz\n", > + parent_clk_freq); > + clk_div = 1; > + } else if (clk_div > RSB_CCR_MAX_CLK_DIV) { > + dev_warn(dev, > + "clock-frequency is too low, setting it to %lu Hz\n", > + parent_clk_freq / RSB_CCR_MAX_CLK_DIV); > + clk_div = RSB_CCR_MAX_CLK_DIV; > + } > + > + writel(RSB_CCR_SDA_OUT_DELAY(1) | RSB_CCR_CLK_DIV(clk_div), > + rsb->regs + RSB_CCR); > + > + ret = devm_request_irq(dev, irq, rsb_interrupt, 0, RSB_CTRL_NAME, rsb); > + if (ret) { > + dev_err(dev, "can't register interrupt handler irq%d: %d\n", > + irq, ret); > + goto err_reset_assert; > + } > + > + rsb_init_device_mode(rsb); > + > + ret = rsb_set_rt_addrs(rsb); > + if (ret) > + goto err_reset_assert; > + > + ret = i2c_add_adapter(&rsb->adapter); > + if (!ret) > + return 0; > + > +err_reset_assert: > + reset_control_assert(rsb->rstc); > + > +err_clk_disable: > + clk_disable_unprepare(rsb->clk); > + > + return ret; > +} > + > +static int rsb_remove(struct platform_device *dev) > +{ > + struct rsb *rsb = platform_get_drvdata(dev); > + > + i2c_del_adapter(&rsb->adapter); > + reset_control_assert(rsb->rstc); > + clk_disable_unprepare(rsb->clk); > + > + return 0; > +} > + > +static struct platform_driver rsb_driver = { > + .probe = rsb_probe, > + .remove = rsb_remove, > + .driver = { > + .name = "i2c-sunxi-rsb", > + .of_match_table = rsb_of_match_table, > + }, > +}; > +module_platform_driver(rsb_driver); > + > +MODULE_AUTHOR("Chen-Yu Tsai <w...@csie.org>"); > +MODULE_DESCRIPTION("Allwinner RSB driver"); > +MODULE_LICENSE("GPL v2"); > -- > 2.1.4 >
signature.asc
Description: Digital signature