18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci Copyright (c) 2002,2003 Alexander Malysh <amalysh@web.de> 48c2ecf20Sopenharmony_ci 58c2ecf20Sopenharmony_ci*/ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci/* 88c2ecf20Sopenharmony_ci Status: beta 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci Supports: 118c2ecf20Sopenharmony_ci SIS 630 128c2ecf20Sopenharmony_ci SIS 730 138c2ecf20Sopenharmony_ci SIS 964 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci Notable differences between chips: 168c2ecf20Sopenharmony_ci +------------------------+--------------------+-------------------+ 178c2ecf20Sopenharmony_ci | | SIS630/730 | SIS964 | 188c2ecf20Sopenharmony_ci +------------------------+--------------------+-------------------+ 198c2ecf20Sopenharmony_ci | Clock | 14kHz/56kHz | 55.56kHz/27.78kHz | 208c2ecf20Sopenharmony_ci | SMBus registers offset | 0x80 | 0xE0 | 218c2ecf20Sopenharmony_ci | SMB_CNT | Bit 1 = Slave Busy | Bit 1 = Bus probe | 228c2ecf20Sopenharmony_ci | (not used yet) | Bit 3 is reserved | Bit 3 = Last byte | 238c2ecf20Sopenharmony_ci | SMB_PCOUNT | Offset + 0x06 | Offset + 0x14 | 248c2ecf20Sopenharmony_ci | SMB_COUNT | 4:0 bits | 5:0 bits | 258c2ecf20Sopenharmony_ci +------------------------+--------------------+-------------------+ 268c2ecf20Sopenharmony_ci (Other differences don't affect the functions provided by the driver) 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci Note: we assume there can only be one device, with one SMBus interface. 298c2ecf20Sopenharmony_ci*/ 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci#include <linux/kernel.h> 328c2ecf20Sopenharmony_ci#include <linux/module.h> 338c2ecf20Sopenharmony_ci#include <linux/delay.h> 348c2ecf20Sopenharmony_ci#include <linux/pci.h> 358c2ecf20Sopenharmony_ci#include <linux/ioport.h> 368c2ecf20Sopenharmony_ci#include <linux/i2c.h> 378c2ecf20Sopenharmony_ci#include <linux/acpi.h> 388c2ecf20Sopenharmony_ci#include <linux/io.h> 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci/* SIS964 id is defined here as we are the only file using it */ 418c2ecf20Sopenharmony_ci#define PCI_DEVICE_ID_SI_964 0x0964 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci/* SIS630/730/964 SMBus registers */ 448c2ecf20Sopenharmony_ci#define SMB_STS 0x00 /* status */ 458c2ecf20Sopenharmony_ci#define SMB_CNT 0x02 /* control */ 468c2ecf20Sopenharmony_ci#define SMBHOST_CNT 0x03 /* host control */ 478c2ecf20Sopenharmony_ci#define SMB_ADDR 0x04 /* address */ 488c2ecf20Sopenharmony_ci#define SMB_CMD 0x05 /* command */ 498c2ecf20Sopenharmony_ci#define SMB_COUNT 0x07 /* byte count */ 508c2ecf20Sopenharmony_ci#define SMB_BYTE 0x08 /* ~0x8F data byte field */ 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci/* SMB_STS register */ 538c2ecf20Sopenharmony_ci#define BYTE_DONE_STS 0x10 /* Byte Done Status / Block Array */ 548c2ecf20Sopenharmony_ci#define SMBCOL_STS 0x04 /* Collision */ 558c2ecf20Sopenharmony_ci#define SMBERR_STS 0x02 /* Device error */ 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci/* SMB_CNT register */ 588c2ecf20Sopenharmony_ci#define MSTO_EN 0x40 /* Host Master Timeout Enable */ 598c2ecf20Sopenharmony_ci#define SMBCLK_SEL 0x20 /* Host master clock selection */ 608c2ecf20Sopenharmony_ci#define SMB_PROBE 0x02 /* Bus Probe/Slave busy */ 618c2ecf20Sopenharmony_ci#define SMB_HOSTBUSY 0x01 /* Host Busy */ 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci/* SMBHOST_CNT register */ 648c2ecf20Sopenharmony_ci#define SMB_KILL 0x20 /* Kill */ 658c2ecf20Sopenharmony_ci#define SMB_START 0x10 /* Start */ 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci/* register count for request_region 688c2ecf20Sopenharmony_ci * As we don't use SMB_PCOUNT, 20 is ok for SiS630 and SiS964 698c2ecf20Sopenharmony_ci */ 708c2ecf20Sopenharmony_ci#define SIS630_SMB_IOREGION 20 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci/* PCI address constants */ 738c2ecf20Sopenharmony_ci/* acpi base address register */ 748c2ecf20Sopenharmony_ci#define SIS630_ACPI_BASE_REG 0x74 758c2ecf20Sopenharmony_ci/* bios control register */ 768c2ecf20Sopenharmony_ci#define SIS630_BIOS_CTL_REG 0x40 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci/* Other settings */ 798c2ecf20Sopenharmony_ci#define MAX_TIMEOUT 500 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci/* SIS630 constants */ 828c2ecf20Sopenharmony_ci#define SIS630_QUICK 0x00 838c2ecf20Sopenharmony_ci#define SIS630_BYTE 0x01 848c2ecf20Sopenharmony_ci#define SIS630_BYTE_DATA 0x02 858c2ecf20Sopenharmony_ci#define SIS630_WORD_DATA 0x03 868c2ecf20Sopenharmony_ci#define SIS630_PCALL 0x04 878c2ecf20Sopenharmony_ci#define SIS630_BLOCK_DATA 0x05 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic struct pci_driver sis630_driver; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci/* insmod parameters */ 928c2ecf20Sopenharmony_cistatic bool high_clock; 938c2ecf20Sopenharmony_cistatic bool force; 948c2ecf20Sopenharmony_cimodule_param(high_clock, bool, 0); 958c2ecf20Sopenharmony_ciMODULE_PARM_DESC(high_clock, 968c2ecf20Sopenharmony_ci "Set Host Master Clock to 56KHz (default 14KHz) (SIS630/730 only)."); 978c2ecf20Sopenharmony_cimodule_param(force, bool, 0); 988c2ecf20Sopenharmony_ciMODULE_PARM_DESC(force, "Forcibly enable the SIS630. DANGEROUS!"); 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci/* SMBus base adress */ 1018c2ecf20Sopenharmony_cistatic unsigned short smbus_base; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci/* supported chips */ 1048c2ecf20Sopenharmony_cistatic int supported[] = { 1058c2ecf20Sopenharmony_ci PCI_DEVICE_ID_SI_630, 1068c2ecf20Sopenharmony_ci PCI_DEVICE_ID_SI_730, 1078c2ecf20Sopenharmony_ci PCI_DEVICE_ID_SI_760, 1088c2ecf20Sopenharmony_ci 0 /* terminates the list */ 1098c2ecf20Sopenharmony_ci}; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_cistatic inline u8 sis630_read(u8 reg) 1128c2ecf20Sopenharmony_ci{ 1138c2ecf20Sopenharmony_ci return inb(smbus_base + reg); 1148c2ecf20Sopenharmony_ci} 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_cistatic inline void sis630_write(u8 reg, u8 data) 1178c2ecf20Sopenharmony_ci{ 1188c2ecf20Sopenharmony_ci outb(data, smbus_base + reg); 1198c2ecf20Sopenharmony_ci} 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_cistatic int sis630_transaction_start(struct i2c_adapter *adap, int size, 1228c2ecf20Sopenharmony_ci u8 *oldclock) 1238c2ecf20Sopenharmony_ci{ 1248c2ecf20Sopenharmony_ci int temp; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci /* Make sure the SMBus host is ready to start transmitting. */ 1278c2ecf20Sopenharmony_ci temp = sis630_read(SMB_CNT); 1288c2ecf20Sopenharmony_ci if ((temp & (SMB_PROBE | SMB_HOSTBUSY)) != 0x00) { 1298c2ecf20Sopenharmony_ci dev_dbg(&adap->dev, "SMBus busy (%02x). Resetting...\n", temp); 1308c2ecf20Sopenharmony_ci /* kill smbus transaction */ 1318c2ecf20Sopenharmony_ci sis630_write(SMBHOST_CNT, SMB_KILL); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci temp = sis630_read(SMB_CNT); 1348c2ecf20Sopenharmony_ci if (temp & (SMB_PROBE | SMB_HOSTBUSY)) { 1358c2ecf20Sopenharmony_ci dev_dbg(&adap->dev, "Failed! (%02x)\n", temp); 1368c2ecf20Sopenharmony_ci return -EBUSY; 1378c2ecf20Sopenharmony_ci } else { 1388c2ecf20Sopenharmony_ci dev_dbg(&adap->dev, "Successful!\n"); 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci } 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci /* save old clock, so we can prevent machine for hung */ 1438c2ecf20Sopenharmony_ci *oldclock = sis630_read(SMB_CNT); 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci dev_dbg(&adap->dev, "saved clock 0x%02x\n", *oldclock); 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci /* disable timeout interrupt, 1488c2ecf20Sopenharmony_ci * set Host Master Clock to 56KHz if requested */ 1498c2ecf20Sopenharmony_ci if (high_clock) 1508c2ecf20Sopenharmony_ci sis630_write(SMB_CNT, SMBCLK_SEL); 1518c2ecf20Sopenharmony_ci else 1528c2ecf20Sopenharmony_ci sis630_write(SMB_CNT, (*oldclock & ~MSTO_EN)); 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci /* clear all sticky bits */ 1558c2ecf20Sopenharmony_ci temp = sis630_read(SMB_STS); 1568c2ecf20Sopenharmony_ci sis630_write(SMB_STS, temp & 0x1e); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci /* start the transaction by setting bit 4 and size */ 1598c2ecf20Sopenharmony_ci sis630_write(SMBHOST_CNT, SMB_START | (size & 0x07)); 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci return 0; 1628c2ecf20Sopenharmony_ci} 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_cistatic int sis630_transaction_wait(struct i2c_adapter *adap, int size) 1658c2ecf20Sopenharmony_ci{ 1668c2ecf20Sopenharmony_ci int temp, result = 0, timeout = 0; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci /* We will always wait for a fraction of a second! */ 1698c2ecf20Sopenharmony_ci do { 1708c2ecf20Sopenharmony_ci msleep(1); 1718c2ecf20Sopenharmony_ci temp = sis630_read(SMB_STS); 1728c2ecf20Sopenharmony_ci /* check if block transmitted */ 1738c2ecf20Sopenharmony_ci if (size == SIS630_BLOCK_DATA && (temp & BYTE_DONE_STS)) 1748c2ecf20Sopenharmony_ci break; 1758c2ecf20Sopenharmony_ci } while (!(temp & 0x0e) && (timeout++ < MAX_TIMEOUT)); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci /* If the SMBus is still busy, we give up */ 1788c2ecf20Sopenharmony_ci if (timeout > MAX_TIMEOUT) { 1798c2ecf20Sopenharmony_ci dev_dbg(&adap->dev, "SMBus Timeout!\n"); 1808c2ecf20Sopenharmony_ci result = -ETIMEDOUT; 1818c2ecf20Sopenharmony_ci } 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci if (temp & SMBERR_STS) { 1848c2ecf20Sopenharmony_ci dev_dbg(&adap->dev, "Error: Failed bus transaction\n"); 1858c2ecf20Sopenharmony_ci result = -ENXIO; 1868c2ecf20Sopenharmony_ci } 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci if (temp & SMBCOL_STS) { 1898c2ecf20Sopenharmony_ci dev_err(&adap->dev, "Bus collision!\n"); 1908c2ecf20Sopenharmony_ci result = -EAGAIN; 1918c2ecf20Sopenharmony_ci } 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci return result; 1948c2ecf20Sopenharmony_ci} 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_cistatic void sis630_transaction_end(struct i2c_adapter *adap, u8 oldclock) 1978c2ecf20Sopenharmony_ci{ 1988c2ecf20Sopenharmony_ci /* clear all status "sticky" bits */ 1998c2ecf20Sopenharmony_ci sis630_write(SMB_STS, 0xFF); 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci dev_dbg(&adap->dev, 2028c2ecf20Sopenharmony_ci "SMB_CNT before clock restore 0x%02x\n", sis630_read(SMB_CNT)); 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci /* 2058c2ecf20Sopenharmony_ci * restore old Host Master Clock if high_clock is set 2068c2ecf20Sopenharmony_ci * and oldclock was not 56KHz 2078c2ecf20Sopenharmony_ci */ 2088c2ecf20Sopenharmony_ci if (high_clock && !(oldclock & SMBCLK_SEL)) 2098c2ecf20Sopenharmony_ci sis630_write(SMB_CNT, sis630_read(SMB_CNT) & ~SMBCLK_SEL); 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci dev_dbg(&adap->dev, 2128c2ecf20Sopenharmony_ci "SMB_CNT after clock restore 0x%02x\n", sis630_read(SMB_CNT)); 2138c2ecf20Sopenharmony_ci} 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_cistatic int sis630_transaction(struct i2c_adapter *adap, int size) 2168c2ecf20Sopenharmony_ci{ 2178c2ecf20Sopenharmony_ci int result = 0; 2188c2ecf20Sopenharmony_ci u8 oldclock = 0; 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci result = sis630_transaction_start(adap, size, &oldclock); 2218c2ecf20Sopenharmony_ci if (!result) { 2228c2ecf20Sopenharmony_ci result = sis630_transaction_wait(adap, size); 2238c2ecf20Sopenharmony_ci sis630_transaction_end(adap, oldclock); 2248c2ecf20Sopenharmony_ci } 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci return result; 2278c2ecf20Sopenharmony_ci} 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_cistatic int sis630_block_data(struct i2c_adapter *adap, 2308c2ecf20Sopenharmony_ci union i2c_smbus_data *data, int read_write) 2318c2ecf20Sopenharmony_ci{ 2328c2ecf20Sopenharmony_ci int i, len = 0, rc = 0; 2338c2ecf20Sopenharmony_ci u8 oldclock = 0; 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci if (read_write == I2C_SMBUS_WRITE) { 2368c2ecf20Sopenharmony_ci len = data->block[0]; 2378c2ecf20Sopenharmony_ci if (len < 0) 2388c2ecf20Sopenharmony_ci len = 0; 2398c2ecf20Sopenharmony_ci else if (len > 32) 2408c2ecf20Sopenharmony_ci len = 32; 2418c2ecf20Sopenharmony_ci sis630_write(SMB_COUNT, len); 2428c2ecf20Sopenharmony_ci for (i = 1; i <= len; i++) { 2438c2ecf20Sopenharmony_ci dev_dbg(&adap->dev, 2448c2ecf20Sopenharmony_ci "set data 0x%02x\n", data->block[i]); 2458c2ecf20Sopenharmony_ci /* set data */ 2468c2ecf20Sopenharmony_ci sis630_write(SMB_BYTE + (i - 1) % 8, data->block[i]); 2478c2ecf20Sopenharmony_ci if (i == 8 || (len < 8 && i == len)) { 2488c2ecf20Sopenharmony_ci dev_dbg(&adap->dev, 2498c2ecf20Sopenharmony_ci "start trans len=%d i=%d\n", len, i); 2508c2ecf20Sopenharmony_ci /* first transaction */ 2518c2ecf20Sopenharmony_ci rc = sis630_transaction_start(adap, 2528c2ecf20Sopenharmony_ci SIS630_BLOCK_DATA, &oldclock); 2538c2ecf20Sopenharmony_ci if (rc) 2548c2ecf20Sopenharmony_ci return rc; 2558c2ecf20Sopenharmony_ci } else if ((i - 1) % 8 == 7 || i == len) { 2568c2ecf20Sopenharmony_ci dev_dbg(&adap->dev, 2578c2ecf20Sopenharmony_ci "trans_wait len=%d i=%d\n", len, i); 2588c2ecf20Sopenharmony_ci if (i > 8) { 2598c2ecf20Sopenharmony_ci dev_dbg(&adap->dev, 2608c2ecf20Sopenharmony_ci "clear smbary_sts" 2618c2ecf20Sopenharmony_ci " len=%d i=%d\n", len, i); 2628c2ecf20Sopenharmony_ci /* 2638c2ecf20Sopenharmony_ci If this is not first transaction, 2648c2ecf20Sopenharmony_ci we must clear sticky bit. 2658c2ecf20Sopenharmony_ci clear SMBARY_STS 2668c2ecf20Sopenharmony_ci */ 2678c2ecf20Sopenharmony_ci sis630_write(SMB_STS, BYTE_DONE_STS); 2688c2ecf20Sopenharmony_ci } 2698c2ecf20Sopenharmony_ci rc = sis630_transaction_wait(adap, 2708c2ecf20Sopenharmony_ci SIS630_BLOCK_DATA); 2718c2ecf20Sopenharmony_ci if (rc) { 2728c2ecf20Sopenharmony_ci dev_dbg(&adap->dev, 2738c2ecf20Sopenharmony_ci "trans_wait failed\n"); 2748c2ecf20Sopenharmony_ci break; 2758c2ecf20Sopenharmony_ci } 2768c2ecf20Sopenharmony_ci } 2778c2ecf20Sopenharmony_ci } 2788c2ecf20Sopenharmony_ci } else { 2798c2ecf20Sopenharmony_ci /* read request */ 2808c2ecf20Sopenharmony_ci data->block[0] = len = 0; 2818c2ecf20Sopenharmony_ci rc = sis630_transaction_start(adap, 2828c2ecf20Sopenharmony_ci SIS630_BLOCK_DATA, &oldclock); 2838c2ecf20Sopenharmony_ci if (rc) 2848c2ecf20Sopenharmony_ci return rc; 2858c2ecf20Sopenharmony_ci do { 2868c2ecf20Sopenharmony_ci rc = sis630_transaction_wait(adap, SIS630_BLOCK_DATA); 2878c2ecf20Sopenharmony_ci if (rc) { 2888c2ecf20Sopenharmony_ci dev_dbg(&adap->dev, "trans_wait failed\n"); 2898c2ecf20Sopenharmony_ci break; 2908c2ecf20Sopenharmony_ci } 2918c2ecf20Sopenharmony_ci /* if this first transaction then read byte count */ 2928c2ecf20Sopenharmony_ci if (len == 0) 2938c2ecf20Sopenharmony_ci data->block[0] = sis630_read(SMB_COUNT); 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci /* just to be sure */ 2968c2ecf20Sopenharmony_ci if (data->block[0] > 32) 2978c2ecf20Sopenharmony_ci data->block[0] = 32; 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_ci dev_dbg(&adap->dev, 3008c2ecf20Sopenharmony_ci "block data read len=0x%x\n", data->block[0]); 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ci for (i = 0; i < 8 && len < data->block[0]; i++, len++) { 3038c2ecf20Sopenharmony_ci dev_dbg(&adap->dev, 3048c2ecf20Sopenharmony_ci "read i=%d len=%d\n", i, len); 3058c2ecf20Sopenharmony_ci data->block[len + 1] = sis630_read(SMB_BYTE + 3068c2ecf20Sopenharmony_ci i); 3078c2ecf20Sopenharmony_ci } 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci dev_dbg(&adap->dev, 3108c2ecf20Sopenharmony_ci "clear smbary_sts len=%d i=%d\n", len, i); 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_ci /* clear SMBARY_STS */ 3138c2ecf20Sopenharmony_ci sis630_write(SMB_STS, BYTE_DONE_STS); 3148c2ecf20Sopenharmony_ci } while (len < data->block[0]); 3158c2ecf20Sopenharmony_ci } 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci sis630_transaction_end(adap, oldclock); 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci return rc; 3208c2ecf20Sopenharmony_ci} 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci/* Return negative errno on error. */ 3238c2ecf20Sopenharmony_cistatic s32 sis630_access(struct i2c_adapter *adap, u16 addr, 3248c2ecf20Sopenharmony_ci unsigned short flags, char read_write, 3258c2ecf20Sopenharmony_ci u8 command, int size, union i2c_smbus_data *data) 3268c2ecf20Sopenharmony_ci{ 3278c2ecf20Sopenharmony_ci int status; 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_ci switch (size) { 3308c2ecf20Sopenharmony_ci case I2C_SMBUS_QUICK: 3318c2ecf20Sopenharmony_ci sis630_write(SMB_ADDR, 3328c2ecf20Sopenharmony_ci ((addr & 0x7f) << 1) | (read_write & 0x01)); 3338c2ecf20Sopenharmony_ci size = SIS630_QUICK; 3348c2ecf20Sopenharmony_ci break; 3358c2ecf20Sopenharmony_ci case I2C_SMBUS_BYTE: 3368c2ecf20Sopenharmony_ci sis630_write(SMB_ADDR, 3378c2ecf20Sopenharmony_ci ((addr & 0x7f) << 1) | (read_write & 0x01)); 3388c2ecf20Sopenharmony_ci if (read_write == I2C_SMBUS_WRITE) 3398c2ecf20Sopenharmony_ci sis630_write(SMB_CMD, command); 3408c2ecf20Sopenharmony_ci size = SIS630_BYTE; 3418c2ecf20Sopenharmony_ci break; 3428c2ecf20Sopenharmony_ci case I2C_SMBUS_BYTE_DATA: 3438c2ecf20Sopenharmony_ci sis630_write(SMB_ADDR, 3448c2ecf20Sopenharmony_ci ((addr & 0x7f) << 1) | (read_write & 0x01)); 3458c2ecf20Sopenharmony_ci sis630_write(SMB_CMD, command); 3468c2ecf20Sopenharmony_ci if (read_write == I2C_SMBUS_WRITE) 3478c2ecf20Sopenharmony_ci sis630_write(SMB_BYTE, data->byte); 3488c2ecf20Sopenharmony_ci size = SIS630_BYTE_DATA; 3498c2ecf20Sopenharmony_ci break; 3508c2ecf20Sopenharmony_ci case I2C_SMBUS_PROC_CALL: 3518c2ecf20Sopenharmony_ci case I2C_SMBUS_WORD_DATA: 3528c2ecf20Sopenharmony_ci sis630_write(SMB_ADDR, 3538c2ecf20Sopenharmony_ci ((addr & 0x7f) << 1) | (read_write & 0x01)); 3548c2ecf20Sopenharmony_ci sis630_write(SMB_CMD, command); 3558c2ecf20Sopenharmony_ci if (read_write == I2C_SMBUS_WRITE) { 3568c2ecf20Sopenharmony_ci sis630_write(SMB_BYTE, data->word & 0xff); 3578c2ecf20Sopenharmony_ci sis630_write(SMB_BYTE + 1, (data->word & 0xff00) >> 8); 3588c2ecf20Sopenharmony_ci } 3598c2ecf20Sopenharmony_ci size = (size == I2C_SMBUS_PROC_CALL ? 3608c2ecf20Sopenharmony_ci SIS630_PCALL : SIS630_WORD_DATA); 3618c2ecf20Sopenharmony_ci break; 3628c2ecf20Sopenharmony_ci case I2C_SMBUS_BLOCK_DATA: 3638c2ecf20Sopenharmony_ci sis630_write(SMB_ADDR, 3648c2ecf20Sopenharmony_ci ((addr & 0x7f) << 1) | (read_write & 0x01)); 3658c2ecf20Sopenharmony_ci sis630_write(SMB_CMD, command); 3668c2ecf20Sopenharmony_ci size = SIS630_BLOCK_DATA; 3678c2ecf20Sopenharmony_ci return sis630_block_data(adap, data, read_write); 3688c2ecf20Sopenharmony_ci default: 3698c2ecf20Sopenharmony_ci dev_warn(&adap->dev, "Unsupported transaction %d\n", size); 3708c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 3718c2ecf20Sopenharmony_ci } 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_ci status = sis630_transaction(adap, size); 3748c2ecf20Sopenharmony_ci if (status) 3758c2ecf20Sopenharmony_ci return status; 3768c2ecf20Sopenharmony_ci 3778c2ecf20Sopenharmony_ci if ((size != SIS630_PCALL) && 3788c2ecf20Sopenharmony_ci ((read_write == I2C_SMBUS_WRITE) || (size == SIS630_QUICK))) { 3798c2ecf20Sopenharmony_ci return 0; 3808c2ecf20Sopenharmony_ci } 3818c2ecf20Sopenharmony_ci 3828c2ecf20Sopenharmony_ci switch (size) { 3838c2ecf20Sopenharmony_ci case SIS630_BYTE: 3848c2ecf20Sopenharmony_ci case SIS630_BYTE_DATA: 3858c2ecf20Sopenharmony_ci data->byte = sis630_read(SMB_BYTE); 3868c2ecf20Sopenharmony_ci break; 3878c2ecf20Sopenharmony_ci case SIS630_PCALL: 3888c2ecf20Sopenharmony_ci case SIS630_WORD_DATA: 3898c2ecf20Sopenharmony_ci data->word = sis630_read(SMB_BYTE) + 3908c2ecf20Sopenharmony_ci (sis630_read(SMB_BYTE + 1) << 8); 3918c2ecf20Sopenharmony_ci break; 3928c2ecf20Sopenharmony_ci } 3938c2ecf20Sopenharmony_ci 3948c2ecf20Sopenharmony_ci return 0; 3958c2ecf20Sopenharmony_ci} 3968c2ecf20Sopenharmony_ci 3978c2ecf20Sopenharmony_cistatic u32 sis630_func(struct i2c_adapter *adapter) 3988c2ecf20Sopenharmony_ci{ 3998c2ecf20Sopenharmony_ci return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | 4008c2ecf20Sopenharmony_ci I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | 4018c2ecf20Sopenharmony_ci I2C_FUNC_SMBUS_PROC_CALL | I2C_FUNC_SMBUS_BLOCK_DATA; 4028c2ecf20Sopenharmony_ci} 4038c2ecf20Sopenharmony_ci 4048c2ecf20Sopenharmony_cistatic int sis630_setup(struct pci_dev *sis630_dev) 4058c2ecf20Sopenharmony_ci{ 4068c2ecf20Sopenharmony_ci unsigned char b; 4078c2ecf20Sopenharmony_ci struct pci_dev *dummy = NULL; 4088c2ecf20Sopenharmony_ci int retval, i; 4098c2ecf20Sopenharmony_ci /* acpi base address */ 4108c2ecf20Sopenharmony_ci unsigned short acpi_base; 4118c2ecf20Sopenharmony_ci 4128c2ecf20Sopenharmony_ci /* check for supported SiS devices */ 4138c2ecf20Sopenharmony_ci for (i = 0; supported[i] > 0; i++) { 4148c2ecf20Sopenharmony_ci dummy = pci_get_device(PCI_VENDOR_ID_SI, supported[i], dummy); 4158c2ecf20Sopenharmony_ci if (dummy) 4168c2ecf20Sopenharmony_ci break; /* found */ 4178c2ecf20Sopenharmony_ci } 4188c2ecf20Sopenharmony_ci 4198c2ecf20Sopenharmony_ci if (dummy) { 4208c2ecf20Sopenharmony_ci pci_dev_put(dummy); 4218c2ecf20Sopenharmony_ci } else if (force) { 4228c2ecf20Sopenharmony_ci dev_err(&sis630_dev->dev, 4238c2ecf20Sopenharmony_ci "WARNING: Can't detect SIS630 compatible device, but " 4248c2ecf20Sopenharmony_ci "loading because of force option enabled\n"); 4258c2ecf20Sopenharmony_ci } else { 4268c2ecf20Sopenharmony_ci return -ENODEV; 4278c2ecf20Sopenharmony_ci } 4288c2ecf20Sopenharmony_ci 4298c2ecf20Sopenharmony_ci /* 4308c2ecf20Sopenharmony_ci Enable ACPI first , so we can accsess reg 74-75 4318c2ecf20Sopenharmony_ci in acpi io space and read acpi base addr 4328c2ecf20Sopenharmony_ci */ 4338c2ecf20Sopenharmony_ci if (pci_read_config_byte(sis630_dev, SIS630_BIOS_CTL_REG, &b)) { 4348c2ecf20Sopenharmony_ci dev_err(&sis630_dev->dev, "Error: Can't read bios ctl reg\n"); 4358c2ecf20Sopenharmony_ci retval = -ENODEV; 4368c2ecf20Sopenharmony_ci goto exit; 4378c2ecf20Sopenharmony_ci } 4388c2ecf20Sopenharmony_ci /* if ACPI already enabled , do nothing */ 4398c2ecf20Sopenharmony_ci if (!(b & 0x80) && 4408c2ecf20Sopenharmony_ci pci_write_config_byte(sis630_dev, SIS630_BIOS_CTL_REG, b | 0x80)) { 4418c2ecf20Sopenharmony_ci dev_err(&sis630_dev->dev, "Error: Can't enable ACPI\n"); 4428c2ecf20Sopenharmony_ci retval = -ENODEV; 4438c2ecf20Sopenharmony_ci goto exit; 4448c2ecf20Sopenharmony_ci } 4458c2ecf20Sopenharmony_ci 4468c2ecf20Sopenharmony_ci /* Determine the ACPI base address */ 4478c2ecf20Sopenharmony_ci if (pci_read_config_word(sis630_dev, 4488c2ecf20Sopenharmony_ci SIS630_ACPI_BASE_REG, &acpi_base)) { 4498c2ecf20Sopenharmony_ci dev_err(&sis630_dev->dev, 4508c2ecf20Sopenharmony_ci "Error: Can't determine ACPI base address\n"); 4518c2ecf20Sopenharmony_ci retval = -ENODEV; 4528c2ecf20Sopenharmony_ci goto exit; 4538c2ecf20Sopenharmony_ci } 4548c2ecf20Sopenharmony_ci 4558c2ecf20Sopenharmony_ci dev_dbg(&sis630_dev->dev, "ACPI base at 0x%04hx\n", acpi_base); 4568c2ecf20Sopenharmony_ci 4578c2ecf20Sopenharmony_ci if (supported[i] == PCI_DEVICE_ID_SI_760) 4588c2ecf20Sopenharmony_ci smbus_base = acpi_base + 0xE0; 4598c2ecf20Sopenharmony_ci else 4608c2ecf20Sopenharmony_ci smbus_base = acpi_base + 0x80; 4618c2ecf20Sopenharmony_ci 4628c2ecf20Sopenharmony_ci dev_dbg(&sis630_dev->dev, "SMBus base at 0x%04hx\n", smbus_base); 4638c2ecf20Sopenharmony_ci 4648c2ecf20Sopenharmony_ci retval = acpi_check_region(smbus_base + SMB_STS, SIS630_SMB_IOREGION, 4658c2ecf20Sopenharmony_ci sis630_driver.name); 4668c2ecf20Sopenharmony_ci if (retval) 4678c2ecf20Sopenharmony_ci goto exit; 4688c2ecf20Sopenharmony_ci 4698c2ecf20Sopenharmony_ci /* Everything is happy, let's grab the memory and set things up. */ 4708c2ecf20Sopenharmony_ci if (!request_region(smbus_base + SMB_STS, SIS630_SMB_IOREGION, 4718c2ecf20Sopenharmony_ci sis630_driver.name)) { 4728c2ecf20Sopenharmony_ci dev_err(&sis630_dev->dev, 4738c2ecf20Sopenharmony_ci "I/O Region 0x%04x-0x%04x for SMBus already in use.\n", 4748c2ecf20Sopenharmony_ci smbus_base + SMB_STS, 4758c2ecf20Sopenharmony_ci smbus_base + SMB_STS + SIS630_SMB_IOREGION - 1); 4768c2ecf20Sopenharmony_ci retval = -EBUSY; 4778c2ecf20Sopenharmony_ci goto exit; 4788c2ecf20Sopenharmony_ci } 4798c2ecf20Sopenharmony_ci 4808c2ecf20Sopenharmony_ci retval = 0; 4818c2ecf20Sopenharmony_ci 4828c2ecf20Sopenharmony_ciexit: 4838c2ecf20Sopenharmony_ci if (retval) 4848c2ecf20Sopenharmony_ci smbus_base = 0; 4858c2ecf20Sopenharmony_ci return retval; 4868c2ecf20Sopenharmony_ci} 4878c2ecf20Sopenharmony_ci 4888c2ecf20Sopenharmony_ci 4898c2ecf20Sopenharmony_cistatic const struct i2c_algorithm smbus_algorithm = { 4908c2ecf20Sopenharmony_ci .smbus_xfer = sis630_access, 4918c2ecf20Sopenharmony_ci .functionality = sis630_func, 4928c2ecf20Sopenharmony_ci}; 4938c2ecf20Sopenharmony_ci 4948c2ecf20Sopenharmony_cistatic struct i2c_adapter sis630_adapter = { 4958c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 4968c2ecf20Sopenharmony_ci .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, 4978c2ecf20Sopenharmony_ci .algo = &smbus_algorithm, 4988c2ecf20Sopenharmony_ci .retries = 3 4998c2ecf20Sopenharmony_ci}; 5008c2ecf20Sopenharmony_ci 5018c2ecf20Sopenharmony_cistatic const struct pci_device_id sis630_ids[] = { 5028c2ecf20Sopenharmony_ci { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_503) }, 5038c2ecf20Sopenharmony_ci { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_LPC) }, 5048c2ecf20Sopenharmony_ci { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_964) }, 5058c2ecf20Sopenharmony_ci { 0, } 5068c2ecf20Sopenharmony_ci}; 5078c2ecf20Sopenharmony_ci 5088c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, sis630_ids); 5098c2ecf20Sopenharmony_ci 5108c2ecf20Sopenharmony_cistatic int sis630_probe(struct pci_dev *dev, const struct pci_device_id *id) 5118c2ecf20Sopenharmony_ci{ 5128c2ecf20Sopenharmony_ci if (sis630_setup(dev)) { 5138c2ecf20Sopenharmony_ci dev_err(&dev->dev, 5148c2ecf20Sopenharmony_ci "SIS630 compatible bus not detected, " 5158c2ecf20Sopenharmony_ci "module not inserted.\n"); 5168c2ecf20Sopenharmony_ci return -ENODEV; 5178c2ecf20Sopenharmony_ci } 5188c2ecf20Sopenharmony_ci 5198c2ecf20Sopenharmony_ci /* set up the sysfs linkage to our parent device */ 5208c2ecf20Sopenharmony_ci sis630_adapter.dev.parent = &dev->dev; 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_ci snprintf(sis630_adapter.name, sizeof(sis630_adapter.name), 5238c2ecf20Sopenharmony_ci "SMBus SIS630 adapter at %04x", smbus_base + SMB_STS); 5248c2ecf20Sopenharmony_ci 5258c2ecf20Sopenharmony_ci return i2c_add_adapter(&sis630_adapter); 5268c2ecf20Sopenharmony_ci} 5278c2ecf20Sopenharmony_ci 5288c2ecf20Sopenharmony_cistatic void sis630_remove(struct pci_dev *dev) 5298c2ecf20Sopenharmony_ci{ 5308c2ecf20Sopenharmony_ci if (smbus_base) { 5318c2ecf20Sopenharmony_ci i2c_del_adapter(&sis630_adapter); 5328c2ecf20Sopenharmony_ci release_region(smbus_base + SMB_STS, SIS630_SMB_IOREGION); 5338c2ecf20Sopenharmony_ci smbus_base = 0; 5348c2ecf20Sopenharmony_ci } 5358c2ecf20Sopenharmony_ci} 5368c2ecf20Sopenharmony_ci 5378c2ecf20Sopenharmony_ci 5388c2ecf20Sopenharmony_cistatic struct pci_driver sis630_driver = { 5398c2ecf20Sopenharmony_ci .name = "sis630_smbus", 5408c2ecf20Sopenharmony_ci .id_table = sis630_ids, 5418c2ecf20Sopenharmony_ci .probe = sis630_probe, 5428c2ecf20Sopenharmony_ci .remove = sis630_remove, 5438c2ecf20Sopenharmony_ci}; 5448c2ecf20Sopenharmony_ci 5458c2ecf20Sopenharmony_cimodule_pci_driver(sis630_driver); 5468c2ecf20Sopenharmony_ci 5478c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 5488c2ecf20Sopenharmony_ciMODULE_AUTHOR("Alexander Malysh <amalysh@web.de>"); 5498c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("SIS630 SMBus driver"); 550