162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* ------------------------------------------------------------------------- */ 362306a36Sopenharmony_ci/* i2c-elektor.c i2c-hw access for PCF8584 style isa bus adaptes */ 462306a36Sopenharmony_ci/* ------------------------------------------------------------------------- */ 562306a36Sopenharmony_ci/* Copyright (C) 1995-97 Simon G. Vogl 662306a36Sopenharmony_ci 1998-99 Hans Berglund 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci/* ------------------------------------------------------------------------- */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci/* With some changes from Kyösti Mälkki <kmalkki@cc.hut.fi> and even 1262306a36Sopenharmony_ci Frodo Looijaard <frodol@dds.nl> */ 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci/* Partially rewriten by Oleg I. Vdovikin for mmapped support of 1562306a36Sopenharmony_ci for Alpha Processor Inc. UP-2000(+) boards */ 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include <linux/kernel.h> 1862306a36Sopenharmony_ci#include <linux/ioport.h> 1962306a36Sopenharmony_ci#include <linux/module.h> 2062306a36Sopenharmony_ci#include <linux/delay.h> 2162306a36Sopenharmony_ci#include <linux/init.h> 2262306a36Sopenharmony_ci#include <linux/interrupt.h> 2362306a36Sopenharmony_ci#include <linux/pci.h> 2462306a36Sopenharmony_ci#include <linux/wait.h> 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#include <linux/isa.h> 2762306a36Sopenharmony_ci#include <linux/i2c.h> 2862306a36Sopenharmony_ci#include <linux/i2c-algo-pcf.h> 2962306a36Sopenharmony_ci#include <linux/io.h> 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#include <asm/irq.h> 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#include "../algos/i2c-algo-pcf.h" 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#define DEFAULT_BASE 0x330 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistatic int base; 3862306a36Sopenharmony_cistatic u8 __iomem *base_iomem; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cistatic int irq; 4162306a36Sopenharmony_cistatic int clock = 0x1c; 4262306a36Sopenharmony_cistatic int own = 0x55; 4362306a36Sopenharmony_cistatic int mmapped; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci/* vdovikin: removed static struct i2c_pcf_isa gpi; code - 4662306a36Sopenharmony_ci this module in real supports only one device, due to missing arguments 4762306a36Sopenharmony_ci in some functions, called from the algo-pcf module. Sometimes it's 4862306a36Sopenharmony_ci need to be rewriten - but for now just remove this for simpler reading */ 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistatic wait_queue_head_t pcf_wait; 5162306a36Sopenharmony_cistatic int pcf_pending; 5262306a36Sopenharmony_cistatic DEFINE_SPINLOCK(lock); 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_cistatic struct i2c_adapter pcf_isa_ops; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci/* ----- local functions ---------------------------------------------- */ 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_cistatic void pcf_isa_setbyte(void *data, int ctl, int val) 5962306a36Sopenharmony_ci{ 6062306a36Sopenharmony_ci u8 __iomem *address = ctl ? (base_iomem + 1) : base_iomem; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci /* enable irq if any specified for serial operation */ 6362306a36Sopenharmony_ci if (ctl && irq && (val & I2C_PCF_ESO)) { 6462306a36Sopenharmony_ci val |= I2C_PCF_ENI; 6562306a36Sopenharmony_ci } 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci pr_debug("%s: Write %p 0x%02X\n", pcf_isa_ops.name, address, val); 6862306a36Sopenharmony_ci iowrite8(val, address); 6962306a36Sopenharmony_ci#ifdef __alpha__ 7062306a36Sopenharmony_ci /* API UP2000 needs some hardware fudging to make the write stick */ 7162306a36Sopenharmony_ci iowrite8(val, address); 7262306a36Sopenharmony_ci#endif 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_cistatic int pcf_isa_getbyte(void *data, int ctl) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci u8 __iomem *address = ctl ? (base_iomem + 1) : base_iomem; 7862306a36Sopenharmony_ci int val = ioread8(address); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci pr_debug("%s: Read %p 0x%02X\n", pcf_isa_ops.name, address, val); 8162306a36Sopenharmony_ci return (val); 8262306a36Sopenharmony_ci} 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic int pcf_isa_getown(void *data) 8562306a36Sopenharmony_ci{ 8662306a36Sopenharmony_ci return (own); 8762306a36Sopenharmony_ci} 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_cistatic int pcf_isa_getclock(void *data) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci return (clock); 9362306a36Sopenharmony_ci} 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistatic void pcf_isa_waitforpin(void *data) 9662306a36Sopenharmony_ci{ 9762306a36Sopenharmony_ci DEFINE_WAIT(wait); 9862306a36Sopenharmony_ci int timeout = 2; 9962306a36Sopenharmony_ci unsigned long flags; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci if (irq > 0) { 10262306a36Sopenharmony_ci spin_lock_irqsave(&lock, flags); 10362306a36Sopenharmony_ci if (pcf_pending == 0) { 10462306a36Sopenharmony_ci spin_unlock_irqrestore(&lock, flags); 10562306a36Sopenharmony_ci prepare_to_wait(&pcf_wait, &wait, TASK_INTERRUPTIBLE); 10662306a36Sopenharmony_ci if (schedule_timeout(timeout*HZ)) { 10762306a36Sopenharmony_ci spin_lock_irqsave(&lock, flags); 10862306a36Sopenharmony_ci if (pcf_pending == 1) { 10962306a36Sopenharmony_ci pcf_pending = 0; 11062306a36Sopenharmony_ci } 11162306a36Sopenharmony_ci spin_unlock_irqrestore(&lock, flags); 11262306a36Sopenharmony_ci } 11362306a36Sopenharmony_ci finish_wait(&pcf_wait, &wait); 11462306a36Sopenharmony_ci } else { 11562306a36Sopenharmony_ci pcf_pending = 0; 11662306a36Sopenharmony_ci spin_unlock_irqrestore(&lock, flags); 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci } else { 11962306a36Sopenharmony_ci udelay(100); 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_cistatic irqreturn_t pcf_isa_handler(int this_irq, void *dev_id) { 12562306a36Sopenharmony_ci spin_lock(&lock); 12662306a36Sopenharmony_ci pcf_pending = 1; 12762306a36Sopenharmony_ci spin_unlock(&lock); 12862306a36Sopenharmony_ci wake_up_interruptible(&pcf_wait); 12962306a36Sopenharmony_ci return IRQ_HANDLED; 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_cistatic int pcf_isa_init(void) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci if (!mmapped) { 13662306a36Sopenharmony_ci if (!request_region(base, 2, pcf_isa_ops.name)) { 13762306a36Sopenharmony_ci printk(KERN_ERR "%s: requested I/O region (%#x:2) is " 13862306a36Sopenharmony_ci "in use\n", pcf_isa_ops.name, base); 13962306a36Sopenharmony_ci return -ENODEV; 14062306a36Sopenharmony_ci } 14162306a36Sopenharmony_ci base_iomem = ioport_map(base, 2); 14262306a36Sopenharmony_ci if (!base_iomem) { 14362306a36Sopenharmony_ci printk(KERN_ERR "%s: remap of I/O region %#x failed\n", 14462306a36Sopenharmony_ci pcf_isa_ops.name, base); 14562306a36Sopenharmony_ci release_region(base, 2); 14662306a36Sopenharmony_ci return -ENODEV; 14762306a36Sopenharmony_ci } 14862306a36Sopenharmony_ci } else { 14962306a36Sopenharmony_ci if (!request_mem_region(base, 2, pcf_isa_ops.name)) { 15062306a36Sopenharmony_ci printk(KERN_ERR "%s: requested memory region (%#x:2) " 15162306a36Sopenharmony_ci "is in use\n", pcf_isa_ops.name, base); 15262306a36Sopenharmony_ci return -ENODEV; 15362306a36Sopenharmony_ci } 15462306a36Sopenharmony_ci base_iomem = ioremap(base, 2); 15562306a36Sopenharmony_ci if (base_iomem == NULL) { 15662306a36Sopenharmony_ci printk(KERN_ERR "%s: remap of memory region %#x " 15762306a36Sopenharmony_ci "failed\n", pcf_isa_ops.name, base); 15862306a36Sopenharmony_ci release_mem_region(base, 2); 15962306a36Sopenharmony_ci return -ENODEV; 16062306a36Sopenharmony_ci } 16162306a36Sopenharmony_ci } 16262306a36Sopenharmony_ci pr_debug("%s: registers %#x remapped to %p\n", pcf_isa_ops.name, base, 16362306a36Sopenharmony_ci base_iomem); 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci if (irq > 0) { 16662306a36Sopenharmony_ci if (request_irq(irq, pcf_isa_handler, 0, pcf_isa_ops.name, 16762306a36Sopenharmony_ci NULL) < 0) { 16862306a36Sopenharmony_ci printk(KERN_ERR "%s: Request irq%d failed\n", 16962306a36Sopenharmony_ci pcf_isa_ops.name, irq); 17062306a36Sopenharmony_ci irq = 0; 17162306a36Sopenharmony_ci } else 17262306a36Sopenharmony_ci enable_irq(irq); 17362306a36Sopenharmony_ci } 17462306a36Sopenharmony_ci return 0; 17562306a36Sopenharmony_ci} 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci/* ------------------------------------------------------------------------ 17862306a36Sopenharmony_ci * Encapsulate the above functions in the correct operations structure. 17962306a36Sopenharmony_ci * This is only done when more than one hardware adapter is supported. 18062306a36Sopenharmony_ci */ 18162306a36Sopenharmony_cistatic struct i2c_algo_pcf_data pcf_isa_data = { 18262306a36Sopenharmony_ci .setpcf = pcf_isa_setbyte, 18362306a36Sopenharmony_ci .getpcf = pcf_isa_getbyte, 18462306a36Sopenharmony_ci .getown = pcf_isa_getown, 18562306a36Sopenharmony_ci .getclock = pcf_isa_getclock, 18662306a36Sopenharmony_ci .waitforpin = pcf_isa_waitforpin, 18762306a36Sopenharmony_ci}; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistatic struct i2c_adapter pcf_isa_ops = { 19062306a36Sopenharmony_ci .owner = THIS_MODULE, 19162306a36Sopenharmony_ci .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, 19262306a36Sopenharmony_ci .algo_data = &pcf_isa_data, 19362306a36Sopenharmony_ci .name = "i2c-elektor", 19462306a36Sopenharmony_ci}; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_cistatic int elektor_match(struct device *dev, unsigned int id) 19762306a36Sopenharmony_ci{ 19862306a36Sopenharmony_ci#ifdef __alpha__ 19962306a36Sopenharmony_ci /* check to see we have memory mapped PCF8584 connected to the 20062306a36Sopenharmony_ci Cypress cy82c693 PCI-ISA bridge as on UP2000 board */ 20162306a36Sopenharmony_ci if (base == 0) { 20262306a36Sopenharmony_ci struct pci_dev *cy693_dev; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci cy693_dev = pci_get_device(PCI_VENDOR_ID_CONTAQ, 20562306a36Sopenharmony_ci PCI_DEVICE_ID_CONTAQ_82C693, NULL); 20662306a36Sopenharmony_ci if (cy693_dev) { 20762306a36Sopenharmony_ci unsigned char config; 20862306a36Sopenharmony_ci /* yeap, we've found cypress, let's check config */ 20962306a36Sopenharmony_ci if (!pci_read_config_byte(cy693_dev, 0x47, &config)) { 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci dev_dbg(dev, "found cy82c693, config " 21262306a36Sopenharmony_ci "register 0x47 = 0x%02x\n", config); 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci /* UP2000 board has this register set to 0xe1, 21562306a36Sopenharmony_ci but the most significant bit as seems can be 21662306a36Sopenharmony_ci reset during the proper initialisation 21762306a36Sopenharmony_ci sequence if guys from API decides to do that 21862306a36Sopenharmony_ci (so, we can even enable Tsunami Pchip 21962306a36Sopenharmony_ci window for the upper 1 Gb) */ 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci /* so just check for ROMCS at 0xe0000, 22262306a36Sopenharmony_ci ROMCS enabled for writes 22362306a36Sopenharmony_ci and external XD Bus buffer in use. */ 22462306a36Sopenharmony_ci if ((config & 0x7f) == 0x61) { 22562306a36Sopenharmony_ci /* seems to be UP2000 like board */ 22662306a36Sopenharmony_ci base = 0xe0000; 22762306a36Sopenharmony_ci mmapped = 1; 22862306a36Sopenharmony_ci /* UP2000 drives ISA with 22962306a36Sopenharmony_ci 8.25 MHz (PCI/4) clock 23062306a36Sopenharmony_ci (this can be read from cypress) */ 23162306a36Sopenharmony_ci clock = I2C_PCF_CLK | I2C_PCF_TRNS90; 23262306a36Sopenharmony_ci dev_info(dev, "found API UP2000 like " 23362306a36Sopenharmony_ci "board, will probe PCF8584 " 23462306a36Sopenharmony_ci "later\n"); 23562306a36Sopenharmony_ci } 23662306a36Sopenharmony_ci } 23762306a36Sopenharmony_ci pci_dev_put(cy693_dev); 23862306a36Sopenharmony_ci } 23962306a36Sopenharmony_ci } 24062306a36Sopenharmony_ci#endif 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci /* sanity checks for mmapped I/O */ 24362306a36Sopenharmony_ci if (mmapped && base < 0xc8000) { 24462306a36Sopenharmony_ci dev_err(dev, "incorrect base address (%#x) specified " 24562306a36Sopenharmony_ci "for mmapped I/O\n", base); 24662306a36Sopenharmony_ci return 0; 24762306a36Sopenharmony_ci } 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci if (base == 0) { 25062306a36Sopenharmony_ci base = DEFAULT_BASE; 25162306a36Sopenharmony_ci } 25262306a36Sopenharmony_ci return 1; 25362306a36Sopenharmony_ci} 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_cistatic int elektor_probe(struct device *dev, unsigned int id) 25662306a36Sopenharmony_ci{ 25762306a36Sopenharmony_ci init_waitqueue_head(&pcf_wait); 25862306a36Sopenharmony_ci if (pcf_isa_init()) 25962306a36Sopenharmony_ci return -ENODEV; 26062306a36Sopenharmony_ci pcf_isa_ops.dev.parent = dev; 26162306a36Sopenharmony_ci if (i2c_pcf_add_bus(&pcf_isa_ops) < 0) 26262306a36Sopenharmony_ci goto fail; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci dev_info(dev, "found device at %#x\n", base); 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci return 0; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci fail: 26962306a36Sopenharmony_ci if (irq > 0) { 27062306a36Sopenharmony_ci disable_irq(irq); 27162306a36Sopenharmony_ci free_irq(irq, NULL); 27262306a36Sopenharmony_ci } 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci if (!mmapped) { 27562306a36Sopenharmony_ci ioport_unmap(base_iomem); 27662306a36Sopenharmony_ci release_region(base, 2); 27762306a36Sopenharmony_ci } else { 27862306a36Sopenharmony_ci iounmap(base_iomem); 27962306a36Sopenharmony_ci release_mem_region(base, 2); 28062306a36Sopenharmony_ci } 28162306a36Sopenharmony_ci return -ENODEV; 28262306a36Sopenharmony_ci} 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_cistatic void elektor_remove(struct device *dev, unsigned int id) 28562306a36Sopenharmony_ci{ 28662306a36Sopenharmony_ci i2c_del_adapter(&pcf_isa_ops); 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci if (irq > 0) { 28962306a36Sopenharmony_ci disable_irq(irq); 29062306a36Sopenharmony_ci free_irq(irq, NULL); 29162306a36Sopenharmony_ci } 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci if (!mmapped) { 29462306a36Sopenharmony_ci ioport_unmap(base_iomem); 29562306a36Sopenharmony_ci release_region(base, 2); 29662306a36Sopenharmony_ci } else { 29762306a36Sopenharmony_ci iounmap(base_iomem); 29862306a36Sopenharmony_ci release_mem_region(base, 2); 29962306a36Sopenharmony_ci } 30062306a36Sopenharmony_ci} 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_cistatic struct isa_driver i2c_elektor_driver = { 30362306a36Sopenharmony_ci .match = elektor_match, 30462306a36Sopenharmony_ci .probe = elektor_probe, 30562306a36Sopenharmony_ci .remove = elektor_remove, 30662306a36Sopenharmony_ci .driver = { 30762306a36Sopenharmony_ci .owner = THIS_MODULE, 30862306a36Sopenharmony_ci .name = "i2c-elektor", 30962306a36Sopenharmony_ci }, 31062306a36Sopenharmony_ci}; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ciMODULE_AUTHOR("Hans Berglund <hb@spacetec.no>"); 31362306a36Sopenharmony_ciMODULE_DESCRIPTION("I2C-Bus adapter routines for PCF8584 ISA bus adapter"); 31462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_cimodule_param_hw(base, int, ioport_or_iomem, 0); 31762306a36Sopenharmony_cimodule_param_hw(irq, int, irq, 0); 31862306a36Sopenharmony_cimodule_param(clock, int, 0); 31962306a36Sopenharmony_cimodule_param(own, int, 0); 32062306a36Sopenharmony_cimodule_param_hw(mmapped, int, other, 0); 32162306a36Sopenharmony_cimodule_isa_driver(i2c_elektor_driver, 1); 322