/* * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive * for more details. * * Copyright (C) 2012 MIPS Technologies, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #define PIC32_I2CxCON 0x0000 #define PIC32_I2CxCONCLR 0x0004 #define PIC32_I2CxCONSET 0x0008 #define PIC32_I2CxCONINV 0x000C #define I2CCON_ON (1<<15) #define I2CCON_FRZ (1<<14) #define I2CCON_SIDL (1<<13) #define I2CCON_SCLREL (1<<12) #define I2CCON_STRICT (1<<11) #define I2CCON_A10M (1<<10) #define I2CCON_DISSLW (1<<9) #define I2CCON_SMEN (1<<8) #define I2CCON_GCEN (1<<7) #define I2CCON_STREN (1<<6) #define I2CCON_ACKDT (1<<5) #define I2CCON_ACKEN (1<<4) #define I2CCON_RCEN (1<<3) #define I2CCON_PEN (1<<2) #define I2CCON_RSEN (1<<1) #define I2CCON_SEN (1<<0) #define PIC32_I2CxSTAT 0x0010 #define PIC32_I2CxSTATCLR 0x0014 #define PIC32_I2CxSTATSET 0x0018 #define PIC32_I2CxSTATINV 0x001C #define I2CSTAT_ACKSTAT (1<<15) #define I2CSTAT_TRSTAT (1<<14) #define I2CSTAT_BCL (1<<10) #define I2CSTAT_GCSTAT (1<<9) #define I2CSTAT_ADD10 (1<<8) #define I2CSTAT_IWCOL (1<<7) #define I2CSTAT_I2COV (1<<6) #define I2CSTAT_DA (1<<5) #define I2CSTAT_P (1<<4) #define I2CSTAT_S (1<<3) #define I2CSTAT_RW (1<<2) #define I2CSTAT_RBF (1<<1) #define I2CSTAT_TBF (1<<0) #define PIC32_I2CxADD 0x0020 #define PIC32_I2CxADDCLR 0x0024 #define PIC32_I2CxADDSET 0x0028 #define PIC32_I2CxADDINV 0x002C #define PIC32_I2CxMSK 0x0030 #define PIC32_I2CxMSKCLR 0x0034 #define PIC32_I2CxMSKSET 0x0038 #define PIC32_I2CxMSKINV 0x003C #define PIC32_I2CxBRG 0x0040 #define PIC32_I2CxBRGCLR 0x0044 #define PIC32_I2CxBRGSET 0x0048 #define PIC32_I2CxBRGINV 0x004C #define PIC32_I2CxTRN 0x0050 #define PIC32_I2CxTRNCLR 0x0054 #define PIC32_I2CxTRNSET 0x0058 #define PIC32_I2CxTRNINV 0x005C #define PIC32_I2CxRCV 0x0060 struct i2c_platform_data { u32 base; struct i2c_adapter adap; u32 xfer_timeout; u32 ack_timeout; u32 ctl_timeout; }; extern u32 pic32_bus_readl(u32 reg); extern void pic32_bus_writel(u32 val, u32 reg); static inline void StartI2C(struct i2c_platform_data *adap) { pr_debug("StartI2C\n"); pic32_bus_writel(I2CCON_SEN, adap->base + PIC32_I2CxCONSET); } static inline void StopI2C(struct i2c_platform_data *adap) { pr_debug("StopI2C\n"); pic32_bus_writel(I2CCON_PEN, adap->base + PIC32_I2CxCONSET); } static inline void AckI2C(struct i2c_platform_data *adap) { pr_debug("AckI2C\n"); pic32_bus_writel(I2CCON_ACKDT, adap->base + PIC32_I2CxCONCLR); pic32_bus_writel(I2CCON_ACKEN, adap->base + PIC32_I2CxCONSET); } static inline void NotAckI2C(struct i2c_platform_data *adap) { pr_debug("NakI2C\n"); pic32_bus_writel(I2CCON_ACKDT, adap->base + PIC32_I2CxCONSET); pic32_bus_writel(I2CCON_ACKEN, adap->base + PIC32_I2CxCONSET); } static inline int IdleI2C(struct i2c_platform_data *adap) { int i; pr_debug("IdleI2C\n"); for (i = 0; i < adap->ctl_timeout; i++) { if (((pic32_bus_readl(adap->base + PIC32_I2CxCON) & (I2CCON_ACKEN | I2CCON_RCEN | I2CCON_PEN | I2CCON_RSEN | I2CCON_SEN)) == 0) && ((pic32_bus_readl(adap->base + PIC32_I2CxSTAT) & (I2CSTAT_TRSTAT)) == 0)) return 0; udelay(1); } return -ETIMEDOUT; } static inline u32 MasterWriteI2C(struct i2c_platform_data *adap, u32 byte) { pr_debug("MasterWriteI2C\n"); pic32_bus_writel(byte, adap->base + PIC32_I2CxTRN); return pic32_bus_readl(adap->base + PIC32_I2CxSTAT) & I2CSTAT_IWCOL; } static inline u32 MasterReadI2C(struct i2c_platform_data *adap) { pr_debug("MasterReadI2C\n"); pic32_bus_writel(I2CCON_RCEN, adap->base + PIC32_I2CxCONSET); while (pic32_bus_readl(adap->base + PIC32_I2CxCON) & I2CCON_RCEN) ; pic32_bus_writel(I2CSTAT_I2COV, adap->base + PIC32_I2CxSTATCLR); return pic32_bus_readl(adap->base + PIC32_I2CxRCV); } static int do_address(struct i2c_platform_data *adap, unsigned int addr, int rd) { pr_debug("doaddress\n"); IdleI2C(adap); StartI2C(adap); IdleI2C(adap); addr <<= 1; if (rd) addr |= 1; if (MasterWriteI2C(adap, addr)) return -EIO; IdleI2C(adap); if (pic32_bus_readl(adap->base + PIC32_I2CxSTAT) & I2CSTAT_ACKSTAT) return -EIO; return 0; } static int i2c_read(struct i2c_platform_data *adap, unsigned char *buf, unsigned int len) { int i; u32 data; pr_debug("i2c_read\n"); i = 0; while (i < len) { data = MasterReadI2C(adap); buf[i++] = data; if (i < len) AckI2C(adap); else NotAckI2C(adap); } StopI2C(adap); IdleI2C(adap); return 0; } static int i2c_write(struct i2c_platform_data *adap, unsigned char *buf, unsigned int len) { int i; u32 data; pr_debug("i2c_write\n"); i = 0; while (i < len) { data = buf[i]; if (MasterWriteI2C(adap, data)) return -EIO; IdleI2C(adap); if (pic32_bus_readl(adap->base + PIC32_I2CxSTAT) & I2CSTAT_ACKSTAT) return -EIO; i++; } StopI2C(adap); IdleI2C(adap); return 0; } static int platform_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msgs, int num) { struct i2c_platform_data *adap = i2c_adap->algo_data; struct i2c_msg *p; int i, err = 0; pr_debug("platform_xfer\n"); for (i = 0; i < num; i++) { #define __BUFSIZE 80 int ii; static char buf[__BUFSIZE]; char *b = buf; p = &msgs[i]; b += sprintf(buf, " [%d bytes]", p->len); if ((p->flags & I2C_M_RD) == 0) { for (ii = 0; ii < p->len; ii++) { if (b < &buf[__BUFSIZE-4]) { b += sprintf(b, " %02x", p->buf[ii]); } else { strcat(b, "..."); break; } } } pr_debug("xfer%d: DevAddr: %04x Op:%s Data:%s\n", i, p->addr, (p->flags & I2C_M_RD) ? "Rd" : "Wr", buf); } for (i = 0; !err && i < num; i++) { p = &msgs[i]; err = do_address(adap, p->addr, p->flags & I2C_M_RD); if (err || !p->len) continue; if (p->flags & I2C_M_RD) err = i2c_read(adap, p->buf, p->len); else err = i2c_write(adap, p->buf, p->len); } /* Return the number of messages processed, or the error code. */ if (err == 0) err = num; return err; } static u32 platform_func(struct i2c_adapter *adap) { pr_debug("platform_algo\n"); return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; } static const struct i2c_algorithm platform_algo = { .master_xfer = platform_xfer, .functionality = platform_func, }; static void i2c_platform_setup(struct i2c_platform_data *priv) { pr_debug("i2c_platform_setup\n"); pic32_bus_writel(500, priv->base + PIC32_I2CxBRG); pic32_bus_writel(I2CCON_ON, priv->base + PIC32_I2CxCONCLR); pic32_bus_writel(I2CCON_ON, priv->base + PIC32_I2CxCONSET); pic32_bus_writel((I2CSTAT_BCL | I2CSTAT_IWCOL), (priv->base + PIC32_I2CxSTATCLR)); } static void i2c_platform_disable(struct i2c_platform_data *priv) { pr_debug("i2c_platform_disable\n"); } static int i2c_platform_probe(struct platform_device *pdev) { struct i2c_platform_data *priv; struct resource *r; int ret; pr_debug("i2c_platform_probe\n"); r = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!r) { ret = -ENODEV; goto out; } priv = kzalloc(sizeof(struct i2c_platform_data), GFP_KERNEL); if (!priv) { ret = -ENOMEM; goto out; } /* FIXME: need to allocate resource in PIC32 space */ #if 0 priv->base = bus_request_region(r->start, resource_size(r), pdev->name); #else priv->base = r->start; #endif if (!priv->base) { ret = -EBUSY; goto out_mem; } priv->xfer_timeout = 200; priv->ack_timeout = 200; priv->ctl_timeout = 200; priv->adap.nr = pdev->id; priv->adap.algo = &platform_algo; priv->adap.algo_data = priv; priv->adap.dev.parent = &pdev->dev; strlcpy(priv->adap.name, "PIC32 I2C", sizeof(priv->adap.name)); i2c_platform_setup(priv); ret = i2c_add_numbered_adapter(&priv->adap); if (ret == 0) { platform_set_drvdata(pdev, priv); return 0; } i2c_platform_disable(priv); out_mem: kfree(priv); out: return ret; } static int i2c_platform_remove(struct platform_device *pdev) { struct i2c_platform_data *priv = platform_get_drvdata(pdev); pr_debug("i2c_platform_remove\n"); platform_set_drvdata(pdev, NULL); i2c_del_adapter(&priv->adap); i2c_platform_disable(priv); kfree(priv); return 0; } #ifdef CONFIG_PM static int i2c_platform_suspend(struct platform_device *pdev, pm_message_t state) { struct i2c_platform_data *priv = platform_get_drvdata(pdev); dev_dbg(&pdev->dev, "i2c_platform_disable\n"); i2c_platform_disable(priv); return 0; } static int i2c_platform_resume(struct platform_device *pdev) { struct i2c_platform_data *priv = platform_get_drvdata(pdev); dev_dbg(&pdev->dev, "i2c_platform_setup\n"); i2c_platform_setup(priv); return 0; } #else #define i2c_platform_suspend NULL #define i2c_platform_resume NULL #endif static struct platform_driver i2c_platform_driver = { .driver = { .name = "i2c_pic32", .owner = THIS_MODULE, }, .probe = i2c_platform_probe, .remove = i2c_platform_remove, .suspend = i2c_platform_suspend, .resume = i2c_platform_resume, }; static int __init i2c_platform_init(void) { pr_debug("i2c_platform_init\n"); return platform_driver_register(&i2c_platform_driver); } static void __exit i2c_platform_exit(void) { pr_debug("i2c_platform_exit\n"); platform_driver_unregister(&i2c_platform_driver); } MODULE_AUTHOR("Chris Dearman, MIPS Technologies INC."); MODULE_DESCRIPTION("PIC32 I2C driver"); MODULE_LICENSE("GPL"); module_init(i2c_platform_init); module_exit(i2c_platform_exit);