diff options
Diffstat (limited to 'drivers/i2c')
| -rw-r--r-- | drivers/i2c/Kconfig | 8 | ||||
| -rw-r--r-- | drivers/i2c/Makefile | 1 | ||||
| -rw-r--r-- | drivers/i2c/i2c-uniphier.c | 239 | 
3 files changed, 248 insertions, 0 deletions
| diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index 96715d0eedb..6a479efd7c6 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -4,3 +4,11 @@ config DM_I2C  	help  	  If you want to use driver model for I2C drivers, say Y.  	  To use legacy I2C drivers, say N. + +config SYS_I2C_UNIPHIER +	bool "UniPhier I2C driver" +	depends on ARCH_UNIPHIER && DM_I2C +	default y +	help +	  Support for Panasonic UniPhier I2C controller driver.  This I2C +	  controller is used on PH1-LD4, PH1-sLD8 or older UniPhier SoCs. diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index 6f3c86c0385..e2fcd24ef3c 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -31,4 +31,5 @@ obj-$(CONFIG_SYS_I2C_SANDBOX) += sandbox_i2c.o i2c-emul-uclass.o  obj-$(CONFIG_SYS_I2C_SH) += sh_i2c.o  obj-$(CONFIG_SYS_I2C_SOFT) += soft_i2c.o  obj-$(CONFIG_SYS_I2C_TEGRA) += tegra_i2c.o +obj-$(CONFIG_SYS_I2C_UNIPHIER) += i2c-uniphier.o  obj-$(CONFIG_SYS_I2C_ZYNQ) += zynq_i2c.o diff --git a/drivers/i2c/i2c-uniphier.c b/drivers/i2c/i2c-uniphier.c new file mode 100644 index 00000000000..bdac1f90799 --- /dev/null +++ b/drivers/i2c/i2c-uniphier.c @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2014 Panasonic Corporation + *   Author: Masahiro Yamada <yamada.m@jp.panasonic.com> + * + * SPDX-License-Identifier:	GPL-2.0+ + */ + +#include <common.h> +#include <linux/types.h> +#include <asm/io.h> +#include <asm/errno.h> +#include <dm/device.h> +#include <dm/root.h> +#include <i2c.h> +#include <fdtdec.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct uniphier_i2c_regs { +	u32 dtrm;			/* data transmission */ +#define I2C_DTRM_STA	(1 << 10) +#define I2C_DTRM_STO	(1 << 9) +#define I2C_DTRM_NACK	(1 << 8) +#define I2C_DTRM_RD	(1 << 0) +	u32 drec;			/* data reception */ +#define I2C_DREC_STS	(1 << 12) +#define I2C_DREC_LRB	(1 << 11) +#define I2C_DREC_LAB	(1 << 9) +	u32 myad;			/* slave address */ +	u32 clk;			/* clock frequency control */ +	u32 brst;			/* bus reset */ +#define I2C_BRST_FOEN	(1 << 1) +#define I2C_BRST_BRST	(1 << 0) +	u32 hold;			/* hold time control */ +	u32 bsts;			/* bus status monitor */ +	u32 noise;			/* noise filter control */ +	u32 setup;			/* setup time control */ +}; + +#define IOBUS_FREQ	100000000 + +struct uniphier_i2c_dev { +	struct uniphier_i2c_regs __iomem *regs;	/* register base */ +	unsigned long input_clk;	/* master clock (Hz) */ +	unsigned long wait_us;		/* wait for every byte transfer (us) */ +}; + +static int uniphier_i2c_probe(struct udevice *dev) +{ +	fdt_addr_t addr; +	fdt_size_t size; +	struct uniphier_i2c_dev *priv = dev_get_priv(dev); + +	addr = fdtdec_get_addr_size(gd->fdt_blob, dev->of_offset, "reg", &size); + +	priv->regs = map_sysmem(addr, size); + +	if (!priv->regs) +		return -ENOMEM; + +	priv->input_clk = IOBUS_FREQ; + +	/* deassert reset */ +	writel(0x3, &priv->regs->brst); + +	return 0; +} + +static int uniphier_i2c_remove(struct udevice *dev) +{ +	struct uniphier_i2c_dev *priv = dev_get_priv(dev); + +	unmap_sysmem(priv->regs); + +	return 0; +} + +static int uniphier_i2c_child_pre_probe(struct udevice *dev) +{ +	struct dm_i2c_chip *i2c_chip = dev_get_parentdata(dev); + +	if (dev->of_offset == -1) +		return 0; +	return i2c_chip_ofdata_to_platdata(gd->fdt_blob, dev->of_offset, +					   i2c_chip); +} + +static int send_and_recv_byte(struct uniphier_i2c_dev *dev, u32 dtrm) +{ +	writel(dtrm, &dev->regs->dtrm); + +	/* +	 * This controller only provides interruption to inform the completion +	 * of each byte transfer.  (No status register to poll it.) +	 * Unfortunately, U-Boot does not have a good support of interrupt. +	 * Wait for a while. +	 */ +	udelay(dev->wait_us); + +	return readl(&dev->regs->drec); +} + +static int send_byte(struct uniphier_i2c_dev *dev, u32 dtrm, bool *stop) +{ +	int ret = 0; +	u32 drec; + +	drec = send_and_recv_byte(dev, dtrm); + +	if (drec & I2C_DREC_LAB) { +		debug("uniphier_i2c: bus arbitration failed\n"); +		*stop = false; +		ret = -EREMOTEIO; +	} +	if (drec & I2C_DREC_LRB) { +		debug("uniphier_i2c: slave did not return ACK\n"); +		ret = -EREMOTEIO; +	} +	return ret; +} + +static int uniphier_i2c_transmit(struct uniphier_i2c_dev *dev, uint addr, +				 uint len, const u8 *buf, bool *stop) +{ +	int ret; + +	debug("%s: addr = %x, len = %d\n", __func__, addr, len); + +	ret = send_byte(dev, I2C_DTRM_STA | I2C_DTRM_NACK | addr << 1, stop); +	if (ret < 0) +		goto fail; + +	while (len--) { +		ret = send_byte(dev, I2C_DTRM_NACK | *buf++, stop); +		if (ret < 0) +			goto fail; +	} + +fail: +	if (*stop) +		writel(I2C_DTRM_STO | I2C_DTRM_NACK, &dev->regs->dtrm); + +	return ret; +} + +static int uniphier_i2c_receive(struct uniphier_i2c_dev *dev, uint addr, +				uint len, u8 *buf, bool *stop) +{ +	int ret; + +	debug("%s: addr = %x, len = %d\n", __func__, addr, len); + +	ret = send_byte(dev, I2C_DTRM_STA | I2C_DTRM_NACK | +			I2C_DTRM_RD | addr << 1, stop); +	if (ret < 0) +		goto fail; + +	while (len--) +		*buf++ = send_and_recv_byte(dev, len ? 0 : I2C_DTRM_NACK); + +fail: +	if (*stop) +		writel(I2C_DTRM_STO | I2C_DTRM_NACK, &dev->regs->dtrm); + +	return ret; +} + +static int uniphier_i2c_xfer(struct udevice *bus, struct i2c_msg *msg, +			     int nmsgs) +{ +	int ret = 0; +	struct uniphier_i2c_dev *dev = dev_get_priv(bus); +	bool stop; + +	for (; nmsgs > 0; nmsgs--, msg++) { +		/* If next message is read, skip the stop condition */ +		stop = nmsgs > 1 && msg[1].flags & I2C_M_RD ? false : true; + +		if (msg->flags & I2C_M_RD) +			ret = uniphier_i2c_receive(dev, msg->addr, msg->len, +						   msg->buf, &stop); +		else +			ret = uniphier_i2c_transmit(dev, msg->addr, msg->len, +						    msg->buf, &stop); + +		if (ret < 0) +			break; +	} + +	return ret; +} + +static int uniphier_i2c_set_bus_speed(struct udevice *bus, unsigned int speed) +{ +	struct uniphier_i2c_dev *priv = dev_get_priv(bus); + +	/* max supported frequency is 400 kHz */ +	if (speed > 400000) +		return -EINVAL; + +	/* bus reset: make sure the bus is idle when change the frequency */ +	writel(0x1, &priv->regs->brst); + +	writel((priv->input_clk / speed / 2 << 16) | (priv->input_clk / speed), +	       &priv->regs->clk); + +	writel(0x3, &priv->regs->brst); + +	/* +	 * Theoretically, each byte can be transferred in +	 * 1000000 * 9 / speed usec.  For safety, wait more than double. +	 */ +	priv->wait_us = 20000000 / speed; + +	return 0; +} + + +static const struct dm_i2c_ops uniphier_i2c_ops = { +	.xfer = uniphier_i2c_xfer, +	.set_bus_speed = uniphier_i2c_set_bus_speed, +}; + +static const struct udevice_id uniphier_i2c_of_match[] = { +	{ .compatible = "panasonic,uniphier-i2c" }, +	{}, +}; + +U_BOOT_DRIVER(uniphier_i2c) = { +	.name = "uniphier-i2c", +	.id = UCLASS_I2C, +	.of_match = uniphier_i2c_of_match, +	.probe = uniphier_i2c_probe, +	.remove = uniphier_i2c_remove, +	.per_child_auto_alloc_size = sizeof(struct dm_i2c_chip), +	.child_pre_probe = uniphier_i2c_child_pre_probe, +	.priv_auto_alloc_size = sizeof(struct uniphier_i2c_dev), +	.ops = &uniphier_i2c_ops, +}; | 
