/* * i2c-nct6775 - Driver for the SMBus master functionality of * Nuvoton NCT677x Super-I/O chips * * Copyright (C) 2019 Adam Honse * * Derived from nct6775 hwmon driver * Copyright (C) 2012 Guenter Roeck * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DRVNAME "i2c-nct6775" /* Nuvoton SMBus address offsets */ #define SMBHSTDAT (0 + nuvoton_nct6793d_smba) #define SMBBLKSZ (1 + nuvoton_nct6793d_smba) #define SMBHSTCMD (2 + nuvoton_nct6793d_smba) #define SMBHSTIDX (3 + nuvoton_nct6793d_smba) //Index field is the Command field on other controllers #define SMBHSTCTL (4 + nuvoton_nct6793d_smba) #define SMBHSTADD (5 + nuvoton_nct6793d_smba) #define SMBHSTERR (9 + nuvoton_nct6793d_smba) #define SMBHSTSTS (0xE + nuvoton_nct6793d_smba) /* Command register */ #define NCT6793D_READ_BYTE 0 #define NCT6793D_READ_WORD 1 #define NCT6793D_READ_BLOCK 2 #define NCT6793D_BLOCK_WRITE_READ_PROC_CALL 3 #define NCT6793D_PROC_CALL 4 #define NCT6793D_WRITE_BYTE 8 #define NCT6793D_WRITE_WORD 9 #define NCT6793D_WRITE_BLOCK 10 /* Control register */ #define NCT6793D_MANUAL_START 128 #define NCT6793D_SOFT_RESET 64 /* Error register */ #define NCT6793D_NO_ACK 32 /* Status register */ #define NCT6793D_FIFO_EMPTY 1 #define NCT6793D_FIFO_FULL 2 #define NCT6793D_MANUAL_ACTIVE 4 #define NCT6775_LD_SMBUS 0x0B /* Other settings */ #define MAX_RETRIES 400 enum kinds { nct6106, nct6775, nct6776, nct6779, nct6791, nct6792, nct6793, nct6795, nct6796, nct6798 }; struct nct6775_sio_data { int sioreg; enum kinds kind; }; /* used to set data->name = nct6775_device_names[data->sio_kind] */ static const char * const nct6775_device_names[] = { "nct6106", "nct6775", "nct6776", "nct6779", "nct6791", "nct6792", "nct6793", "nct6795", "nct6796", "nct6798", }; static const char * const nct6775_sio_names[] __initconst = { "NCT6106D", "NCT6775F", "NCT6776D/F", "NCT6779D", "NCT6791D", "NCT6792D", "NCT6793D", "NCT6795D", "NCT6796D", "NCT6798D", }; #define SIO_REG_LDSEL 0x07 /* Logical device select */ #define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ #define SIO_REG_SMBA 0x62 /* SMBus base address register */ #define SIO_NCT6106_ID 0xc450 #define SIO_NCT6775_ID 0xb470 #define SIO_NCT6776_ID 0xc330 #define SIO_NCT6779_ID 0xc560 #define SIO_NCT6791_ID 0xc800 #define SIO_NCT6792_ID 0xc910 #define SIO_NCT6793_ID 0xd120 #define SIO_NCT6795_ID 0xd350 #define SIO_NCT6796_ID 0xd420 #define SIO_NCT6798_ID 0xd428 #define SIO_ID_MASK 0xFFF0 static inline void superio_outb(int ioreg, int reg, int val) { outb(reg, ioreg); outb(val, ioreg + 1); } static inline int superio_inb(int ioreg, int reg) { outb(reg, ioreg); return inb(ioreg + 1); } static inline void superio_select(int ioreg, int ld) { outb(SIO_REG_LDSEL, ioreg); outb(ld, ioreg + 1); } static inline int superio_enter(int ioreg) { /* * Try to reserve and for exclusive access. */ if (!request_muxed_region(ioreg, 2, DRVNAME)) return -EBUSY; outb(0x87, ioreg); outb(0x87, ioreg); return 0; } static inline void superio_exit(int ioreg) { outb(0xaa, ioreg); outb(0x02, ioreg); outb(0x02, ioreg + 1); release_region(ioreg, 2); } /* * ISA constants */ #define IOREGION_ALIGNMENT (~7) #define IOREGION_LENGTH 2 #define ADDR_REG_OFFSET 0 #define DATA_REG_OFFSET 1 #define NCT6775_REG_BANK 0x4E #define NCT6775_REG_CONFIG 0x40 static struct i2c_adapter *nct6775_adapter; struct i2c_nct6775_adapdata { unsigned short smba; }; /* Return negative errno on error. */ static s32 nct6775_access(struct i2c_adapter * adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data * data) { struct i2c_nct6775_adapdata *adapdata = i2c_get_adapdata(adap); unsigned short nuvoton_nct6793d_smba = adapdata->smba; int i, len, cnt; union i2c_smbus_data tmp_data; int timeout = 0; tmp_data.word = 0; cnt = 0; len = 0; outb_p(NCT6793D_SOFT_RESET, SMBHSTCTL); switch (size) { case I2C_SMBUS_QUICK: outb_p((addr << 1) | read_write, SMBHSTADD); break; case I2C_SMBUS_BYTE_DATA: tmp_data.byte = data->byte; case I2C_SMBUS_BYTE: outb_p((addr << 1) | read_write, SMBHSTADD); outb_p(command, SMBHSTIDX); if (read_write == I2C_SMBUS_WRITE) { outb_p(tmp_data.byte, SMBHSTDAT); outb_p(NCT6793D_WRITE_BYTE, SMBHSTCMD); } else { outb_p(NCT6793D_READ_BYTE, SMBHSTCMD); } break; case I2C_SMBUS_WORD_DATA: outb_p((addr << 1) | read_write, SMBHSTADD); outb_p(command, SMBHSTIDX); if (read_write == I2C_SMBUS_WRITE) { outb_p(data->word & 0xff, SMBHSTDAT); outb_p((data->word & 0xff00) >> 8, SMBHSTDAT); outb_p(NCT6793D_WRITE_WORD, SMBHSTCMD); } else { outb_p(NCT6793D_READ_WORD, SMBHSTCMD); } break; case I2C_SMBUS_BLOCK_DATA: outb_p((addr << 1) | read_write, SMBHSTADD); outb_p(command, SMBHSTIDX); if (read_write == I2C_SMBUS_WRITE) { len = data->block[0]; if (len == 0 || len > I2C_SMBUS_BLOCK_MAX) return -EINVAL; outb_p(len, SMBBLKSZ); cnt = 1; if (len >= 4) { for (i = cnt; i <= 4; i++) { outb_p(data->block[i], SMBHSTDAT); } len -= 4; cnt += 4; } else { for (i = cnt; i <= len; i++ ) { outb_p(data->block[i], SMBHSTDAT); } len = 0; } outb_p(NCT6793D_WRITE_BLOCK, SMBHSTCMD); } else { return -ENOTSUPP; } break; default: dev_warn(&adap->dev, "Unsupported transaction %d\n", size); return -EOPNOTSUPP; } outb_p(NCT6793D_MANUAL_START, SMBHSTCTL); while ((size == I2C_SMBUS_BLOCK_DATA) && (len > 0)) { if (read_write == I2C_SMBUS_WRITE) { timeout = 0; while ((inb_p(SMBHSTSTS) & NCT6793D_FIFO_EMPTY) == 0) { if(timeout > MAX_RETRIES) { return -ETIMEDOUT; } usleep_range(250, 500); timeout++; } //Load more bytes into FIFO if (len >= 4) { for (i = cnt; i <= (cnt + 4); i++) { outb_p(data->block[i], SMBHSTDAT); } len -= 4; cnt += 4; } else { for (i = cnt; i <= (cnt + len); i++) { outb_p(data->block[i], SMBHSTDAT); } len = 0; } } else { return -ENOTSUPP; } } //wait for manual mode to complete timeout = 0; while ((inb_p(SMBHSTSTS) & NCT6793D_MANUAL_ACTIVE) != 0) { if(timeout > MAX_RETRIES) { return -ETIMEDOUT; } usleep_range(250, 500); timeout++; } if ((inb_p(SMBHSTERR) & NCT6793D_NO_ACK) != 0) { return -ENXIO; } else if ((read_write == I2C_SMBUS_WRITE) || (size == I2C_SMBUS_QUICK)) { return 0; } switch (size) { case I2C_SMBUS_QUICK: case I2C_SMBUS_BYTE_DATA: data->byte = inb_p(SMBHSTDAT); break; case I2C_SMBUS_WORD_DATA: data->word = inb_p(SMBHSTDAT) + (inb_p(SMBHSTDAT) << 8); break; } return 0; } static u32 nct6775_func(struct i2c_adapter *adapter) { return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA; } static const struct i2c_algorithm smbus_algorithm = { .smbus_xfer = nct6775_access, .functionality = nct6775_func, }; static int nct6775_add_adapter(unsigned short smba, const char *name, struct i2c_adapter **padap) { struct i2c_adapter *adap; struct i2c_nct6775_adapdata *adapdata; int retval; adap = kzalloc(sizeof(*adap), GFP_KERNEL); if (adap == NULL) { return -ENOMEM; } adap->owner = THIS_MODULE; adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; adap->algo = &smbus_algorithm; adapdata = kzalloc(sizeof(*adapdata), GFP_KERNEL); if (adapdata == NULL) { kfree(adap); return -ENOMEM; } adapdata->smba = smba; snprintf(adap->name, sizeof(adap->name), "SMBus NCT67xx adapter%s at %04x", name, smba); i2c_set_adapdata(adap, adapdata); retval = i2c_add_adapter(adap); if (retval) { kfree(adapdata); kfree(adap); return retval; } *padap = adap; return 0; } static void nct6775_remove_adapter(struct i2c_adapter *adap) { struct i2c_nct6775_adapdata *adapdata = i2c_get_adapdata(adap); if (adapdata->smba) { i2c_del_adapter(adap); kfree(adapdata); kfree(adap); } } //static SIMPLE_DEV_PM_OPS(nct6775_dev_pm_ops, nct6775_suspend, nct6775_resume); /* * when Super-I/O functions move to a separate file, the Super-I/O * bus will manage the lifetime of the device and this module will only keep * track of the nct6775 driver. But since we use platform_device_alloc(), we * must keep track of the device */ static struct platform_device *pdev[2]; static int nct6775_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct nct6775_sio_data *sio_data = dev_get_platdata(dev); struct resource *res; res = platform_get_resource(pdev, IORESOURCE_IO, 0); if (!devm_request_region(&pdev->dev, res->start, IOREGION_LENGTH, DRVNAME)) return -EBUSY; switch (sio_data->kind) { case nct6791: case nct6792: case nct6793: case nct6795: case nct6796: case nct6798: nct6775_add_adapter(res->start, "", &nct6775_adapter); break; default: return -ENODEV; } return 0; } /* static void nct6791_enable_io_mapping(int sioaddr) { int val; val = superio_inb(sioaddr, NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE); if (val & 0x10) { pr_info("Enabling hardware monitor logical device mappings.\n"); superio_outb(sioaddr, NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE, val & ~0x10); } }*/ static struct platform_driver i2c_nct6775_driver = { .driver = { .name = DRVNAME, // .pm = &nct6775_dev_pm_ops, }, .probe = nct6775_probe, }; static void __exit i2c_nct6775_exit(void) { int i; if(nct6775_adapter) nct6775_remove_adapter(nct6775_adapter); for (i = 0; i < ARRAY_SIZE(pdev); i++) { if (pdev[i]) platform_device_unregister(pdev[i]); } platform_driver_unregister(&i2c_nct6775_driver); } /* nct6775_find() looks for a '627 in the Super-I/O config space */ static int __init nct6775_find(int sioaddr, struct nct6775_sio_data *sio_data) { u16 val; int err; int addr; err = superio_enter(sioaddr); if (err) return err; val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8) | superio_inb(sioaddr, SIO_REG_DEVID + 1); switch (val & SIO_ID_MASK) { case SIO_NCT6106_ID: sio_data->kind = nct6106; break; case SIO_NCT6775_ID: sio_data->kind = nct6775; break; case SIO_NCT6776_ID: sio_data->kind = nct6776; break; case SIO_NCT6779_ID: sio_data->kind = nct6779; break; case SIO_NCT6791_ID: sio_data->kind = nct6791; break; case SIO_NCT6792_ID: sio_data->kind = nct6792; break; case SIO_NCT6793_ID: sio_data->kind = nct6793; break; case SIO_NCT6795_ID: sio_data->kind = nct6795; break; case SIO_NCT6796_ID: sio_data->kind = nct6796; break; case SIO_NCT6798_ID: sio_data->kind = nct6798; break; default: if (val != 0xffff) pr_debug("unsupported chip ID: 0x%04x\n", val); superio_exit(sioaddr); return -ENODEV; } /* We have a known chip, find the SMBus I/O address */ superio_select(sioaddr, NCT6775_LD_SMBUS); val = (superio_inb(sioaddr, SIO_REG_SMBA) << 8) | superio_inb(sioaddr, SIO_REG_SMBA + 1); addr = val & IOREGION_ALIGNMENT; if (addr == 0) { pr_err("Refusing to enable a Super-I/O device with a base I/O port 0\n"); superio_exit(sioaddr); return -ENODEV; } //if (sio_data->kind == nct6791 || sio_data->kind == nct6792 || // sio_data->kind == nct6793 || sio_data->kind == nct6795 || // sio_data->kind == nct6796) // nct6791_enable_io_mapping(sioaddr); superio_exit(sioaddr); pr_info("Found %s or compatible chip at %#x:%#x\n", nct6775_sio_names[sio_data->kind], sioaddr, addr); sio_data->sioreg = sioaddr; return addr; } static int __init i2c_nct6775_init(void) { int i, err; bool found = false; int address; struct resource res; struct nct6775_sio_data sio_data; int sioaddr[2] = { 0x2e, 0x4e }; err = platform_driver_register(&i2c_nct6775_driver); if (err) return err; /* * initialize sio_data->kind and sio_data->sioreg. * * when Super-I/O functions move to a separate file, the Super-I/O * driver will probe 0x2e and 0x4e and auto-detect the presence of a * nct6775 hardware monitor, and call probe() */ for (i = 0; i < ARRAY_SIZE(pdev); i++) { address = nct6775_find(sioaddr[i], &sio_data); if (address <= 0) continue; found = true; pdev[i] = platform_device_alloc(DRVNAME, address); if (!pdev[i]) { err = -ENOMEM; goto exit_device_unregister; } err = platform_device_add_data(pdev[i], &sio_data, sizeof(struct nct6775_sio_data)); if (err) goto exit_device_put; memset(&res, 0, sizeof(res)); res.name = DRVNAME; res.start = address; res.end = address + IOREGION_LENGTH - 1; res.flags = IORESOURCE_IO; err = acpi_check_resource_conflict(&res); if (err) { platform_device_put(pdev[i]); pdev[i] = NULL; continue; } err = platform_device_add_resources(pdev[i], &res, 1); if (err) goto exit_device_put; /* platform_device_add calls probe() */ err = platform_device_add(pdev[i]); if (err) goto exit_device_put; } if (!found) { err = -ENODEV; goto exit_unregister; } return 0; exit_device_put: platform_device_put(pdev[i]); exit_device_unregister: while (--i >= 0) { if (pdev[i]) platform_device_unregister(pdev[i]); } exit_unregister: platform_driver_unregister(&i2c_nct6775_driver); return err; } MODULE_AUTHOR("Adam Honse "); MODULE_DESCRIPTION("SMBus driver for NCT6775F and compatible chips"); MODULE_LICENSE("GPL"); module_init(i2c_nct6775_init); module_exit(i2c_nct6775_exit);