mirror of
				https://github.com/Ysurac/openmptcprouter.git
				synced 2025-03-09 15:40:20 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			660 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			660 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From 9a8aaa74bef34d4757294f809141dfa9a4e01057 Mon Sep 17 00:00:00 2001
 | |
| From: popcornmix <popcornmix@gmail.com>
 | |
| Date: Wed, 17 Jun 2015 15:44:08 +0100
 | |
| Subject: [PATCH] Add Chris Boot's i2c driver
 | |
| MIME-Version: 1.0
 | |
| Content-Type: text/plain; charset=UTF-8
 | |
| Content-Transfer-Encoding: 8bit
 | |
| 
 | |
| i2c-bcm2708: fixed baudrate
 | |
| 
 | |
| Fixed issue where the wrong CDIV value was set for baudrates below 3815 Hz (for 250MHz bus clock).
 | |
| In that case the computed CDIV value was more than 0xffff. However the CDIV register width is only 16 bits.
 | |
| This resulted in incorrect setting of CDIV and higher baudrate than intended.
 | |
| Example: 3500Hz -> CDIV=0x11704 -> CDIV(16bit)=0x1704 -> 42430Hz
 | |
| After correction: 3500Hz -> CDIV=0x11704 -> CDIV(16bit)=0xffff -> 3815Hz
 | |
| The correct baudrate is shown in the log after the cdiv > 0xffff correction.
 | |
| 
 | |
| Perform I2C combined transactions when possible
 | |
| 
 | |
| Perform I2C combined transactions whenever possible, within the
 | |
| restrictions of the Broadcomm Serial Controller.
 | |
| 
 | |
| Disable DONE interrupt during TA poll
 | |
| 
 | |
| Prevent interrupt from being triggered if poll is missed and transfer
 | |
| starts and finishes.
 | |
| 
 | |
| i2c: Make combined transactions optional and disabled by default
 | |
| 
 | |
| i2c: bcm2708: add device tree support
 | |
| 
 | |
| Add DT support to driver and add to .dtsi file.
 | |
| Setup pins in .dts file.
 | |
| i2c is disabled by default.
 | |
| 
 | |
| Signed-off-by: Noralf Tronnes <notro@tronnes.org>
 | |
| 
 | |
| bcm2708: don't register i2c controllers when using DT
 | |
| 
 | |
| The devices for the i2c controllers are in the Device Tree.
 | |
| Only register devices when not using DT.
 | |
| 
 | |
| Signed-off-by: Noralf Tronnes <notro@tronnes.org>
 | |
| 
 | |
| I2C: Only register the I2C device for the current board revision
 | |
| 
 | |
| i2c_bcm2708: Fix clock reference counting
 | |
| 
 | |
| Fix grabbing lock from atomic context in i2c driver
 | |
| 
 | |
| 2 main changes:
 | |
| - check for timeouts in the bcm2708_bsc_setup function as indicated by this comment:
 | |
|       /* poll for transfer start bit (should only take 1-20 polls) */
 | |
|   This implies that the setup function can now fail so account for this everywhere it's called
 | |
| - Removed the clk_get_rate call from inside the setup function as it locks a mutex and that's not ok since we call it from under a spin lock.
 | |
| 
 | |
| i2c-bcm2708: When using DT, leave the GPIO setup to pinctrl
 | |
| 
 | |
| i2c-bcm2708: Increase timeouts to allow larger transfers
 | |
| 
 | |
| Use the timeout value provided by the I2C_TIMEOUT ioctl when waiting
 | |
| for completion. The default timeout is 1 second.
 | |
| 
 | |
| See: https://github.com/raspberrypi/linux/issues/260
 | |
| 
 | |
| i2c-bcm2708/BCM270X_DT: Add support for I2C2
 | |
| 
 | |
| The third I2C bus (I2C2) is normally reserved for HDMI use. Careless
 | |
| use of this bus can break an attached display - use with caution.
 | |
| 
 | |
| It is recommended to disable accesses by VideoCore by setting
 | |
| hdmi_ignore_edid=1 or hdmi_edid_file=1 in config.txt.
 | |
| 
 | |
| The interface is disabled by default - enable using the
 | |
| i2c2_iknowwhatimdoing DT parameter.
 | |
| 
 | |
| bcm2708-spi: Don't use static pin configuration with DT
 | |
| 
 | |
| Also remove superfluous error checking - the SPI framework ensures the
 | |
| validity of the chip_select value.
 | |
| 
 | |
| i2c-bcm2708: Remove non-DT support
 | |
| 
 | |
| Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
 | |
| 
 | |
| Set the BSC_CLKT clock streching timeout to 35ms as per SMBus specs.
 | |
| 
 | |
| Fixes i2c_bcm2708: Write to FIFO correctly - v2 (#1574)
 | |
| 
 | |
| * i2c: fix i2c_bcm2708: Clear FIFO before sending data
 | |
| 
 | |
| Make sure FIFO gets cleared before trying to send
 | |
| data in case of a repeated start (COMBINED=Y).
 | |
| 
 | |
| * i2c: fix i2c_bcm2708: Only write to FIFO when not full
 | |
| 
 | |
| Check if FIFO can accept data before writing.
 | |
| To avoid a peripheral read on the last iteration of a loop,
 | |
| both bcm2708_bsc_fifo_fill and ~drain are changed as well.
 | |
| ---
 | |
|  drivers/i2c/busses/Kconfig       |  19 ++
 | |
|  drivers/i2c/busses/Makefile      |   2 +
 | |
|  drivers/i2c/busses/i2c-bcm2708.c | 512 +++++++++++++++++++++++++++++++
 | |
|  3 files changed, 533 insertions(+)
 | |
|  create mode 100644 drivers/i2c/busses/i2c-bcm2708.c
 | |
| 
 | |
| --- a/drivers/i2c/busses/Kconfig
 | |
| +++ b/drivers/i2c/busses/Kconfig
 | |
| @@ -16,6 +16,25 @@ config I2C_CCGX_UCSI
 | |
|  	  for Cypress CCGx Type-C controller. Individual bus drivers
 | |
|  	  need to select this one on demand.
 | |
|  
 | |
| +config I2C_BCM2708
 | |
| +	tristate "BCM2708 BSC"
 | |
| +	depends on ARCH_BCM2835
 | |
| +	help
 | |
| +	  Enabling this option will add BSC (Broadcom Serial Controller)
 | |
| +	  support for the BCM2708. BSC is a Broadcom proprietary bus compatible
 | |
| +	  with I2C/TWI/SMBus.
 | |
| +
 | |
| +config I2C_BCM2708_BAUDRATE
 | |
| +	prompt "BCM2708 I2C baudrate"
 | |
| +	depends on I2C_BCM2708
 | |
| +	int
 | |
| +	default 100000
 | |
| +	help
 | |
| +	  Set the I2C baudrate. This will alter the default value. A
 | |
| +	  different baudrate can be set by using a module parameter as well. If
 | |
| +	  no parameter is provided when loading, this is the value that will be
 | |
| +	  used.
 | |
| +
 | |
|  config I2C_ALI1535
 | |
|  	tristate "ALI 1535"
 | |
|  	depends on PCI
 | |
| --- a/drivers/i2c/busses/Makefile
 | |
| +++ b/drivers/i2c/busses/Makefile
 | |
| @@ -3,6 +3,8 @@
 | |
|  # Makefile for the i2c bus drivers.
 | |
|  #
 | |
|  
 | |
| +obj-$(CONFIG_I2C_BCM2708)	+= i2c-bcm2708.o
 | |
| +
 | |
|  # ACPI drivers
 | |
|  obj-$(CONFIG_I2C_SCMI)		+= i2c-scmi.o
 | |
|  
 | |
| --- /dev/null
 | |
| +++ b/drivers/i2c/busses/i2c-bcm2708.c
 | |
| @@ -0,0 +1,512 @@
 | |
| +/*
 | |
| + * Driver for Broadcom BCM2708 BSC Controllers
 | |
| + *
 | |
| + * Copyright (C) 2012 Chris Boot & Frank Buss
 | |
| + *
 | |
| + * This driver is inspired by:
 | |
| + * i2c-ocores.c, by Peter Korsgaard <jacmet@sunsite.dk>
 | |
| + *
 | |
| + * 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 <linux/kernel.h>
 | |
| +#include <linux/module.h>
 | |
| +#include <linux/spinlock.h>
 | |
| +#include <linux/clk.h>
 | |
| +#include <linux/err.h>
 | |
| +#include <linux/of.h>
 | |
| +#include <linux/platform_device.h>
 | |
| +#include <linux/io.h>
 | |
| +#include <linux/slab.h>
 | |
| +#include <linux/i2c.h>
 | |
| +#include <linux/interrupt.h>
 | |
| +#include <linux/sched.h>
 | |
| +#include <linux/wait.h>
 | |
| +
 | |
| +/* BSC register offsets */
 | |
| +#define BSC_C			0x00
 | |
| +#define BSC_S			0x04
 | |
| +#define BSC_DLEN		0x08
 | |
| +#define BSC_A			0x0c
 | |
| +#define BSC_FIFO		0x10
 | |
| +#define BSC_DIV			0x14
 | |
| +#define BSC_DEL			0x18
 | |
| +#define BSC_CLKT		0x1c
 | |
| +
 | |
| +/* Bitfields in BSC_C */
 | |
| +#define BSC_C_I2CEN		0x00008000
 | |
| +#define BSC_C_INTR		0x00000400
 | |
| +#define BSC_C_INTT		0x00000200
 | |
| +#define BSC_C_INTD		0x00000100
 | |
| +#define BSC_C_ST		0x00000080
 | |
| +#define BSC_C_CLEAR_1		0x00000020
 | |
| +#define BSC_C_CLEAR_2		0x00000010
 | |
| +#define BSC_C_READ		0x00000001
 | |
| +
 | |
| +/* Bitfields in BSC_S */
 | |
| +#define BSC_S_CLKT		0x00000200
 | |
| +#define BSC_S_ERR		0x00000100
 | |
| +#define BSC_S_RXF		0x00000080
 | |
| +#define BSC_S_TXE		0x00000040
 | |
| +#define BSC_S_RXD		0x00000020
 | |
| +#define BSC_S_TXD		0x00000010
 | |
| +#define BSC_S_RXR		0x00000008
 | |
| +#define BSC_S_TXW		0x00000004
 | |
| +#define BSC_S_DONE		0x00000002
 | |
| +#define BSC_S_TA		0x00000001
 | |
| +
 | |
| +#define I2C_WAIT_LOOP_COUNT	200
 | |
| +
 | |
| +#define DRV_NAME		"bcm2708_i2c"
 | |
| +
 | |
| +static unsigned int baudrate;
 | |
| +module_param(baudrate, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
 | |
| +MODULE_PARM_DESC(baudrate, "The I2C baudrate");
 | |
| +
 | |
| +static bool combined = false;
 | |
| +module_param(combined, bool, 0644);
 | |
| +MODULE_PARM_DESC(combined, "Use combined transactions");
 | |
| +
 | |
| +struct bcm2708_i2c {
 | |
| +	struct i2c_adapter adapter;
 | |
| +
 | |
| +	spinlock_t lock;
 | |
| +	void __iomem *base;
 | |
| +	int irq;
 | |
| +	struct clk *clk;
 | |
| +	u32 cdiv;
 | |
| +	u32 clk_tout;
 | |
| +
 | |
| +	struct completion done;
 | |
| +
 | |
| +	struct i2c_msg *msg;
 | |
| +	int pos;
 | |
| +	int nmsgs;
 | |
| +	bool error;
 | |
| +};
 | |
| +
 | |
| +static inline u32 bcm2708_rd(struct bcm2708_i2c *bi, unsigned reg)
 | |
| +{
 | |
| +	return readl(bi->base + reg);
 | |
| +}
 | |
| +
 | |
| +static inline void bcm2708_wr(struct bcm2708_i2c *bi, unsigned reg, u32 val)
 | |
| +{
 | |
| +	writel(val, bi->base + reg);
 | |
| +}
 | |
| +
 | |
| +static inline void bcm2708_bsc_reset(struct bcm2708_i2c *bi)
 | |
| +{
 | |
| +	bcm2708_wr(bi, BSC_C, 0);
 | |
| +	bcm2708_wr(bi, BSC_S, BSC_S_CLKT | BSC_S_ERR | BSC_S_DONE);
 | |
| +}
 | |
| +
 | |
| +static inline void bcm2708_bsc_fifo_drain(struct bcm2708_i2c *bi)
 | |
| +{
 | |
| +	while ((bi->pos < bi->msg->len) && (bcm2708_rd(bi, BSC_S) & BSC_S_RXD))
 | |
| +		bi->msg->buf[bi->pos++] = bcm2708_rd(bi, BSC_FIFO);
 | |
| +}
 | |
| +
 | |
| +static inline void bcm2708_bsc_fifo_fill(struct bcm2708_i2c *bi)
 | |
| +{
 | |
| +	while ((bi->pos < bi->msg->len) && (bcm2708_rd(bi, BSC_S) & BSC_S_TXD))
 | |
| +		bcm2708_wr(bi, BSC_FIFO, bi->msg->buf[bi->pos++]);
 | |
| +}
 | |
| +
 | |
| +static inline int bcm2708_bsc_setup(struct bcm2708_i2c *bi)
 | |
| +{
 | |
| +	u32 cdiv, s, clk_tout;
 | |
| +	u32 c = BSC_C_I2CEN | BSC_C_INTD | BSC_C_ST | BSC_C_CLEAR_1;
 | |
| +	int wait_loops = I2C_WAIT_LOOP_COUNT;
 | |
| +
 | |
| +	/* Can't call clk_get_rate as it locks a mutex and here we are spinlocked.
 | |
| +	 * Use the value that we cached in the probe.
 | |
| +	 */
 | |
| +	cdiv = bi->cdiv;
 | |
| +	clk_tout = bi->clk_tout;
 | |
| +
 | |
| +	if (bi->msg->flags & I2C_M_RD)
 | |
| +		c |= BSC_C_INTR | BSC_C_READ;
 | |
| +	else
 | |
| +		c |= BSC_C_INTT;
 | |
| +
 | |
| +	bcm2708_wr(bi, BSC_CLKT, clk_tout);
 | |
| +	bcm2708_wr(bi, BSC_DIV, cdiv);
 | |
| +	bcm2708_wr(bi, BSC_A, bi->msg->addr);
 | |
| +	bcm2708_wr(bi, BSC_DLEN, bi->msg->len);
 | |
| +	if (combined)
 | |
| +	{
 | |
| +		/* Do the next two messages meet combined transaction criteria?
 | |
| +		   - Current message is a write, next message is a read
 | |
| +		   - Both messages to same slave address
 | |
| +		   - Write message can fit inside FIFO (16 bytes or less) */
 | |
| +		if ( (bi->nmsgs > 1) &&
 | |
| +			!(bi->msg[0].flags & I2C_M_RD) && (bi->msg[1].flags & I2C_M_RD) &&
 | |
| +			 (bi->msg[0].addr == bi->msg[1].addr) && (bi->msg[0].len <= 16)) {
 | |
| +
 | |
| +			/* Clear FIFO */
 | |
| +			bcm2708_wr(bi, BSC_C, BSC_C_CLEAR_1);
 | |
| +
 | |
| +			/* Fill FIFO with entire write message (16 byte FIFO) */
 | |
| +			while (bi->pos < bi->msg->len) {
 | |
| +				bcm2708_wr(bi, BSC_FIFO, bi->msg->buf[bi->pos++]);
 | |
| +			}
 | |
| +			/* Start write transfer (no interrupts, don't clear FIFO) */
 | |
| +			bcm2708_wr(bi, BSC_C, BSC_C_I2CEN | BSC_C_ST);
 | |
| +
 | |
| +			/* poll for transfer start bit (should only take 1-20 polls) */
 | |
| +			do {
 | |
| +				s = bcm2708_rd(bi, BSC_S);
 | |
| +			} while (!(s & (BSC_S_TA | BSC_S_ERR | BSC_S_CLKT | BSC_S_DONE)) && --wait_loops >= 0);
 | |
| +
 | |
| +			/* did we time out or some error occured? */
 | |
| +			if (wait_loops < 0 || (s & (BSC_S_ERR | BSC_S_CLKT))) {
 | |
| +				return -1;
 | |
| +			}
 | |
| +
 | |
| +			/* Send next read message before the write transfer finishes. */
 | |
| +			bi->nmsgs--;
 | |
| +			bi->msg++;
 | |
| +			bi->pos = 0;
 | |
| +			bcm2708_wr(bi, BSC_DLEN, bi->msg->len);
 | |
| +			c = BSC_C_I2CEN | BSC_C_INTD | BSC_C_INTR | BSC_C_ST | BSC_C_READ;
 | |
| +		}
 | |
| +	}
 | |
| +	bcm2708_wr(bi, BSC_C, c);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static irqreturn_t bcm2708_i2c_interrupt(int irq, void *dev_id)
 | |
| +{
 | |
| +	struct bcm2708_i2c *bi = dev_id;
 | |
| +	bool handled = true;
 | |
| +	u32 s;
 | |
| +	int ret;
 | |
| +
 | |
| +	spin_lock(&bi->lock);
 | |
| +
 | |
| +	/* we may see camera interrupts on the "other" I2C channel
 | |
| +		   Just return if we've not sent anything */
 | |
| +	if (!bi->nmsgs || !bi->msg) {
 | |
| +		goto early_exit;
 | |
| +	}
 | |
| +
 | |
| +	s = bcm2708_rd(bi, BSC_S);
 | |
| +
 | |
| +	if (s & (BSC_S_CLKT | BSC_S_ERR)) {
 | |
| +		bcm2708_bsc_reset(bi);
 | |
| +		bi->error = true;
 | |
| +
 | |
| +		bi->msg = 0; /* to inform the that all work is done */
 | |
| +		bi->nmsgs = 0;
 | |
| +		/* wake up our bh */
 | |
| +		complete(&bi->done);
 | |
| +	} else if (s & BSC_S_DONE) {
 | |
| +		bi->nmsgs--;
 | |
| +
 | |
| +		if (bi->msg->flags & I2C_M_RD) {
 | |
| +			bcm2708_bsc_fifo_drain(bi);
 | |
| +		}
 | |
| +
 | |
| +		bcm2708_bsc_reset(bi);
 | |
| +
 | |
| +		if (bi->nmsgs) {
 | |
| +			/* advance to next message */
 | |
| +			bi->msg++;
 | |
| +			bi->pos = 0;
 | |
| +			ret = bcm2708_bsc_setup(bi);
 | |
| +			if (ret < 0) {
 | |
| +				bcm2708_bsc_reset(bi);
 | |
| +				bi->error = true;
 | |
| +				bi->msg = 0; /* to inform the that all work is done */
 | |
| +				bi->nmsgs = 0;
 | |
| +				/* wake up our bh */
 | |
| +				complete(&bi->done);
 | |
| +				goto early_exit;
 | |
| +			}
 | |
| +		} else {
 | |
| +			bi->msg = 0; /* to inform the that all work is done */
 | |
| +			bi->nmsgs = 0;
 | |
| +			/* wake up our bh */
 | |
| +			complete(&bi->done);
 | |
| +		}
 | |
| +	} else if (s & BSC_S_TXW) {
 | |
| +		bcm2708_bsc_fifo_fill(bi);
 | |
| +	} else if (s & BSC_S_RXR) {
 | |
| +		bcm2708_bsc_fifo_drain(bi);
 | |
| +	} else {
 | |
| +		handled = false;
 | |
| +	}
 | |
| +
 | |
| +early_exit:
 | |
| +	spin_unlock(&bi->lock);
 | |
| +
 | |
| +	return handled ? IRQ_HANDLED : IRQ_NONE;
 | |
| +}
 | |
| +
 | |
| +static int bcm2708_i2c_master_xfer(struct i2c_adapter *adap,
 | |
| +	struct i2c_msg *msgs, int num)
 | |
| +{
 | |
| +	struct bcm2708_i2c *bi = adap->algo_data;
 | |
| +	unsigned long flags;
 | |
| +	int ret;
 | |
| +
 | |
| +	spin_lock_irqsave(&bi->lock, flags);
 | |
| +
 | |
| +	reinit_completion(&bi->done);
 | |
| +	bi->msg = msgs;
 | |
| +	bi->pos = 0;
 | |
| +	bi->nmsgs = num;
 | |
| +	bi->error = false;
 | |
| +
 | |
| +	ret = bcm2708_bsc_setup(bi);
 | |
| +
 | |
| +	spin_unlock_irqrestore(&bi->lock, flags);
 | |
| +
 | |
| +	/* check the result of the setup */
 | |
| +	if (ret < 0)
 | |
| +	{
 | |
| +		dev_err(&adap->dev, "transfer setup timed out\n");
 | |
| +		goto error_timeout;
 | |
| +	}
 | |
| +
 | |
| +	ret = wait_for_completion_timeout(&bi->done, adap->timeout);
 | |
| +	if (ret == 0) {
 | |
| +		dev_err(&adap->dev, "transfer timed out\n");
 | |
| +		goto error_timeout;
 | |
| +	}
 | |
| +
 | |
| +	ret = bi->error ? -EIO : num;
 | |
| +	return ret;
 | |
| +
 | |
| +error_timeout:
 | |
| +	spin_lock_irqsave(&bi->lock, flags);
 | |
| +	bcm2708_bsc_reset(bi);
 | |
| +	bi->msg = 0; /* to inform the interrupt handler that there's nothing else to be done */
 | |
| +	bi->nmsgs = 0;
 | |
| +	spin_unlock_irqrestore(&bi->lock, flags);
 | |
| +	return -ETIMEDOUT;
 | |
| +}
 | |
| +
 | |
| +static u32 bcm2708_i2c_functionality(struct i2c_adapter *adap)
 | |
| +{
 | |
| +	return I2C_FUNC_I2C | /*I2C_FUNC_10BIT_ADDR |*/ I2C_FUNC_SMBUS_EMUL;
 | |
| +}
 | |
| +
 | |
| +static struct i2c_algorithm bcm2708_i2c_algorithm = {
 | |
| +	.master_xfer = bcm2708_i2c_master_xfer,
 | |
| +	.functionality = bcm2708_i2c_functionality,
 | |
| +};
 | |
| +
 | |
| +static int bcm2708_i2c_probe(struct platform_device *pdev)
 | |
| +{
 | |
| +	struct resource *regs;
 | |
| +	int irq, err = -ENOMEM;
 | |
| +	struct clk *clk;
 | |
| +	struct bcm2708_i2c *bi;
 | |
| +	struct i2c_adapter *adap;
 | |
| +	unsigned long bus_hz;
 | |
| +	u32 cdiv, clk_tout;
 | |
| +	u32 baud;
 | |
| +
 | |
| +	baud = CONFIG_I2C_BCM2708_BAUDRATE;
 | |
| +
 | |
| +	if (pdev->dev.of_node) {
 | |
| +		u32 bus_clk_rate;
 | |
| +		pdev->id = of_alias_get_id(pdev->dev.of_node, "i2c");
 | |
| +		if (pdev->id < 0) {
 | |
| +			dev_err(&pdev->dev, "alias is missing\n");
 | |
| +			return -EINVAL;
 | |
| +		}
 | |
| +		if (!of_property_read_u32(pdev->dev.of_node,
 | |
| +					"clock-frequency", &bus_clk_rate))
 | |
| +			baud = bus_clk_rate;
 | |
| +		else
 | |
| +			dev_warn(&pdev->dev,
 | |
| +				"Could not read clock-frequency property\n");
 | |
| +	}
 | |
| +
 | |
| +	if (baudrate)
 | |
| +		baud = baudrate;
 | |
| +
 | |
| +	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 | |
| +	if (!regs) {
 | |
| +		dev_err(&pdev->dev, "could not get IO memory\n");
 | |
| +		return -ENXIO;
 | |
| +	}
 | |
| +
 | |
| +	irq = platform_get_irq(pdev, 0);
 | |
| +	if (irq < 0) {
 | |
| +		dev_err(&pdev->dev, "could not get IRQ\n");
 | |
| +		return irq;
 | |
| +	}
 | |
| +
 | |
| +	clk = clk_get(&pdev->dev, NULL);
 | |
| +	if (IS_ERR(clk)) {
 | |
| +		dev_err(&pdev->dev, "could not find clk: %ld\n", PTR_ERR(clk));
 | |
| +		return PTR_ERR(clk);
 | |
| +	}
 | |
| +
 | |
| +	err = clk_prepare_enable(clk);
 | |
| +	if (err) {
 | |
| +		dev_err(&pdev->dev, "could not enable clk: %d\n", err);
 | |
| +		goto out_clk_put;
 | |
| +	}
 | |
| +
 | |
| +	bi = kzalloc(sizeof(*bi), GFP_KERNEL);
 | |
| +	if (!bi)
 | |
| +		goto out_clk_disable;
 | |
| +
 | |
| +	platform_set_drvdata(pdev, bi);
 | |
| +
 | |
| +	adap = &bi->adapter;
 | |
| +	adap->class = I2C_CLASS_HWMON | I2C_CLASS_DDC;
 | |
| +	adap->algo = &bcm2708_i2c_algorithm;
 | |
| +	adap->algo_data = bi;
 | |
| +	adap->dev.parent = &pdev->dev;
 | |
| +	adap->nr = pdev->id;
 | |
| +	strlcpy(adap->name, dev_name(&pdev->dev), sizeof(adap->name));
 | |
| +	adap->dev.of_node = pdev->dev.of_node;
 | |
| +
 | |
| +	switch (pdev->id) {
 | |
| +	case 0:
 | |
| +		adap->class = I2C_CLASS_HWMON;
 | |
| +		break;
 | |
| +	case 1:
 | |
| +		adap->class = I2C_CLASS_DDC;
 | |
| +		break;
 | |
| +	case 2:
 | |
| +		adap->class = I2C_CLASS_DDC;
 | |
| +		break;
 | |
| +	default:
 | |
| +		dev_err(&pdev->dev, "can only bind to BSC 0, 1 or 2\n");
 | |
| +		err = -ENXIO;
 | |
| +		goto out_free_bi;
 | |
| +	}
 | |
| +
 | |
| +	spin_lock_init(&bi->lock);
 | |
| +	init_completion(&bi->done);
 | |
| +
 | |
| +	bi->base = ioremap(regs->start, resource_size(regs));
 | |
| +	if (!bi->base) {
 | |
| +		dev_err(&pdev->dev, "could not remap memory\n");
 | |
| +		goto out_free_bi;
 | |
| +	}
 | |
| +
 | |
| +	bi->irq = irq;
 | |
| +	bi->clk = clk;
 | |
| +
 | |
| +	err = request_irq(irq, bcm2708_i2c_interrupt, IRQF_SHARED,
 | |
| +			dev_name(&pdev->dev), bi);
 | |
| +	if (err) {
 | |
| +		dev_err(&pdev->dev, "could not request IRQ: %d\n", err);
 | |
| +		goto out_iounmap;
 | |
| +	}
 | |
| +
 | |
| +	bcm2708_bsc_reset(bi);
 | |
| +
 | |
| +	err = i2c_add_numbered_adapter(adap);
 | |
| +	if (err < 0) {
 | |
| +		dev_err(&pdev->dev, "could not add I2C adapter: %d\n", err);
 | |
| +		goto out_free_irq;
 | |
| +	}
 | |
| +
 | |
| +	bus_hz = clk_get_rate(bi->clk);
 | |
| +	cdiv = bus_hz / baud;
 | |
| +	if (cdiv > 0xffff) {
 | |
| +		cdiv = 0xffff;
 | |
| +		baud = bus_hz / cdiv;
 | |
| +	}
 | |
| +
 | |
| +	clk_tout = 35/1000*baud; //35ms timeout as per SMBus specs.
 | |
| +	if (clk_tout > 0xffff)
 | |
| +		clk_tout = 0xffff;
 | |
| +	
 | |
| +	bi->cdiv = cdiv;
 | |
| +	bi->clk_tout = clk_tout;
 | |
| +
 | |
| +	dev_info(&pdev->dev, "BSC%d Controller at 0x%08lx (irq %d) (baudrate %d)\n",
 | |
| +		pdev->id, (unsigned long)regs->start, irq, baud);
 | |
| +
 | |
| +	return 0;
 | |
| +
 | |
| +out_free_irq:
 | |
| +	free_irq(bi->irq, bi);
 | |
| +out_iounmap:
 | |
| +	iounmap(bi->base);
 | |
| +out_free_bi:
 | |
| +	kfree(bi);
 | |
| +out_clk_disable:
 | |
| +	clk_disable_unprepare(clk);
 | |
| +out_clk_put:
 | |
| +	clk_put(clk);
 | |
| +	return err;
 | |
| +}
 | |
| +
 | |
| +static int bcm2708_i2c_remove(struct platform_device *pdev)
 | |
| +{
 | |
| +	struct bcm2708_i2c *bi = platform_get_drvdata(pdev);
 | |
| +
 | |
| +	platform_set_drvdata(pdev, NULL);
 | |
| +
 | |
| +	i2c_del_adapter(&bi->adapter);
 | |
| +	free_irq(bi->irq, bi);
 | |
| +	iounmap(bi->base);
 | |
| +	clk_disable_unprepare(bi->clk);
 | |
| +	clk_put(bi->clk);
 | |
| +	kfree(bi);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static const struct of_device_id bcm2708_i2c_of_match[] = {
 | |
| +        { .compatible = "brcm,bcm2708-i2c" },
 | |
| +        {},
 | |
| +};
 | |
| +MODULE_DEVICE_TABLE(of, bcm2708_i2c_of_match);
 | |
| +
 | |
| +static struct platform_driver bcm2708_i2c_driver = {
 | |
| +	.driver		= {
 | |
| +		.name	= DRV_NAME,
 | |
| +		.owner	= THIS_MODULE,
 | |
| +		.of_match_table = bcm2708_i2c_of_match,
 | |
| +	},
 | |
| +	.probe		= bcm2708_i2c_probe,
 | |
| +	.remove		= bcm2708_i2c_remove,
 | |
| +};
 | |
| +
 | |
| +// module_platform_driver(bcm2708_i2c_driver);
 | |
| +
 | |
| +
 | |
| +static int __init bcm2708_i2c_init(void)
 | |
| +{
 | |
| +	return platform_driver_register(&bcm2708_i2c_driver);
 | |
| +}
 | |
| +
 | |
| +static void __exit bcm2708_i2c_exit(void)
 | |
| +{
 | |
| +	platform_driver_unregister(&bcm2708_i2c_driver);
 | |
| +}
 | |
| +
 | |
| +module_init(bcm2708_i2c_init);
 | |
| +module_exit(bcm2708_i2c_exit);
 | |
| +
 | |
| +
 | |
| +
 | |
| +MODULE_DESCRIPTION("BSC controller driver for Broadcom BCM2708");
 | |
| +MODULE_AUTHOR("Chris Boot <bootc@bootc.net>");
 | |
| +MODULE_LICENSE("GPL v2");
 | |
| +MODULE_ALIAS("platform:" DRV_NAME);
 |