diff options
| author | Yen Lin <yelin@nvidia.com> | 2012-03-06 19:00:23 +0000 | 
|---|---|---|
| committer | Albert ARIBAUD <albert.u.boot@aribaud.net> | 2012-03-29 08:12:50 +0200 | 
| commit | 96a78ac0c445999ce21fb42ecab061479b4d7056 (patch) | |
| tree | 421a551928a999d1bc1bd9c8e72563db72910b46 /drivers/i2c/tegra_i2c.c | |
| parent | 34fa833aeeea498033f2c68b33ed8d57bf951e22 (diff) | |
| download | olio-uboot-2014.01-96a78ac0c445999ce21fb42ecab061479b4d7056.tar.xz olio-uboot-2014.01-96a78ac0c445999ce21fb42ecab061479b4d7056.zip | |
tegra: i2c: Add I2C driver
Add basic i2c driver for Tegra2 with 8- and 16-bit address support.
The driver requires CONFIG_OF_CONTROL to obtain its configuration
from the device tree.
(Simon Glass: sjg@chromium.org modified for upstream)
Signed-off-by: Simon Glass <sjg@chromium.org>
Acked-by: Heiko Schocher <hs@denx.de>
Acked-by: Stephen Warren <swarren@wwwdotorg.org>
Signed-off-by: Tom Warren <twarren@nvidia.com>
Diffstat (limited to 'drivers/i2c/tegra_i2c.c')
| -rw-r--r-- | drivers/i2c/tegra_i2c.c | 569 | 
1 files changed, 569 insertions, 0 deletions
| diff --git a/drivers/i2c/tegra_i2c.c b/drivers/i2c/tegra_i2c.c new file mode 100644 index 000000000..21f689726 --- /dev/null +++ b/drivers/i2c/tegra_i2c.c @@ -0,0 +1,569 @@ +/* + * Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * Copyright (c) 2010-2011 NVIDIA Corporation + *  NVIDIA Corporation <www.nvidia.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <fdtdec.h> +#include <i2c.h> +#include <asm/io.h> +#include <asm/arch/clk_rst.h> +#include <asm/arch/clock.h> +#include <asm/arch/funcmux.h> +#include <asm/arch/gpio.h> +#include <asm/arch/pinmux.h> +#include <asm/arch/tegra_i2c.h> + +DECLARE_GLOBAL_DATA_PTR; + +static unsigned int i2c_bus_num; + +/* Information about i2c controller */ +struct i2c_bus { +	int			id; +	enum periph_id		periph_id; +	int			speed; +	int			pinmux_config; +	struct i2c_control	*control; +	struct i2c_ctlr		*regs; +	int			is_dvc;	/* DVC type, rather than I2C */ +	int			inited;	/* bus is inited */ +}; + +static struct i2c_bus i2c_controllers[TEGRA_I2C_NUM_CONTROLLERS]; + +static void set_packet_mode(struct i2c_bus *i2c_bus) +{ +	u32 config; + +	config = I2C_CNFG_NEW_MASTER_FSM_MASK | I2C_CNFG_PACKET_MODE_MASK; + +	if (i2c_bus->is_dvc) { +		struct dvc_ctlr *dvc = (struct dvc_ctlr *)i2c_bus->regs; + +		writel(config, &dvc->cnfg); +	} else { +		writel(config, &i2c_bus->regs->cnfg); +		/* +		 * program I2C_SL_CNFG.NEWSL to ENABLE. This fixes probe +		 * issues, i.e., some slaves may be wrongly detected. +		 */ +		setbits_le32(&i2c_bus->regs->sl_cnfg, I2C_SL_CNFG_NEWSL_MASK); +	} +} + +static void i2c_reset_controller(struct i2c_bus *i2c_bus) +{ +	/* Reset I2C controller. */ +	reset_periph(i2c_bus->periph_id, 1); + +	/* re-program config register to packet mode */ +	set_packet_mode(i2c_bus); +} + +static void i2c_init_controller(struct i2c_bus *i2c_bus) +{ +	/* +	 * Use PLLP - DP-04508-001_v06 datasheet indicates a divisor of 8 +	 * here, in section 23.3.1, but in fact we seem to need a factor of +	 * 16 to get the right frequency. +	 */ +	clock_start_periph_pll(i2c_bus->periph_id, CLOCK_ID_PERIPH, +			       i2c_bus->speed * 2 * 8); + +	/* Reset I2C controller. */ +	i2c_reset_controller(i2c_bus); + +	/* Configure I2C controller. */ +	if (i2c_bus->is_dvc) {	/* only for DVC I2C */ +		struct dvc_ctlr *dvc = (struct dvc_ctlr *)i2c_bus->regs; + +		setbits_le32(&dvc->ctrl3, DVC_CTRL_REG3_I2C_HW_SW_PROG_MASK); +	} + +	funcmux_select(i2c_bus->periph_id, i2c_bus->pinmux_config); +} + +static void send_packet_headers( +	struct i2c_bus *i2c_bus, +	struct i2c_trans_info *trans, +	u32 packet_id) +{ +	u32 data; + +	/* prepare header1: Header size = 0 Protocol = I2C, pktType = 0 */ +	data = PROTOCOL_TYPE_I2C << PKT_HDR1_PROTOCOL_SHIFT; +	data |= packet_id << PKT_HDR1_PKT_ID_SHIFT; +	data |= i2c_bus->id << PKT_HDR1_CTLR_ID_SHIFT; +	writel(data, &i2c_bus->control->tx_fifo); +	debug("pkt header 1 sent (0x%x)\n", data); + +	/* prepare header2 */ +	data = (trans->num_bytes - 1) << PKT_HDR2_PAYLOAD_SIZE_SHIFT; +	writel(data, &i2c_bus->control->tx_fifo); +	debug("pkt header 2 sent (0x%x)\n", data); + +	/* prepare IO specific header: configure the slave address */ +	data = trans->address << PKT_HDR3_SLAVE_ADDR_SHIFT; + +	/* Enable Read if it is not a write transaction */ +	if (!(trans->flags & I2C_IS_WRITE)) +		data |= PKT_HDR3_READ_MODE_MASK; + +	/* Write I2C specific header */ +	writel(data, &i2c_bus->control->tx_fifo); +	debug("pkt header 3 sent (0x%x)\n", data); +} + +static int wait_for_tx_fifo_empty(struct i2c_control *control) +{ +	u32 count; +	int timeout_us = I2C_TIMEOUT_USEC; + +	while (timeout_us >= 0) { +		count = (readl(&control->fifo_status) & TX_FIFO_EMPTY_CNT_MASK) +				>> TX_FIFO_EMPTY_CNT_SHIFT; +		if (count == I2C_FIFO_DEPTH) +			return 1; +		udelay(10); +		timeout_us -= 10; +	} + +	return 0; +} + +static int wait_for_rx_fifo_notempty(struct i2c_control *control) +{ +	u32 count; +	int timeout_us = I2C_TIMEOUT_USEC; + +	while (timeout_us >= 0) { +		count = (readl(&control->fifo_status) & TX_FIFO_FULL_CNT_MASK) +				>> TX_FIFO_FULL_CNT_SHIFT; +		if (count) +			return 1; +		udelay(10); +		timeout_us -= 10; +	} + +	return 0; +} + +static int wait_for_transfer_complete(struct i2c_control *control) +{ +	int int_status; +	int timeout_us = I2C_TIMEOUT_USEC; + +	while (timeout_us >= 0) { +		int_status = readl(&control->int_status); +		if (int_status & I2C_INT_NO_ACK_MASK) +			return -int_status; +		if (int_status & I2C_INT_ARBITRATION_LOST_MASK) +			return -int_status; +		if (int_status & I2C_INT_XFER_COMPLETE_MASK) +			return 0; + +		udelay(10); +		timeout_us -= 10; +	} + +	return -1; +} + +static int send_recv_packets(struct i2c_bus *i2c_bus, +			     struct i2c_trans_info *trans) +{ +	struct i2c_control *control = i2c_bus->control; +	u32 int_status; +	u32 words; +	u8 *dptr; +	u32 local; +	uchar last_bytes; +	int error = 0; +	int is_write = trans->flags & I2C_IS_WRITE; + +	/* clear status from previous transaction, XFER_COMPLETE, NOACK, etc. */ +	int_status = readl(&control->int_status); +	writel(int_status, &control->int_status); + +	send_packet_headers(i2c_bus, trans, 1); + +	words = DIV_ROUND_UP(trans->num_bytes, 4); +	last_bytes = trans->num_bytes & 3; +	dptr = trans->buf; + +	while (words) { +		u32 *wptr = (u32 *)dptr; + +		if (is_write) { +			/* deal with word alignment */ +			if ((unsigned)dptr & 3) { +				memcpy(&local, dptr, sizeof(u32)); +				writel(local, &control->tx_fifo); +				debug("pkt data sent (0x%x)\n", local); +			} else { +				writel(*wptr, &control->tx_fifo); +				debug("pkt data sent (0x%x)\n", *wptr); +			} +			if (!wait_for_tx_fifo_empty(control)) { +				error = -1; +				goto exit; +			} +		} else { +			if (!wait_for_rx_fifo_notempty(control)) { +				error = -1; +				goto exit; +			} +			/* +			 * for the last word, we read into our local buffer, +			 * in case that caller did not provide enough buffer. +			 */ +			local = readl(&control->rx_fifo); +			if ((words == 1) && last_bytes) +				memcpy(dptr, (char *)&local, last_bytes); +			else if ((unsigned)dptr & 3) +				memcpy(dptr, &local, sizeof(u32)); +			else +				*wptr = local; +			debug("pkt data received (0x%x)\n", local); +		} +		words--; +		dptr += sizeof(u32); +	} + +	if (wait_for_transfer_complete(control)) { +		error = -1; +		goto exit; +	} +	return 0; +exit: +	/* error, reset the controller. */ +	i2c_reset_controller(i2c_bus); + +	return error; +} + +static int tegra2_i2c_write_data(u32 addr, u8 *data, u32 len) +{ +	int error; +	struct i2c_trans_info trans_info; + +	trans_info.address = addr; +	trans_info.buf = data; +	trans_info.flags = I2C_IS_WRITE; +	trans_info.num_bytes = len; +	trans_info.is_10bit_address = 0; + +	error = send_recv_packets(&i2c_controllers[i2c_bus_num], &trans_info); +	if (error) +		debug("tegra2_i2c_write_data: Error (%d) !!!\n", error); + +	return error; +} + +static int tegra2_i2c_read_data(u32 addr, u8 *data, u32 len) +{ +	int error; +	struct i2c_trans_info trans_info; + +	trans_info.address = addr | 1; +	trans_info.buf = data; +	trans_info.flags = 0; +	trans_info.num_bytes = len; +	trans_info.is_10bit_address = 0; + +	error = send_recv_packets(&i2c_controllers[i2c_bus_num], &trans_info); +	if (error) +		debug("tegra2_i2c_read_data: Error (%d) !!!\n", error); + +	return error; +} + +#ifndef CONFIG_OF_CONTROL +#error "Please enable device tree support to use this driver" +#endif + +unsigned int i2c_get_bus_speed(void) +{ +	return i2c_controllers[i2c_bus_num].speed; +} + +int i2c_set_bus_speed(unsigned int speed) +{ +	struct i2c_bus *i2c_bus; + +	i2c_bus = &i2c_controllers[i2c_bus_num]; +	i2c_bus->speed = speed; +	i2c_init_controller(i2c_bus); + +	return 0; +} + +static int i2c_get_config(const void *blob, int node, struct i2c_bus *i2c_bus) +{ +	i2c_bus->regs = (struct i2c_ctlr *)fdtdec_get_addr(blob, node, "reg"); + +	/* +	 * We don't have a binding for pinmux yet. Leave it out for now. So +	 * far no one needs anything other than the default. +	 */ +	i2c_bus->pinmux_config = FUNCMUX_DEFAULT; +	i2c_bus->speed = fdtdec_get_int(blob, node, "clock-frequency", 0); +	i2c_bus->periph_id = clock_decode_periph_id(blob, node); + +	/* +	 * We can't specify the pinmux config in the fdt, so I2C2 will not +	 * work on Seaboard. It normally has no devices on it anyway. +	 * You could add in this little hack if you need to use it. +	 * The correct solution is a pinmux binding in the fdt. +	 * +	 *	if (i2c_bus->periph_id == PERIPH_ID_I2C2) +	 *		i2c_bus->pinmux_config = FUNCMUX_I2C2_PTA; +	 */ +	if (i2c_bus->periph_id == -1) +		return -FDT_ERR_NOTFOUND; + +	return 0; +} + +/* + * Process a list of nodes, adding them to our list of I2C ports. + * + * @param blob		fdt blob + * @param node_list	list of nodes to process (any <=0 are ignored) + * @param count		number of nodes to process + * @param is_dvc	1 if these are DVC ports, 0 if standard I2C + * @return 0 if ok, -1 on error + */ +static int process_nodes(const void *blob, int node_list[], int count, +			 int is_dvc) +{ +	struct i2c_bus *i2c_bus; +	int i; + +	/* build the i2c_controllers[] for each controller */ +	for (i = 0; i < count; i++) { +		int node = node_list[i]; + +		if (node <= 0) +			continue; + +		i2c_bus = &i2c_controllers[i]; +		i2c_bus->id = i; + +		if (i2c_get_config(blob, node, i2c_bus)) { +			printf("i2c_init_board: failed to decode bus %d\n", i); +			return -1; +		} + +		i2c_bus->is_dvc = is_dvc; +		if (is_dvc) { +			i2c_bus->control = +				&((struct dvc_ctlr *)i2c_bus->regs)->control; +		} else { +			i2c_bus->control = &i2c_bus->regs->control; +		} +		debug("%s: controller bus %d at %p, periph_id %d, speed %d: ", +		      is_dvc ? "dvc" : "i2c", i, i2c_bus->regs, +		      i2c_bus->periph_id, i2c_bus->speed); +		i2c_init_controller(i2c_bus); +		debug("ok\n"); +		i2c_bus->inited = 1; + +		/* Mark position as used */ +		node_list[i] = -1; +	} + +	return 0; +} + +/* Sadly there is no error return from this function */ +void i2c_init_board(void) +{ +	int node_list[TEGRA_I2C_NUM_CONTROLLERS]; +	const void *blob = gd->fdt_blob; +	int count; + +	/* First get the normal i2c ports */ +	count = fdtdec_find_aliases_for_id(blob, "i2c", +			COMPAT_NVIDIA_TEGRA20_I2C, node_list, +			TEGRA_I2C_NUM_CONTROLLERS); +	if (process_nodes(blob, node_list, count, 0)) +		return; + +	/* Now look for dvc ports */ +	count = fdtdec_add_aliases_for_id(blob, "i2c", +			COMPAT_NVIDIA_TEGRA20_DVC, node_list, +			TEGRA_I2C_NUM_CONTROLLERS); +	if (process_nodes(blob, node_list, count, 1)) +		return; +} + +void i2c_init(int speed, int slaveaddr) +{ +	/* This will override the speed selected in the fdt for that port */ +	debug("i2c_init(speed=%u, slaveaddr=0x%x)\n", speed, slaveaddr); +	i2c_set_bus_speed(speed); +} + +/* i2c write version without the register address */ +int i2c_write_data(uchar chip, uchar *buffer, int len) +{ +	int rc; + +	debug("i2c_write_data: chip=0x%x, len=0x%x\n", chip, len); +	debug("write_data: "); +	/* use rc for counter */ +	for (rc = 0; rc < len; ++rc) +		debug(" 0x%02x", buffer[rc]); +	debug("\n"); + +	/* Shift 7-bit address over for lower-level i2c functions */ +	rc = tegra2_i2c_write_data(chip << 1, buffer, len); +	if (rc) +		debug("i2c_write_data(): rc=%d\n", rc); + +	return rc; +} + +/* i2c read version without the register address */ +int i2c_read_data(uchar chip, uchar *buffer, int len) +{ +	int rc; + +	debug("inside i2c_read_data():\n"); +	/* Shift 7-bit address over for lower-level i2c functions */ +	rc = tegra2_i2c_read_data(chip << 1, buffer, len); +	if (rc) { +		debug("i2c_read_data(): rc=%d\n", rc); +		return rc; +	} + +	debug("i2c_read_data: "); +	/* reuse rc for counter*/ +	for (rc = 0; rc < len; ++rc) +		debug(" 0x%02x", buffer[rc]); +	debug("\n"); + +	return 0; +} + +/* Probe to see if a chip is present. */ +int i2c_probe(uchar chip) +{ +	int rc; +	uchar reg; + +	debug("i2c_probe: addr=0x%x\n", chip); +	reg = 0; +	rc = i2c_write_data(chip, ®, 1); +	if (rc) { +		debug("Error probing 0x%x.\n", chip); +		return 1; +	} +	return 0; +} + +static int i2c_addr_ok(const uint addr, const int alen) +{ +	/* We support 7 or 10 bit addresses, so one or two bytes each */ +	return alen == 1 || alen == 2; +} + +/* Read bytes */ +int i2c_read(uchar chip, uint addr, int alen, uchar *buffer, int len) +{ +	uint offset; +	int i; + +	debug("i2c_read: chip=0x%x, addr=0x%x, len=0x%x\n", +				chip, addr, len); +	if (!i2c_addr_ok(addr, alen)) { +		debug("i2c_read: Bad address %x.%d.\n", addr, alen); +		return 1; +	} +	for (offset = 0; offset < len; offset++) { +		if (alen) { +			uchar data[alen]; +			for (i = 0; i < alen; i++) { +				data[alen - i - 1] = +					(addr + offset) >> (8 * i); +			} +			if (i2c_write_data(chip, data, alen)) { +				debug("i2c_read: error sending (0x%x)\n", +					addr); +				return 1; +			} +		} +		if (i2c_read_data(chip, buffer + offset, 1)) { +			debug("i2c_read: error reading (0x%x)\n", addr); +			return 1; +		} +	} + +	return 0; +} + +/* Write bytes */ +int i2c_write(uchar chip, uint addr, int alen, uchar *buffer, int len) +{ +	uint offset; +	int i; + +	debug("i2c_write: chip=0x%x, addr=0x%x, len=0x%x\n", +				chip, addr, len); +	if (!i2c_addr_ok(addr, alen)) { +		debug("i2c_write: Bad address %x.%d.\n", addr, alen); +		return 1; +	} +	for (offset = 0; offset < len; offset++) { +		uchar data[alen + 1]; +		for (i = 0; i < alen; i++) +			data[alen - i - 1] = (addr + offset) >> (8 * i); +		data[alen] = buffer[offset]; +		if (i2c_write_data(chip, data, alen + 1)) { +			debug("i2c_write: error sending (0x%x)\n", addr); +			return 1; +		} +	} + +	return 0; +} + +#if defined(CONFIG_I2C_MULTI_BUS) +/* + * Functions for multiple I2C bus handling + */ +unsigned int i2c_get_bus_num(void) +{ +	return i2c_bus_num; +} + +int i2c_set_bus_num(unsigned int bus) +{ +	if (bus >= TEGRA_I2C_NUM_CONTROLLERS || !i2c_controllers[bus].inited) +		return -1; +	i2c_bus_num = bus; + +	return 0; +} +#endif |