18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * i2c-amd756-s4882.c - i2c-amd756 extras for the Tyan S4882 motherboard 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2004, 2008 Jean Delvare <jdelvare@suse.de> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci/* 98c2ecf20Sopenharmony_ci * We select the channels by sending commands to the Philips 108c2ecf20Sopenharmony_ci * PCA9556 chip at I2C address 0x18. The main adapter is used for 118c2ecf20Sopenharmony_ci * the non-multiplexed part of the bus, and 4 virtual adapters 128c2ecf20Sopenharmony_ci * are defined for the multiplexed addresses: 0x50-0x53 (memory 138c2ecf20Sopenharmony_ci * module EEPROM) located on channels 1-4, and 0x4c (LM63) 148c2ecf20Sopenharmony_ci * located on multiplexed channels 0 and 5-7. We define one 158c2ecf20Sopenharmony_ci * virtual adapter per CPU, which corresponds to two multiplexed 168c2ecf20Sopenharmony_ci * channels: 178c2ecf20Sopenharmony_ci * CPU0: virtual adapter 1, channels 1 and 0 188c2ecf20Sopenharmony_ci * CPU1: virtual adapter 2, channels 2 and 5 198c2ecf20Sopenharmony_ci * CPU2: virtual adapter 3, channels 3 and 6 208c2ecf20Sopenharmony_ci * CPU3: virtual adapter 4, channels 4 and 7 218c2ecf20Sopenharmony_ci */ 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#include <linux/module.h> 248c2ecf20Sopenharmony_ci#include <linux/kernel.h> 258c2ecf20Sopenharmony_ci#include <linux/slab.h> 268c2ecf20Sopenharmony_ci#include <linux/init.h> 278c2ecf20Sopenharmony_ci#include <linux/i2c.h> 288c2ecf20Sopenharmony_ci#include <linux/mutex.h> 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ciextern struct i2c_adapter amd756_smbus; 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistatic struct i2c_adapter *s4882_adapter; 338c2ecf20Sopenharmony_cistatic struct i2c_algorithm *s4882_algo; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci/* Wrapper access functions for multiplexed SMBus */ 368c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(amd756_lock); 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cistatic s32 amd756_access_virt0(struct i2c_adapter * adap, u16 addr, 398c2ecf20Sopenharmony_ci unsigned short flags, char read_write, 408c2ecf20Sopenharmony_ci u8 command, int size, 418c2ecf20Sopenharmony_ci union i2c_smbus_data * data) 428c2ecf20Sopenharmony_ci{ 438c2ecf20Sopenharmony_ci int error; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci /* We exclude the multiplexed addresses */ 468c2ecf20Sopenharmony_ci if (addr == 0x4c || (addr & 0xfc) == 0x50 || (addr & 0xfc) == 0x30 478c2ecf20Sopenharmony_ci || addr == 0x18) 488c2ecf20Sopenharmony_ci return -ENXIO; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci mutex_lock(&amd756_lock); 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci error = amd756_smbus.algo->smbus_xfer(adap, addr, flags, read_write, 538c2ecf20Sopenharmony_ci command, size, data); 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci mutex_unlock(&amd756_lock); 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci return error; 588c2ecf20Sopenharmony_ci} 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci/* We remember the last used channels combination so as to only switch 618c2ecf20Sopenharmony_ci channels when it is really needed. This greatly reduces the SMBus 628c2ecf20Sopenharmony_ci overhead, but also assumes that nobody will be writing to the PCA9556 638c2ecf20Sopenharmony_ci in our back. */ 648c2ecf20Sopenharmony_cistatic u8 last_channels; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_cistatic inline s32 amd756_access_channel(struct i2c_adapter * adap, u16 addr, 678c2ecf20Sopenharmony_ci unsigned short flags, char read_write, 688c2ecf20Sopenharmony_ci u8 command, int size, 698c2ecf20Sopenharmony_ci union i2c_smbus_data * data, 708c2ecf20Sopenharmony_ci u8 channels) 718c2ecf20Sopenharmony_ci{ 728c2ecf20Sopenharmony_ci int error; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci /* We exclude the non-multiplexed addresses */ 758c2ecf20Sopenharmony_ci if (addr != 0x4c && (addr & 0xfc) != 0x50 && (addr & 0xfc) != 0x30) 768c2ecf20Sopenharmony_ci return -ENXIO; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci mutex_lock(&amd756_lock); 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci if (last_channels != channels) { 818c2ecf20Sopenharmony_ci union i2c_smbus_data mplxdata; 828c2ecf20Sopenharmony_ci mplxdata.byte = channels; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci error = amd756_smbus.algo->smbus_xfer(adap, 0x18, 0, 858c2ecf20Sopenharmony_ci I2C_SMBUS_WRITE, 0x01, 868c2ecf20Sopenharmony_ci I2C_SMBUS_BYTE_DATA, 878c2ecf20Sopenharmony_ci &mplxdata); 888c2ecf20Sopenharmony_ci if (error) 898c2ecf20Sopenharmony_ci goto UNLOCK; 908c2ecf20Sopenharmony_ci last_channels = channels; 918c2ecf20Sopenharmony_ci } 928c2ecf20Sopenharmony_ci error = amd756_smbus.algo->smbus_xfer(adap, addr, flags, read_write, 938c2ecf20Sopenharmony_ci command, size, data); 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ciUNLOCK: 968c2ecf20Sopenharmony_ci mutex_unlock(&amd756_lock); 978c2ecf20Sopenharmony_ci return error; 988c2ecf20Sopenharmony_ci} 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_cistatic s32 amd756_access_virt1(struct i2c_adapter * adap, u16 addr, 1018c2ecf20Sopenharmony_ci unsigned short flags, char read_write, 1028c2ecf20Sopenharmony_ci u8 command, int size, 1038c2ecf20Sopenharmony_ci union i2c_smbus_data * data) 1048c2ecf20Sopenharmony_ci{ 1058c2ecf20Sopenharmony_ci /* CPU0: channels 1 and 0 enabled */ 1068c2ecf20Sopenharmony_ci return amd756_access_channel(adap, addr, flags, read_write, command, 1078c2ecf20Sopenharmony_ci size, data, 0x03); 1088c2ecf20Sopenharmony_ci} 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic s32 amd756_access_virt2(struct i2c_adapter * adap, u16 addr, 1118c2ecf20Sopenharmony_ci unsigned short flags, char read_write, 1128c2ecf20Sopenharmony_ci u8 command, int size, 1138c2ecf20Sopenharmony_ci union i2c_smbus_data * data) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci /* CPU1: channels 2 and 5 enabled */ 1168c2ecf20Sopenharmony_ci return amd756_access_channel(adap, addr, flags, read_write, command, 1178c2ecf20Sopenharmony_ci size, data, 0x24); 1188c2ecf20Sopenharmony_ci} 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_cistatic s32 amd756_access_virt3(struct i2c_adapter * adap, u16 addr, 1218c2ecf20Sopenharmony_ci unsigned short flags, char read_write, 1228c2ecf20Sopenharmony_ci u8 command, int size, 1238c2ecf20Sopenharmony_ci union i2c_smbus_data * data) 1248c2ecf20Sopenharmony_ci{ 1258c2ecf20Sopenharmony_ci /* CPU2: channels 3 and 6 enabled */ 1268c2ecf20Sopenharmony_ci return amd756_access_channel(adap, addr, flags, read_write, command, 1278c2ecf20Sopenharmony_ci size, data, 0x48); 1288c2ecf20Sopenharmony_ci} 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_cistatic s32 amd756_access_virt4(struct i2c_adapter * adap, u16 addr, 1318c2ecf20Sopenharmony_ci unsigned short flags, char read_write, 1328c2ecf20Sopenharmony_ci u8 command, int size, 1338c2ecf20Sopenharmony_ci union i2c_smbus_data * data) 1348c2ecf20Sopenharmony_ci{ 1358c2ecf20Sopenharmony_ci /* CPU3: channels 4 and 7 enabled */ 1368c2ecf20Sopenharmony_ci return amd756_access_channel(adap, addr, flags, read_write, command, 1378c2ecf20Sopenharmony_ci size, data, 0x90); 1388c2ecf20Sopenharmony_ci} 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_cistatic int __init amd756_s4882_init(void) 1418c2ecf20Sopenharmony_ci{ 1428c2ecf20Sopenharmony_ci int i, error; 1438c2ecf20Sopenharmony_ci union i2c_smbus_data ioconfig; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci if (!amd756_smbus.dev.parent) 1468c2ecf20Sopenharmony_ci return -ENODEV; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci /* Configure the PCA9556 multiplexer */ 1498c2ecf20Sopenharmony_ci ioconfig.byte = 0x00; /* All I/O to output mode */ 1508c2ecf20Sopenharmony_ci error = i2c_smbus_xfer(&amd756_smbus, 0x18, 0, I2C_SMBUS_WRITE, 0x03, 1518c2ecf20Sopenharmony_ci I2C_SMBUS_BYTE_DATA, &ioconfig); 1528c2ecf20Sopenharmony_ci if (error) { 1538c2ecf20Sopenharmony_ci dev_err(&amd756_smbus.dev, "PCA9556 configuration failed\n"); 1548c2ecf20Sopenharmony_ci error = -EIO; 1558c2ecf20Sopenharmony_ci goto ERROR0; 1568c2ecf20Sopenharmony_ci } 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci /* Unregister physical bus */ 1598c2ecf20Sopenharmony_ci i2c_del_adapter(&amd756_smbus); 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci printk(KERN_INFO "Enabling SMBus multiplexing for Tyan S4882\n"); 1628c2ecf20Sopenharmony_ci /* Define the 5 virtual adapters and algorithms structures */ 1638c2ecf20Sopenharmony_ci if (!(s4882_adapter = kcalloc(5, sizeof(struct i2c_adapter), 1648c2ecf20Sopenharmony_ci GFP_KERNEL))) { 1658c2ecf20Sopenharmony_ci error = -ENOMEM; 1668c2ecf20Sopenharmony_ci goto ERROR1; 1678c2ecf20Sopenharmony_ci } 1688c2ecf20Sopenharmony_ci if (!(s4882_algo = kcalloc(5, sizeof(struct i2c_algorithm), 1698c2ecf20Sopenharmony_ci GFP_KERNEL))) { 1708c2ecf20Sopenharmony_ci error = -ENOMEM; 1718c2ecf20Sopenharmony_ci goto ERROR2; 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci /* Fill in the new structures */ 1758c2ecf20Sopenharmony_ci s4882_algo[0] = *(amd756_smbus.algo); 1768c2ecf20Sopenharmony_ci s4882_algo[0].smbus_xfer = amd756_access_virt0; 1778c2ecf20Sopenharmony_ci s4882_adapter[0] = amd756_smbus; 1788c2ecf20Sopenharmony_ci s4882_adapter[0].algo = s4882_algo; 1798c2ecf20Sopenharmony_ci s4882_adapter[0].dev.parent = amd756_smbus.dev.parent; 1808c2ecf20Sopenharmony_ci for (i = 1; i < 5; i++) { 1818c2ecf20Sopenharmony_ci s4882_algo[i] = *(amd756_smbus.algo); 1828c2ecf20Sopenharmony_ci s4882_adapter[i] = amd756_smbus; 1838c2ecf20Sopenharmony_ci snprintf(s4882_adapter[i].name, sizeof(s4882_adapter[i].name), 1848c2ecf20Sopenharmony_ci "SMBus 8111 adapter (CPU%d)", i-1); 1858c2ecf20Sopenharmony_ci s4882_adapter[i].algo = s4882_algo+i; 1868c2ecf20Sopenharmony_ci s4882_adapter[i].dev.parent = amd756_smbus.dev.parent; 1878c2ecf20Sopenharmony_ci } 1888c2ecf20Sopenharmony_ci s4882_algo[1].smbus_xfer = amd756_access_virt1; 1898c2ecf20Sopenharmony_ci s4882_algo[2].smbus_xfer = amd756_access_virt2; 1908c2ecf20Sopenharmony_ci s4882_algo[3].smbus_xfer = amd756_access_virt3; 1918c2ecf20Sopenharmony_ci s4882_algo[4].smbus_xfer = amd756_access_virt4; 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci /* Register virtual adapters */ 1948c2ecf20Sopenharmony_ci for (i = 0; i < 5; i++) { 1958c2ecf20Sopenharmony_ci error = i2c_add_adapter(s4882_adapter+i); 1968c2ecf20Sopenharmony_ci if (error) { 1978c2ecf20Sopenharmony_ci printk(KERN_ERR "i2c-amd756-s4882: " 1988c2ecf20Sopenharmony_ci "Virtual adapter %d registration " 1998c2ecf20Sopenharmony_ci "failed, module not inserted\n", i); 2008c2ecf20Sopenharmony_ci for (i--; i >= 0; i--) 2018c2ecf20Sopenharmony_ci i2c_del_adapter(s4882_adapter+i); 2028c2ecf20Sopenharmony_ci goto ERROR3; 2038c2ecf20Sopenharmony_ci } 2048c2ecf20Sopenharmony_ci } 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci return 0; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ciERROR3: 2098c2ecf20Sopenharmony_ci kfree(s4882_algo); 2108c2ecf20Sopenharmony_ci s4882_algo = NULL; 2118c2ecf20Sopenharmony_ciERROR2: 2128c2ecf20Sopenharmony_ci kfree(s4882_adapter); 2138c2ecf20Sopenharmony_ci s4882_adapter = NULL; 2148c2ecf20Sopenharmony_ciERROR1: 2158c2ecf20Sopenharmony_ci /* Restore physical bus */ 2168c2ecf20Sopenharmony_ci i2c_add_adapter(&amd756_smbus); 2178c2ecf20Sopenharmony_ciERROR0: 2188c2ecf20Sopenharmony_ci return error; 2198c2ecf20Sopenharmony_ci} 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_cistatic void __exit amd756_s4882_exit(void) 2228c2ecf20Sopenharmony_ci{ 2238c2ecf20Sopenharmony_ci if (s4882_adapter) { 2248c2ecf20Sopenharmony_ci int i; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci for (i = 0; i < 5; i++) 2278c2ecf20Sopenharmony_ci i2c_del_adapter(s4882_adapter+i); 2288c2ecf20Sopenharmony_ci kfree(s4882_adapter); 2298c2ecf20Sopenharmony_ci s4882_adapter = NULL; 2308c2ecf20Sopenharmony_ci } 2318c2ecf20Sopenharmony_ci kfree(s4882_algo); 2328c2ecf20Sopenharmony_ci s4882_algo = NULL; 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci /* Restore physical bus */ 2358c2ecf20Sopenharmony_ci if (i2c_add_adapter(&amd756_smbus)) 2368c2ecf20Sopenharmony_ci printk(KERN_ERR "i2c-amd756-s4882: " 2378c2ecf20Sopenharmony_ci "Physical bus restoration failed\n"); 2388c2ecf20Sopenharmony_ci} 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jean Delvare <jdelvare@suse.de>"); 2418c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("S4882 SMBus multiplexing"); 2428c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_cimodule_init(amd756_s4882_init); 2458c2ecf20Sopenharmony_cimodule_exit(amd756_s4882_exit); 246