All of lore.kernel.org
 help / color / mirror / Atom feed
From: Heiko Schocher invitel <heiko.schocher@invitel.hu>
To: u-boot@lists.denx.de
Subject: [U-Boot] [PATCH v3 1/2] i2c: atmel: add i2c driver
Date: Wed, 29 Jun 2016 08:10:58 +0200	[thread overview]
Message-ID: <57736672.10801@invitel.hu> (raw)
In-Reply-To: <1466400159-24233-2-git-send-email-songjun.wu@atmel.com>

Hello Songjun Wu,

Am 20.06.2016 um 07:22 schrieb Songjun Wu:
> Add i2c driver.
>
> Signed-off-by: Songjun Wu <songjun.wu@atmel.com>
> ---
>
> Changes in v3:
> - Update the clk API.
>
> Changes in v2:
> - Add code to get and enable clock.
>
>   drivers/i2c/Kconfig    |  10 ++
>   drivers/i2c/Makefile   |   1 +
>   drivers/i2c/at91_i2c.c | 338 +++++++++++++++++++++++++++++++++++++++++++++++++
>   drivers/i2c/at91_i2c.h |  77 +++++++++++
>   4 files changed, 426 insertions(+)
>   create mode 100644 drivers/i2c/at91_i2c.c
>   create mode 100644 drivers/i2c/at91_i2c.h

Thanks!

Reviewed-by: Heiko Schocher <hs@denx.de>
Acked-by: Heiko Schocher <hs@denx.de>

bye,
Heiko
>
> diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
> index 6e22bba..f6a613c 100644
> --- a/drivers/i2c/Kconfig
> +++ b/drivers/i2c/Kconfig
> @@ -58,6 +58,16 @@ config DM_I2C_GPIO
>   	  bindings are supported.
>   	  Binding info: doc/device-tree-bindings/i2c/i2c-gpio.txt
>
> +config SYS_I2C_AT91
> +	bool "Atmel I2C driver"
> +	depends on DM_I2C && ARCH_AT91
> +	help
> +	  Add support for the Atmel I2C driver. A serious problem is that there
> +	  is no documented way to issue repeated START conditions for more than
> +	  two messages, as needed to support combined I2C messages. Use the
> +	  i2c-gpio driver unless your system can cope with this limitation.
> +	  Binding info: doc/device-tree-bindings/i2c/i2c-at91.txt
> +
>   config SYS_I2C_FSL
>          bool "Freescale I2C bus driver"
>          depends on DM_I2C
> diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
> index 167424d..c5362a5 100644
> --- a/drivers/i2c/Makefile
> +++ b/drivers/i2c/Makefile
> @@ -16,6 +16,7 @@ obj-$(CONFIG_PCA9564_I2C) += pca9564_i2c.o
>   obj-$(CONFIG_TSI108_I2C) += tsi108_i2c.o
>   obj-$(CONFIG_SH_SH7734_I2C) += sh_sh7734_i2c.o
>   obj-$(CONFIG_SYS_I2C) += i2c_core.o
> +obj-$(CONFIG_SYS_I2C_AT91) += at91_i2c.o
>   obj-$(CONFIG_SYS_I2C_CADENCE) += i2c-cdns.o
>   obj-$(CONFIG_SYS_I2C_DAVINCI) += davinci_i2c.o
>   obj-$(CONFIG_SYS_I2C_DW) += designware_i2c.o
> diff --git a/drivers/i2c/at91_i2c.c b/drivers/i2c/at91_i2c.c
> new file mode 100644
> index 0000000..8e9c3ad
> --- /dev/null
> +++ b/drivers/i2c/at91_i2c.c
> @@ -0,0 +1,338 @@
> +/*
> + * Atmel I2C driver.
> + *
> + * (C) Copyright 2016 Songjun Wu <songjun.wu@atmel.com>
> + *
> + * SPDX-License-Identifier:	GPL-2.0+
> + */
> +
> +#include <asm/io.h>
> +#include <common.h>
> +#include <clk_client.h>
> +#include <dm.h>
> +#include <errno.h>
> +#include <fdtdec.h>
> +#include <i2c.h>
> +#include <linux/bitops.h>
> +#include <mach/clk.h>
> +
> +#include "at91_i2c.h"
> +
> +DECLARE_GLOBAL_DATA_PTR;
> +
> +#define I2C_TIMEOUT_MS	100
> +
> +static int at91_wait_for_xfer(struct at91_i2c_bus *bus, u32 status)
> +{
> +	struct at91_i2c_regs *reg = bus->regs;
> +	ulong start_time = get_timer(0);
> +	u32 sr;
> +
> +	bus->status = 0;
> +
> +	do {
> +		sr = readl(&reg->sr);
> +		bus->status |= sr;
> +
> +		if (sr & TWI_SR_NACK)
> +			return -EREMOTEIO;
> +		else if (sr & status)
> +			return 0;
> +	} while (get_timer(start_time) < I2C_TIMEOUT_MS);
> +
> +	return -ETIMEDOUT;
> +}
> +
> +static int at91_i2c_xfer_msg(struct at91_i2c_bus *bus, struct i2c_msg *msg)
> +{
> +	struct at91_i2c_regs *reg = bus->regs;
> +	bool is_read = msg->flags & I2C_M_RD;
> +	u32 i;
> +	int ret = 0;
> +
> +	readl(&reg->sr);
> +	if (is_read) {
> +		writel(TWI_CR_START, &reg->cr);
> +
> +		for (i = 0; !ret && i < (msg->len - 1); i++) {
> +			ret = at91_wait_for_xfer(bus, TWI_SR_RXRDY);
> +			msg->buf[i] = readl(&reg->rhr);
> +		}
> +
> +		if (ret)
> +			goto error;
> +
> +		writel(TWI_CR_STOP, &reg->cr);
> +
> +		ret = at91_wait_for_xfer(bus, TWI_SR_RXRDY);
> +		if (ret)
> +			goto error;
> +
> +		msg->buf[i] = readl(&reg->rhr);
> +
> +	} else {
> +		writel(msg->buf[0], &reg->thr);
> +		for (i = 1; !ret && (i < msg->len); i++) {
> +			writel(msg->buf[i], &reg->thr);
> +			ret = at91_wait_for_xfer(bus, TWI_SR_TXRDY);
> +		}
> +
> +		if (ret)
> +			goto error;
> +
> +		writel(TWI_CR_STOP, &reg->cr);
> +	}
> +
> +	if (!ret)
> +		ret = at91_wait_for_xfer(bus, TWI_SR_TXCOMP);
> +
> +	if (ret)
> +		goto error;
> +
> +	if (bus->status & (TWI_SR_OVRE | TWI_SR_UNRE | TWI_SR_LOCK)) {
> +		ret = -EIO;
> +		goto error;
> +	}
> +
> +	return 0;
> +
> +error:
> +	if (bus->status & TWI_SR_LOCK)
> +		writel(TWI_CR_LOCKCLR, &reg->cr);
> +
> +	return ret;
> +}
> +
> +static int at91_i2c_xfer(struct udevice *dev, struct i2c_msg *msg, int nmsgs)
> +{
> +	struct at91_i2c_bus *bus = dev_get_priv(dev);
> +	struct at91_i2c_regs *reg = bus->regs;
> +	struct i2c_msg *m_start = msg;
> +	bool is_read;
> +	u32 int_addr_flag = 0;
> +	int ret = 0;
> +
> +	if (nmsgs == 2) {
> +		int internal_address = 0;
> +		int i;
> +
> +		/* 1st msg is put into the internal address, start with 2nd */
> +		m_start = &msg[1];
> +
> +		/* the max length of internal address is 3 bytes */
> +		if (msg->len > 3)
> +			return -EFAULT;
> +
> +		for (i = 0; i < msg->len; ++i) {
> +			const unsigned addr = msg->buf[msg->len - 1 - i];
> +
> +			internal_address |= addr << (8 * i);
> +			int_addr_flag += TWI_MMR_IADRSZ_1;
> +		}
> +
> +		writel(internal_address, &reg->iadr);
> +	}
> +
> +	is_read = m_start->flags & I2C_M_RD;
> +
> +	writel((m_start->addr << 16) | int_addr_flag |
> +	       (is_read ? TWI_MMR_MREAD : 0), &reg->mmr);
> +
> +	ret = at91_i2c_xfer_msg(bus, m_start);
> +
> +	return ret;
> +}
> +
> +/*
> + * Calculate symmetric clock as stated in datasheet:
> + * twi_clk = F_MAIN / (2 * (cdiv * (1 << ckdiv) + offset))
> + */
> +static void at91_calc_i2c_clock(struct udevice *dev, int i2c_clk)
> +{
> +	struct at91_i2c_bus *bus = dev_get_priv(dev);
> +	const struct at91_i2c_pdata *pdata = bus->pdata;
> +	int offset = pdata->clk_offset;
> +	int max_ckdiv = pdata->clk_max_div;
> +	int ckdiv, cdiv, div;
> +	unsigned long src_rate;
> +
> +	src_rate = bus->bus_clk_rate;
> +
> +	div = max(0, (int)DIV_ROUND_UP(src_rate, 2 * i2c_clk) - offset);
> +	ckdiv = fls(div >> 8);
> +	cdiv = div >> ckdiv;
> +
> +	if (ckdiv > max_ckdiv) {
> +		ckdiv = max_ckdiv;
> +		cdiv = 255;
> +	}
> +
> +	bus->speed = DIV_ROUND_UP(src_rate,
> +				  (cdiv * (1 << ckdiv) + offset) * 2);
> +
> +	bus->cwgr_val = (ckdiv << 16) | (cdiv << 8) | cdiv;
> +}
> +
> +static int at91_i2c_enable_clk(struct udevice *dev)
> +{
> +	struct at91_i2c_bus *bus = dev_get_priv(dev);
> +	struct udevice *dev_clk;
> +	struct clk clk;
> +	ulong clk_rate;
> +	int periph;
> +	int ret;
> +
> +	ret = clk_get_by_index(dev, 0, &clk);
> +	if (ret)
> +		return -EINVAL;
> +
> +	periph = fdtdec_get_uint(gd->fdt_blob, clk.dev->of_offset, "reg", -1);
> +	if (periph < 0)
> +		return -EINVAL;
> +
> +	dev_clk = dev_get_parent(clk.dev);
> +	ret = clk_request(dev_clk, &clk);
> +	if (ret)
> +		return ret;
> +
> +	clk.id = periph;
> +	ret = clk_enable(&clk);
> +	if (ret)
> +		return ret;
> +
> +	ret = clk_get_by_index(dev_clk, 0, &clk);
> +	if (ret)
> +		return ret;
> +
> +	clk_rate = clk_get_rate(&clk);
> +	if (!clk_rate)
> +		return -ENODEV;
> +
> +	bus->bus_clk_rate = clk_rate;
> +
> +	clk_free(&clk);
> +
> +	return 0;
> +}
> +
> +static int at91_i2c_probe(struct udevice *dev, uint chip, uint chip_flags)
> +{
> +	struct at91_i2c_bus *bus = dev_get_priv(dev);
> +	struct at91_i2c_regs *reg = bus->regs;
> +	int ret;
> +
> +	ret = at91_i2c_enable_clk(dev);
> +	if (ret)
> +		return ret;
> +
> +	writel(TWI_CR_SWRST, &reg->cr);
> +
> +	at91_calc_i2c_clock(dev, bus->clock_frequency);
> +
> +	writel(bus->cwgr_val, &reg->cwgr);
> +	writel(TWI_CR_MSEN, &reg->cr);
> +	writel(TWI_CR_SVDIS, &reg->cr);
> +
> +	return 0;
> +}
> +
> +static int at91_i2c_set_bus_speed(struct udevice *dev, unsigned int speed)
> +{
> +	struct at91_i2c_bus *bus = dev_get_priv(dev);
> +
> +	at91_calc_i2c_clock(dev, speed);
> +
> +	writel(bus->cwgr_val, &bus->regs->cwgr);
> +
> +	return 0;
> +}
> +
> +int at91_i2c_get_bus_speed(struct udevice *dev)
> +{
> +	struct at91_i2c_bus *bus = dev_get_priv(dev);
> +
> +	return bus->speed;
> +}
> +
> +static int at91_i2c_ofdata_to_platdata(struct udevice *dev)
> +{
> +	const void *blob = gd->fdt_blob;
> +	struct at91_i2c_bus *bus = dev_get_priv(dev);
> +	int node = dev->of_offset;
> +
> +	bus->regs = (struct at91_i2c_regs *)dev_get_addr(dev);
> +	bus->pdata = (struct at91_i2c_pdata *)dev_get_driver_data(dev);
> +	bus->clock_frequency = fdtdec_get_int(blob, node,
> +					      "clock-frequency", 100000);
> +
> +	return 0;
> +}
> +
> +static const struct dm_i2c_ops at91_i2c_ops = {
> +	.xfer		= at91_i2c_xfer,
> +	.probe_chip	= at91_i2c_probe,
> +	.set_bus_speed	= at91_i2c_set_bus_speed,
> +	.get_bus_speed	= at91_i2c_get_bus_speed,
> +};
> +
> +static const struct at91_i2c_pdata at91rm9200_config = {
> +	.clk_max_div = 5,
> +	.clk_offset = 3,
> +};
> +
> +static const struct at91_i2c_pdata at91sam9261_config = {
> +	.clk_max_div = 5,
> +	.clk_offset = 4,
> +};
> +
> +static const struct at91_i2c_pdata at91sam9260_config = {
> +	.clk_max_div = 7,
> +	.clk_offset = 4,
> +};
> +
> +static const struct at91_i2c_pdata at91sam9g20_config = {
> +	.clk_max_div = 7,
> +	.clk_offset = 4,
> +};
> +
> +static const struct at91_i2c_pdata at91sam9g10_config = {
> +	.clk_max_div = 7,
> +	.clk_offset = 4,
> +};
> +
> +static const struct at91_i2c_pdata at91sam9x5_config = {
> +	.clk_max_div = 7,
> +	.clk_offset = 4,
> +};
> +
> +static const struct at91_i2c_pdata sama5d4_config = {
> +	.clk_max_div = 7,
> +	.clk_offset = 4,
> +};
> +
> +static const struct at91_i2c_pdata sama5d2_config = {
> +	.clk_max_div = 7,
> +	.clk_offset = 3,
> +};
> +
> +static const struct udevice_id at91_i2c_ids[] = {
> +{ .compatible = "atmel,at91rm9200-i2c", .data = (long)&at91rm9200_config },
> +{ .compatible = "atmel,at91sam9260-i2c", .data = (long)&at91sam9260_config },
> +{ .compatible = "atmel,at91sam9261-i2c", .data = (long)&at91sam9261_config },
> +{ .compatible = "atmel,at91sam9g20-i2c", .data = (long)&at91sam9g20_config },
> +{ .compatible = "atmel,at91sam9g10-i2c", .data = (long)&at91sam9g10_config },
> +{ .compatible = "atmel,at91sam9x5-i2c", .data = (long)&at91sam9x5_config },
> +{ .compatible = "atmel,sama5d4-i2c", .data = (long)&sama5d4_config },
> +{ .compatible = "atmel,sama5d2-i2c", .data = (long)&sama5d2_config },
> +{ }
> +};
> +
> +U_BOOT_DRIVER(i2c_at91) = {
> +	.name	= "i2c_at91",
> +	.id	= UCLASS_I2C,
> +	.of_match = at91_i2c_ids,
> +	.ofdata_to_platdata = at91_i2c_ofdata_to_platdata,
> +	.per_child_auto_alloc_size = sizeof(struct dm_i2c_chip),
> +	.priv_auto_alloc_size = sizeof(struct at91_i2c_bus),
> +	.ops	= &at91_i2c_ops,
> +};
> diff --git a/drivers/i2c/at91_i2c.h b/drivers/i2c/at91_i2c.h
> new file mode 100644
> index 0000000..87f02bf
> --- /dev/null
> +++ b/drivers/i2c/at91_i2c.h
> @@ -0,0 +1,77 @@
> +#ifndef _AT91_I2C_H
> +#define _AT91_I2C_H
> +
> +#define	TWI_CR_START		BIT(0)	/* Send a Start Condition */
> +#define	TWI_CR_MSEN		BIT(2)	/* Master Transfer Enable */
> +#define	TWI_CR_STOP		BIT(1)	/* Send a Stop Condition */
> +#define	TWI_CR_SVDIS		BIT(5)	/* Slave Transfer Disable */
> +#define	TWI_CR_SWRST		BIT(7)	/* Software Reset */
> +#define	TWI_CR_ACMEN		BIT(16) /* Alternative Command Mode Enable */
> +#define	TWI_CR_ACMDIS		BIT(17) /* Alternative Command Mode Disable */
> +#define	TWI_CR_LOCKCLR		BIT(26) /* Lock Clear */
> +
> +#define	TWI_MMR_MREAD		BIT(12) /* Master Read Direction */
> +#define	TWI_MMR_IADRSZ_1	BIT(8)	/* Internal Device Address Size */
> +
> +#define	TWI_SR_TXCOMP		BIT(0)	/* Transmission Complete */
> +#define	TWI_SR_RXRDY		BIT(1)	/* Receive Holding Register Ready */
> +#define	TWI_SR_TXRDY		BIT(2)	/* Transmit Holding Register Ready */
> +#define	TWI_SR_OVRE		BIT(6)	/* Overrun Error */
> +#define	TWI_SR_UNRE		BIT(7)	/* Underrun Error */
> +#define	TWI_SR_NACK		BIT(8)	/* Not Acknowledged */
> +#define	TWI_SR_LOCK		BIT(23) /* TWI Lock due to Frame Errors */
> +
> +#define	TWI_ACR_DATAL(len)	((len) & 0xff)
> +#define	TWI_ACR_DIR_READ	BIT(8)
> +
> +#define	TWI_CWGR_HOLD_MAX	0x1f
> +#define	TWI_CWGR_HOLD(x)	(((x) & TWI_CWGR_HOLD_MAX) << 24)
> +
> +struct at91_i2c_regs {
> +	u32 cr;
> +	u32 mmr;
> +	u32 smr;
> +	u32 iadr;
> +	u32 cwgr;
> +	u32 rev_0[3];
> +	u32 sr;
> +	u32 ier;
> +	u32 idr;
> +	u32 imr;
> +	u32 rhr;
> +	u32 thr;
> +	u32 smbtr;
> +	u32 rev_1;
> +	u32 acr;
> +	u32 filtr;
> +	u32 rev_2;
> +	u32 swmr;
> +	u32 fmr;
> +	u32 flr;
> +	u32 rev_3;
> +	u32 fsr;
> +	u32 fier;
> +	u32 fidr;
> +	u32 fimr;
> +	u32 rev_4[29];
> +	u32 wpmr;
> +	u32 wpsr;
> +	u32 rev_5[6];
> +};
> +
> +struct at91_i2c_pdata {
> +	unsigned clk_max_div;
> +	unsigned clk_offset;
> +};
> +
> +struct at91_i2c_bus {
> +	struct at91_i2c_regs *regs;
> +	u32 status;
> +	ulong bus_clk_rate;
> +	u32 clock_frequency;
> +	u32 speed;
> +	u32 cwgr_val;
> +	const struct at91_i2c_pdata *pdata;
> +};
> +
> +#endif
>
-- 
DENX Software Engineering GmbH, Managing Director: Wolfgang Denk
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany

  reply	other threads:[~2016-06-29  6:10 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-06-20  5:22 [U-Boot] [PATCH v3 0/2] i2c: atmel: add driver for Atmel i2c platform Songjun Wu
2016-06-20  5:22 ` [U-Boot] [PATCH v3 1/2] i2c: atmel: add i2c driver Songjun Wu
2016-06-29  6:10   ` Heiko Schocher invitel [this message]
2016-08-15 20:15   ` [U-Boot] [U-Boot,v3,1/2] " Andreas Bießmann
2016-06-20  5:22 ` [U-Boot] [PATCH v3 2/2] i2c: atmel: DT binding for " Songjun Wu
2016-06-29  6:11   ` Heiko Schocher invitel
2016-08-15 20:16   ` [U-Boot] [U-Boot,v3,2/2] " Andreas Bießmann

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=57736672.10801@invitel.hu \
    --to=heiko.schocher@invitel.hu \
    --cc=u-boot@lists.denx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.