162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci i2c Support for Via Technologies 82C586B South Bridge 462306a36Sopenharmony_ci 562306a36Sopenharmony_ci Copyright (c) 1998, 1999 Kyösti Mälkki <kmalkki@cc.hut.fi> 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci*/ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/kernel.h> 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/pci.h> 1262306a36Sopenharmony_ci#include <linux/ioport.h> 1362306a36Sopenharmony_ci#include <linux/i2c.h> 1462306a36Sopenharmony_ci#include <linux/i2c-algo-bit.h> 1562306a36Sopenharmony_ci#include <linux/io.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci/* Power management registers */ 1862306a36Sopenharmony_ci#define PM_CFG_REVID 0x08 /* silicon revision code */ 1962306a36Sopenharmony_ci#define PM_CFG_IOBASE0 0x20 2062306a36Sopenharmony_ci#define PM_CFG_IOBASE1 0x48 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define I2C_DIR (pm_io_base+0x40) 2362306a36Sopenharmony_ci#define I2C_OUT (pm_io_base+0x42) 2462306a36Sopenharmony_ci#define I2C_IN (pm_io_base+0x44) 2562306a36Sopenharmony_ci#define I2C_SCL 0x02 /* clock bit in DIR/OUT/IN register */ 2662306a36Sopenharmony_ci#define I2C_SDA 0x04 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci/* io-region reservation */ 2962306a36Sopenharmony_ci#define IOSPACE 0x06 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistatic struct pci_driver vt586b_driver; 3262306a36Sopenharmony_cistatic u16 pm_io_base; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci/* 3562306a36Sopenharmony_ci It does not appear from the datasheet that the GPIO pins are 3662306a36Sopenharmony_ci open drain. So a we set a low value by setting the direction to 3762306a36Sopenharmony_ci output and a high value by setting the direction to input and 3862306a36Sopenharmony_ci relying on the required I2C pullup. The data value is initialized 3962306a36Sopenharmony_ci to 0 in via_init() and never changed. 4062306a36Sopenharmony_ci*/ 4162306a36Sopenharmony_cistatic void bit_via_setscl(void *data, int state) 4262306a36Sopenharmony_ci{ 4362306a36Sopenharmony_ci outb(state ? inb(I2C_DIR) & ~I2C_SCL : inb(I2C_DIR) | I2C_SCL, I2C_DIR); 4462306a36Sopenharmony_ci} 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic void bit_via_setsda(void *data, int state) 4762306a36Sopenharmony_ci{ 4862306a36Sopenharmony_ci outb(state ? inb(I2C_DIR) & ~I2C_SDA : inb(I2C_DIR) | I2C_SDA, I2C_DIR); 4962306a36Sopenharmony_ci} 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistatic int bit_via_getscl(void *data) 5262306a36Sopenharmony_ci{ 5362306a36Sopenharmony_ci return (0 != (inb(I2C_IN) & I2C_SCL)); 5462306a36Sopenharmony_ci} 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic int bit_via_getsda(void *data) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci return (0 != (inb(I2C_IN) & I2C_SDA)); 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_cistatic struct i2c_algo_bit_data bit_data = { 6362306a36Sopenharmony_ci .setsda = bit_via_setsda, 6462306a36Sopenharmony_ci .setscl = bit_via_setscl, 6562306a36Sopenharmony_ci .getsda = bit_via_getsda, 6662306a36Sopenharmony_ci .getscl = bit_via_getscl, 6762306a36Sopenharmony_ci .udelay = 5, 6862306a36Sopenharmony_ci .timeout = HZ 6962306a36Sopenharmony_ci}; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_cistatic struct i2c_adapter vt586b_adapter = { 7262306a36Sopenharmony_ci .owner = THIS_MODULE, 7362306a36Sopenharmony_ci .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, 7462306a36Sopenharmony_ci .name = "VIA i2c", 7562306a36Sopenharmony_ci .algo_data = &bit_data, 7662306a36Sopenharmony_ci}; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic const struct pci_device_id vt586b_ids[] = { 8062306a36Sopenharmony_ci { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C586_3) }, 8162306a36Sopenharmony_ci { 0, } 8262306a36Sopenharmony_ci}; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ciMODULE_DEVICE_TABLE (pci, vt586b_ids); 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic int vt586b_probe(struct pci_dev *dev, const struct pci_device_id *id) 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci u16 base; 8962306a36Sopenharmony_ci u8 rev; 9062306a36Sopenharmony_ci int res; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci if (pm_io_base) { 9362306a36Sopenharmony_ci dev_err(&dev->dev, "i2c-via: Will only support one host\n"); 9462306a36Sopenharmony_ci return -ENODEV; 9562306a36Sopenharmony_ci } 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci pci_read_config_byte(dev, PM_CFG_REVID, &rev); 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci switch (rev) { 10062306a36Sopenharmony_ci case 0x00: 10162306a36Sopenharmony_ci base = PM_CFG_IOBASE0; 10262306a36Sopenharmony_ci break; 10362306a36Sopenharmony_ci case 0x01: 10462306a36Sopenharmony_ci case 0x10: 10562306a36Sopenharmony_ci base = PM_CFG_IOBASE1; 10662306a36Sopenharmony_ci break; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci default: 10962306a36Sopenharmony_ci base = PM_CFG_IOBASE1; 11062306a36Sopenharmony_ci /* later revision */ 11162306a36Sopenharmony_ci } 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci pci_read_config_word(dev, base, &pm_io_base); 11462306a36Sopenharmony_ci pm_io_base &= (0xff << 8); 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci if (!request_region(I2C_DIR, IOSPACE, vt586b_driver.name)) { 11762306a36Sopenharmony_ci dev_err(&dev->dev, "IO 0x%x-0x%x already in use\n", I2C_DIR, I2C_DIR + IOSPACE); 11862306a36Sopenharmony_ci return -ENODEV; 11962306a36Sopenharmony_ci } 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci outb(inb(I2C_DIR) & ~(I2C_SDA | I2C_SCL), I2C_DIR); 12262306a36Sopenharmony_ci outb(inb(I2C_OUT) & ~(I2C_SDA | I2C_SCL), I2C_OUT); 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci /* set up the sysfs linkage to our parent device */ 12562306a36Sopenharmony_ci vt586b_adapter.dev.parent = &dev->dev; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci res = i2c_bit_add_bus(&vt586b_adapter); 12862306a36Sopenharmony_ci if ( res < 0 ) { 12962306a36Sopenharmony_ci release_region(I2C_DIR, IOSPACE); 13062306a36Sopenharmony_ci pm_io_base = 0; 13162306a36Sopenharmony_ci return res; 13262306a36Sopenharmony_ci } 13362306a36Sopenharmony_ci return 0; 13462306a36Sopenharmony_ci} 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_cistatic void vt586b_remove(struct pci_dev *dev) 13762306a36Sopenharmony_ci{ 13862306a36Sopenharmony_ci i2c_del_adapter(&vt586b_adapter); 13962306a36Sopenharmony_ci release_region(I2C_DIR, IOSPACE); 14062306a36Sopenharmony_ci pm_io_base = 0; 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_cistatic struct pci_driver vt586b_driver = { 14562306a36Sopenharmony_ci .name = "vt586b_smbus", 14662306a36Sopenharmony_ci .id_table = vt586b_ids, 14762306a36Sopenharmony_ci .probe = vt586b_probe, 14862306a36Sopenharmony_ci .remove = vt586b_remove, 14962306a36Sopenharmony_ci}; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_cimodule_pci_driver(vt586b_driver); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ciMODULE_AUTHOR("Kyösti Mälkki <kmalkki@cc.hut.fi>"); 15462306a36Sopenharmony_ciMODULE_DESCRIPTION("i2c for Via vt82c586b southbridge"); 15562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 156