| 
									
										
										
										
											2012-01-27 14:15:37 +05:30
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright 2011, Netlogic Microsystems Inc. | 
					
						
							|  |  |  |  * Copyright 2004, Matt Porter <mporter@kernel.crashing.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. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-01-21 11:09:03 +01:00
										 |  |  | #include <linux/err.h>
 | 
					
						
							| 
									
										
										
										
											2012-01-27 14:15:37 +05:30
										 |  |  | #include <linux/kernel.h>
 | 
					
						
							|  |  |  | #include <linux/module.h>
 | 
					
						
							|  |  |  | #include <linux/slab.h>
 | 
					
						
							|  |  |  | #include <linux/ioport.h>
 | 
					
						
							|  |  |  | #include <linux/delay.h>
 | 
					
						
							|  |  |  | #include <linux/errno.h>
 | 
					
						
							|  |  |  | #include <linux/i2c.h>
 | 
					
						
							|  |  |  | #include <linux/io.h>
 | 
					
						
							|  |  |  | #include <linux/platform_device.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* XLR I2C REGISTERS */ | 
					
						
							|  |  |  | #define XLR_I2C_CFG		0x00
 | 
					
						
							|  |  |  | #define XLR_I2C_CLKDIV		0x01
 | 
					
						
							|  |  |  | #define XLR_I2C_DEVADDR		0x02
 | 
					
						
							|  |  |  | #define XLR_I2C_ADDR		0x03
 | 
					
						
							|  |  |  | #define XLR_I2C_DATAOUT		0x04
 | 
					
						
							|  |  |  | #define XLR_I2C_DATAIN		0x05
 | 
					
						
							|  |  |  | #define XLR_I2C_STATUS		0x06
 | 
					
						
							|  |  |  | #define XLR_I2C_STARTXFR	0x07
 | 
					
						
							|  |  |  | #define XLR_I2C_BYTECNT		0x08
 | 
					
						
							|  |  |  | #define XLR_I2C_HDSTATIM	0x09
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* XLR I2C REGISTERS FLAGS */ | 
					
						
							|  |  |  | #define XLR_I2C_BUS_BUSY	0x01
 | 
					
						
							|  |  |  | #define XLR_I2C_SDOEMPTY	0x02
 | 
					
						
							|  |  |  | #define XLR_I2C_RXRDY		0x04
 | 
					
						
							|  |  |  | #define XLR_I2C_ACK_ERR		0x08
 | 
					
						
							|  |  |  | #define XLR_I2C_ARB_STARTERR	0x30
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Register Values */ | 
					
						
							|  |  |  | #define XLR_I2C_CFG_ADDR	0xF8
 | 
					
						
							|  |  |  | #define XLR_I2C_CFG_NOADDR	0xFA
 | 
					
						
							|  |  |  | #define XLR_I2C_STARTXFR_ND	0x02    /* No Data */
 | 
					
						
							|  |  |  | #define XLR_I2C_STARTXFR_RD	0x01    /* Read */
 | 
					
						
							|  |  |  | #define XLR_I2C_STARTXFR_WR	0x00    /* Write */
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define XLR_I2C_TIMEOUT		10	/* timeout per byte in msec */
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * On XLR/XLS, we need to use __raw_ IO to read the I2C registers | 
					
						
							|  |  |  |  * because they are in the big-endian MMIO area on the SoC. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * The readl/writel implementation on XLR/XLS byteswaps, because | 
					
						
							|  |  |  |  * those are for its little-endian PCI space (see arch/mips/Kconfig). | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static inline void xlr_i2c_wreg(u32 __iomem *base, unsigned int reg, u32 val) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	__raw_writel(val, base + reg); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline u32 xlr_i2c_rdreg(u32 __iomem *base, unsigned int reg) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return __raw_readl(base + reg); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct xlr_i2c_private { | 
					
						
							|  |  |  | 	struct i2c_adapter adap; | 
					
						
							|  |  |  | 	u32 __iomem *iobase; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int xlr_i2c_tx(struct xlr_i2c_private *priv,  u16 len, | 
					
						
							|  |  |  | 	u8 *buf, u16 addr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct i2c_adapter *adap = &priv->adap; | 
					
						
							|  |  |  | 	unsigned long timeout, stoptime, checktime; | 
					
						
							|  |  |  | 	u32 i2c_status; | 
					
						
							|  |  |  | 	int pos, timedout; | 
					
						
							|  |  |  | 	u8 offset, byte; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	offset = buf[0]; | 
					
						
							|  |  |  | 	xlr_i2c_wreg(priv->iobase, XLR_I2C_ADDR, offset); | 
					
						
							|  |  |  | 	xlr_i2c_wreg(priv->iobase, XLR_I2C_DEVADDR, addr); | 
					
						
							|  |  |  | 	xlr_i2c_wreg(priv->iobase, XLR_I2C_CFG, XLR_I2C_CFG_ADDR); | 
					
						
							|  |  |  | 	xlr_i2c_wreg(priv->iobase, XLR_I2C_BYTECNT, len - 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	timeout = msecs_to_jiffies(XLR_I2C_TIMEOUT); | 
					
						
							|  |  |  | 	stoptime = jiffies + timeout; | 
					
						
							|  |  |  | 	timedout = 0; | 
					
						
							|  |  |  | 	pos = 1; | 
					
						
							|  |  |  | retry: | 
					
						
							|  |  |  | 	if (len == 1) { | 
					
						
							|  |  |  | 		xlr_i2c_wreg(priv->iobase, XLR_I2C_STARTXFR, | 
					
						
							|  |  |  | 				XLR_I2C_STARTXFR_ND); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		xlr_i2c_wreg(priv->iobase, XLR_I2C_DATAOUT, buf[pos]); | 
					
						
							|  |  |  | 		xlr_i2c_wreg(priv->iobase, XLR_I2C_STARTXFR, | 
					
						
							|  |  |  | 				XLR_I2C_STARTXFR_WR); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	while (!timedout) { | 
					
						
							|  |  |  | 		checktime = jiffies; | 
					
						
							|  |  |  | 		i2c_status = xlr_i2c_rdreg(priv->iobase, XLR_I2C_STATUS); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (i2c_status & XLR_I2C_SDOEMPTY) { | 
					
						
							|  |  |  | 			pos++; | 
					
						
							|  |  |  | 			/* need to do a empty dataout after the last byte */ | 
					
						
							|  |  |  | 			byte = (pos < len) ? buf[pos] : 0; | 
					
						
							|  |  |  | 			xlr_i2c_wreg(priv->iobase, XLR_I2C_DATAOUT, byte); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/* reset timeout on successful xmit */ | 
					
						
							|  |  |  | 			stoptime = jiffies + timeout; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		timedout = time_after(checktime, stoptime); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (i2c_status & XLR_I2C_ARB_STARTERR) { | 
					
						
							|  |  |  | 			if (timedout) | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			goto retry; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (i2c_status & XLR_I2C_ACK_ERR) | 
					
						
							|  |  |  | 			return -EIO; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if ((i2c_status & XLR_I2C_BUS_BUSY) == 0 && pos >= len) | 
					
						
							|  |  |  | 			return 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	dev_err(&adap->dev, "I2C transmit timeout\n"); | 
					
						
							|  |  |  | 	return -ETIMEDOUT; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int xlr_i2c_rx(struct xlr_i2c_private *priv, u16 len, u8 *buf, u16 addr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct i2c_adapter *adap = &priv->adap; | 
					
						
							|  |  |  | 	u32 i2c_status; | 
					
						
							|  |  |  | 	unsigned long timeout, stoptime, checktime; | 
					
						
							|  |  |  | 	int nbytes, timedout; | 
					
						
							|  |  |  | 	u8 byte; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	xlr_i2c_wreg(priv->iobase, XLR_I2C_CFG, XLR_I2C_CFG_NOADDR); | 
					
						
							|  |  |  | 	xlr_i2c_wreg(priv->iobase, XLR_I2C_BYTECNT, len); | 
					
						
							|  |  |  | 	xlr_i2c_wreg(priv->iobase, XLR_I2C_DEVADDR, addr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	timeout = msecs_to_jiffies(XLR_I2C_TIMEOUT); | 
					
						
							|  |  |  | 	stoptime = jiffies + timeout; | 
					
						
							|  |  |  | 	timedout = 0; | 
					
						
							|  |  |  | 	nbytes = 0; | 
					
						
							|  |  |  | retry: | 
					
						
							|  |  |  | 	xlr_i2c_wreg(priv->iobase, XLR_I2C_STARTXFR, XLR_I2C_STARTXFR_RD); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	while (!timedout) { | 
					
						
							|  |  |  | 		checktime = jiffies; | 
					
						
							|  |  |  | 		i2c_status = xlr_i2c_rdreg(priv->iobase, XLR_I2C_STATUS); | 
					
						
							|  |  |  | 		if (i2c_status & XLR_I2C_RXRDY) { | 
					
						
							|  |  |  | 			if (nbytes > len) | 
					
						
							|  |  |  | 				return -EIO;	/* should not happen */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/* we need to do a dummy datain when nbytes == len */ | 
					
						
							|  |  |  | 			byte = xlr_i2c_rdreg(priv->iobase, XLR_I2C_DATAIN); | 
					
						
							|  |  |  | 			if (nbytes < len) | 
					
						
							|  |  |  | 				buf[nbytes] = byte; | 
					
						
							|  |  |  | 			nbytes++; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/* reset timeout on successful read */ | 
					
						
							|  |  |  | 			stoptime = jiffies + timeout; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		timedout = time_after(checktime, stoptime); | 
					
						
							|  |  |  | 		if (i2c_status & XLR_I2C_ARB_STARTERR) { | 
					
						
							|  |  |  | 			if (timedout) | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			goto retry; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (i2c_status & XLR_I2C_ACK_ERR) | 
					
						
							|  |  |  | 			return -EIO; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if ((i2c_status & XLR_I2C_BUS_BUSY) == 0) | 
					
						
							|  |  |  | 			return 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dev_err(&adap->dev, "I2C receive timeout\n"); | 
					
						
							|  |  |  | 	return -ETIMEDOUT; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int xlr_i2c_xfer(struct i2c_adapter *adap, | 
					
						
							|  |  |  | 	struct i2c_msg *msgs, int num) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct i2c_msg *msg; | 
					
						
							|  |  |  | 	int i; | 
					
						
							|  |  |  | 	int ret = 0; | 
					
						
							|  |  |  | 	struct xlr_i2c_private *priv = i2c_get_adapdata(adap); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i = 0; ret == 0 && i < num; i++) { | 
					
						
							|  |  |  | 		msg = &msgs[i]; | 
					
						
							|  |  |  | 		if (msg->flags & I2C_M_RD) | 
					
						
							|  |  |  | 			ret = xlr_i2c_rx(priv, msg->len, &msg->buf[0], | 
					
						
							|  |  |  | 					msg->addr); | 
					
						
							|  |  |  | 		else | 
					
						
							|  |  |  | 			ret = xlr_i2c_tx(priv, msg->len, &msg->buf[0], | 
					
						
							|  |  |  | 					msg->addr); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return (ret != 0) ? ret : num; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static u32 xlr_func(struct i2c_adapter *adap) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	/* Emulate SMBUS over I2C */ | 
					
						
							|  |  |  | 	return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct i2c_algorithm xlr_i2c_algo = { | 
					
						
							|  |  |  | 	.master_xfer	= xlr_i2c_xfer, | 
					
						
							|  |  |  | 	.functionality	= xlr_func, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-11-27 15:59:38 -05:00
										 |  |  | static int xlr_i2c_probe(struct platform_device *pdev) | 
					
						
							| 
									
										
										
										
											2012-01-27 14:15:37 +05:30
										 |  |  | { | 
					
						
							|  |  |  | 	struct xlr_i2c_private  *priv; | 
					
						
							|  |  |  | 	struct resource *res; | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); | 
					
						
							|  |  |  | 	if (!priv) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
					
						
							| 
									
										
										
										
											2013-01-21 11:09:03 +01:00
										 |  |  | 	priv->iobase = devm_ioremap_resource(&pdev->dev, res); | 
					
						
							|  |  |  | 	if (IS_ERR(priv->iobase)) | 
					
						
							|  |  |  | 		return PTR_ERR(priv->iobase); | 
					
						
							| 
									
										
										
										
											2012-01-27 14:15:37 +05:30
										 |  |  | 
 | 
					
						
							|  |  |  | 	priv->adap.dev.parent = &pdev->dev; | 
					
						
							|  |  |  | 	priv->adap.owner	= THIS_MODULE; | 
					
						
							|  |  |  | 	priv->adap.algo_data	= priv; | 
					
						
							|  |  |  | 	priv->adap.algo		= &xlr_i2c_algo; | 
					
						
							|  |  |  | 	priv->adap.nr		= pdev->id; | 
					
						
							|  |  |  | 	priv->adap.class	= I2C_CLASS_HWMON; | 
					
						
							|  |  |  | 	snprintf(priv->adap.name, sizeof(priv->adap.name), "xlr-i2c"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	i2c_set_adapdata(&priv->adap, priv); | 
					
						
							|  |  |  | 	ret = i2c_add_numbered_adapter(&priv->adap); | 
					
						
							|  |  |  | 	if (ret < 0) { | 
					
						
							|  |  |  | 		dev_err(&priv->adap.dev, "Failed to add i2c bus.\n"); | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	platform_set_drvdata(pdev, priv); | 
					
						
							|  |  |  | 	dev_info(&priv->adap.dev, "Added I2C Bus.\n"); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-11-27 15:59:38 -05:00
										 |  |  | static int xlr_i2c_remove(struct platform_device *pdev) | 
					
						
							| 
									
										
										
										
											2012-01-27 14:15:37 +05:30
										 |  |  | { | 
					
						
							|  |  |  | 	struct xlr_i2c_private *priv; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	priv = platform_get_drvdata(pdev); | 
					
						
							|  |  |  | 	i2c_del_adapter(&priv->adap); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct platform_driver xlr_i2c_driver = { | 
					
						
							|  |  |  | 	.probe  = xlr_i2c_probe, | 
					
						
							| 
									
										
										
										
											2012-11-27 15:59:38 -05:00
										 |  |  | 	.remove = xlr_i2c_remove, | 
					
						
							| 
									
										
										
										
											2012-01-27 14:15:37 +05:30
										 |  |  | 	.driver = { | 
					
						
							|  |  |  | 		.name   = "xlr-i2cbus", | 
					
						
							|  |  |  | 		.owner  = THIS_MODULE, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module_platform_driver(xlr_i2c_driver); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | MODULE_AUTHOR("Ganesan Ramalingam <ganesanr@netlogicmicro.com>"); | 
					
						
							|  |  |  | MODULE_DESCRIPTION("XLR/XLS SoC I2C Controller driver"); | 
					
						
							|  |  |  | MODULE_LICENSE("GPL v2"); | 
					
						
							|  |  |  | MODULE_ALIAS("platform:xlr-i2cbus"); |