18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * i2c-pca-isa.c driver for PCA9564 on ISA boards 48c2ecf20Sopenharmony_ci * Copyright (C) 2004 Arcom Control Systems 58c2ecf20Sopenharmony_ci * Copyright (C) 2008 Pengutronix 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/kernel.h> 98c2ecf20Sopenharmony_ci#include <linux/ioport.h> 108c2ecf20Sopenharmony_ci#include <linux/module.h> 118c2ecf20Sopenharmony_ci#include <linux/moduleparam.h> 128c2ecf20Sopenharmony_ci#include <linux/delay.h> 138c2ecf20Sopenharmony_ci#include <linux/jiffies.h> 148c2ecf20Sopenharmony_ci#include <linux/init.h> 158c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 168c2ecf20Sopenharmony_ci#include <linux/wait.h> 178c2ecf20Sopenharmony_ci#include <linux/isa.h> 188c2ecf20Sopenharmony_ci#include <linux/i2c.h> 198c2ecf20Sopenharmony_ci#include <linux/i2c-algo-pca.h> 208c2ecf20Sopenharmony_ci#include <linux/io.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#include <asm/irq.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#define DRIVER "i2c-pca-isa" 258c2ecf20Sopenharmony_ci#define IO_SIZE 4 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistatic unsigned long base; 288c2ecf20Sopenharmony_cistatic int irq = -1; 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci/* Data sheet recommends 59kHz for 100kHz operation due to variation 318c2ecf20Sopenharmony_ci * in the actual clock rate */ 328c2ecf20Sopenharmony_cistatic int clock = 59000; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistatic struct i2c_adapter pca_isa_ops; 358c2ecf20Sopenharmony_cistatic wait_queue_head_t pca_wait; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistatic void pca_isa_writebyte(void *pd, int reg, int val) 388c2ecf20Sopenharmony_ci{ 398c2ecf20Sopenharmony_ci#ifdef DEBUG_IO 408c2ecf20Sopenharmony_ci static char *names[] = { "T/O", "DAT", "ADR", "CON" }; 418c2ecf20Sopenharmony_ci printk(KERN_DEBUG "*** write %s at %#lx <= %#04x\n", names[reg], 428c2ecf20Sopenharmony_ci base+reg, val); 438c2ecf20Sopenharmony_ci#endif 448c2ecf20Sopenharmony_ci outb(val, base+reg); 458c2ecf20Sopenharmony_ci} 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cistatic int pca_isa_readbyte(void *pd, int reg) 488c2ecf20Sopenharmony_ci{ 498c2ecf20Sopenharmony_ci int res = inb(base+reg); 508c2ecf20Sopenharmony_ci#ifdef DEBUG_IO 518c2ecf20Sopenharmony_ci { 528c2ecf20Sopenharmony_ci static char *names[] = { "STA", "DAT", "ADR", "CON" }; 538c2ecf20Sopenharmony_ci printk(KERN_DEBUG "*** read %s => %#04x\n", names[reg], res); 548c2ecf20Sopenharmony_ci } 558c2ecf20Sopenharmony_ci#endif 568c2ecf20Sopenharmony_ci return res; 578c2ecf20Sopenharmony_ci} 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic int pca_isa_waitforcompletion(void *pd) 608c2ecf20Sopenharmony_ci{ 618c2ecf20Sopenharmony_ci unsigned long timeout; 628c2ecf20Sopenharmony_ci long ret; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci if (irq > -1) { 658c2ecf20Sopenharmony_ci ret = wait_event_timeout(pca_wait, 668c2ecf20Sopenharmony_ci pca_isa_readbyte(pd, I2C_PCA_CON) 678c2ecf20Sopenharmony_ci & I2C_PCA_CON_SI, pca_isa_ops.timeout); 688c2ecf20Sopenharmony_ci } else { 698c2ecf20Sopenharmony_ci /* Do polling */ 708c2ecf20Sopenharmony_ci timeout = jiffies + pca_isa_ops.timeout; 718c2ecf20Sopenharmony_ci do { 728c2ecf20Sopenharmony_ci ret = time_before(jiffies, timeout); 738c2ecf20Sopenharmony_ci if (pca_isa_readbyte(pd, I2C_PCA_CON) 748c2ecf20Sopenharmony_ci & I2C_PCA_CON_SI) 758c2ecf20Sopenharmony_ci break; 768c2ecf20Sopenharmony_ci udelay(100); 778c2ecf20Sopenharmony_ci } while (ret); 788c2ecf20Sopenharmony_ci } 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci return ret > 0; 818c2ecf20Sopenharmony_ci} 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_cistatic void pca_isa_resetchip(void *pd) 848c2ecf20Sopenharmony_ci{ 858c2ecf20Sopenharmony_ci /* apparently only an external reset will do it. not a lot can be done */ 868c2ecf20Sopenharmony_ci printk(KERN_WARNING DRIVER ": Haven't figured out how to do a reset yet\n"); 878c2ecf20Sopenharmony_ci} 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic irqreturn_t pca_handler(int this_irq, void *dev_id) { 908c2ecf20Sopenharmony_ci wake_up(&pca_wait); 918c2ecf20Sopenharmony_ci return IRQ_HANDLED; 928c2ecf20Sopenharmony_ci} 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_cistatic struct i2c_algo_pca_data pca_isa_data = { 958c2ecf20Sopenharmony_ci /* .data intentionally left NULL, not needed with ISA */ 968c2ecf20Sopenharmony_ci .write_byte = pca_isa_writebyte, 978c2ecf20Sopenharmony_ci .read_byte = pca_isa_readbyte, 988c2ecf20Sopenharmony_ci .wait_for_completion = pca_isa_waitforcompletion, 998c2ecf20Sopenharmony_ci .reset_chip = pca_isa_resetchip, 1008c2ecf20Sopenharmony_ci}; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_cistatic struct i2c_adapter pca_isa_ops = { 1038c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1048c2ecf20Sopenharmony_ci .algo_data = &pca_isa_data, 1058c2ecf20Sopenharmony_ci .name = "PCA9564/PCA9665 ISA Adapter", 1068c2ecf20Sopenharmony_ci .timeout = HZ, 1078c2ecf20Sopenharmony_ci}; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_cistatic int pca_isa_match(struct device *dev, unsigned int id) 1108c2ecf20Sopenharmony_ci{ 1118c2ecf20Sopenharmony_ci int match = base != 0; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci if (match) { 1148c2ecf20Sopenharmony_ci if (irq <= -1) 1158c2ecf20Sopenharmony_ci dev_warn(dev, "Using polling mode (specify irq)\n"); 1168c2ecf20Sopenharmony_ci } else 1178c2ecf20Sopenharmony_ci dev_err(dev, "Please specify I/O base\n"); 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci return match; 1208c2ecf20Sopenharmony_ci} 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_cistatic int pca_isa_probe(struct device *dev, unsigned int id) 1238c2ecf20Sopenharmony_ci{ 1248c2ecf20Sopenharmony_ci init_waitqueue_head(&pca_wait); 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci dev_info(dev, "i/o base %#08lx. irq %d\n", base, irq); 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci#ifdef CONFIG_PPC 1298c2ecf20Sopenharmony_ci if (check_legacy_ioport(base)) { 1308c2ecf20Sopenharmony_ci dev_err(dev, "I/O address %#08lx is not available\n", base); 1318c2ecf20Sopenharmony_ci goto out; 1328c2ecf20Sopenharmony_ci } 1338c2ecf20Sopenharmony_ci#endif 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci if (!request_region(base, IO_SIZE, "i2c-pca-isa")) { 1368c2ecf20Sopenharmony_ci dev_err(dev, "I/O address %#08lx is in use\n", base); 1378c2ecf20Sopenharmony_ci goto out; 1388c2ecf20Sopenharmony_ci } 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci if (irq > -1) { 1418c2ecf20Sopenharmony_ci if (request_irq(irq, pca_handler, 0, "i2c-pca-isa", &pca_isa_ops) < 0) { 1428c2ecf20Sopenharmony_ci dev_err(dev, "Request irq%d failed\n", irq); 1438c2ecf20Sopenharmony_ci goto out_region; 1448c2ecf20Sopenharmony_ci } 1458c2ecf20Sopenharmony_ci } 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci pca_isa_data.i2c_clock = clock; 1488c2ecf20Sopenharmony_ci if (i2c_pca_add_bus(&pca_isa_ops) < 0) { 1498c2ecf20Sopenharmony_ci dev_err(dev, "Failed to add i2c bus\n"); 1508c2ecf20Sopenharmony_ci goto out_irq; 1518c2ecf20Sopenharmony_ci } 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci return 0; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci out_irq: 1568c2ecf20Sopenharmony_ci if (irq > -1) 1578c2ecf20Sopenharmony_ci free_irq(irq, &pca_isa_ops); 1588c2ecf20Sopenharmony_ci out_region: 1598c2ecf20Sopenharmony_ci release_region(base, IO_SIZE); 1608c2ecf20Sopenharmony_ci out: 1618c2ecf20Sopenharmony_ci return -ENODEV; 1628c2ecf20Sopenharmony_ci} 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_cistatic int pca_isa_remove(struct device *dev, unsigned int id) 1658c2ecf20Sopenharmony_ci{ 1668c2ecf20Sopenharmony_ci i2c_del_adapter(&pca_isa_ops); 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci if (irq > -1) { 1698c2ecf20Sopenharmony_ci disable_irq(irq); 1708c2ecf20Sopenharmony_ci free_irq(irq, &pca_isa_ops); 1718c2ecf20Sopenharmony_ci } 1728c2ecf20Sopenharmony_ci release_region(base, IO_SIZE); 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci return 0; 1758c2ecf20Sopenharmony_ci} 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_cistatic struct isa_driver pca_isa_driver = { 1788c2ecf20Sopenharmony_ci .match = pca_isa_match, 1798c2ecf20Sopenharmony_ci .probe = pca_isa_probe, 1808c2ecf20Sopenharmony_ci .remove = pca_isa_remove, 1818c2ecf20Sopenharmony_ci .driver = { 1828c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1838c2ecf20Sopenharmony_ci .name = DRIVER, 1848c2ecf20Sopenharmony_ci } 1858c2ecf20Sopenharmony_ci}; 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ciMODULE_AUTHOR("Ian Campbell <icampbell@arcom.com>"); 1888c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ISA base PCA9564/PCA9665 driver"); 1898c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_cimodule_param_hw(base, ulong, ioport, 0); 1928c2ecf20Sopenharmony_ciMODULE_PARM_DESC(base, "I/O base address"); 1938c2ecf20Sopenharmony_cimodule_param_hw(irq, int, irq, 0); 1948c2ecf20Sopenharmony_ciMODULE_PARM_DESC(irq, "IRQ"); 1958c2ecf20Sopenharmony_cimodule_param(clock, int, 0); 1968c2ecf20Sopenharmony_ciMODULE_PARM_DESC(clock, "Clock rate in hertz.\n\t\t" 1978c2ecf20Sopenharmony_ci "For PCA9564: 330000,288000,217000,146000," 1988c2ecf20Sopenharmony_ci "88000,59000,44000,36000\n" 1998c2ecf20Sopenharmony_ci "\t\tFor PCA9665:\tStandard: 60300 - 100099\n" 2008c2ecf20Sopenharmony_ci "\t\t\t\tFast: 100100 - 400099\n" 2018c2ecf20Sopenharmony_ci "\t\t\t\tFast+: 400100 - 10000099\n" 2028c2ecf20Sopenharmony_ci "\t\t\t\tTurbo: Up to 1265800"); 2038c2ecf20Sopenharmony_cimodule_isa_driver(pca_isa_driver, 1); 204