/* * MEI-I2C driver * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mei_dev.h" #include "client.h" #include "i2c-mei_rw.h" #if (defined CONFIG_I2C_MUX_GPIO || defined CONFIG_I2C_MUX_GPIO_MODULE) && \ defined CONFIG_DMI #include #include #endif /* PCI Address Constants */ #define SMBBAR 0 #define SMBPCICTL 0x004 #define SMBPCISTS 0x006 #define SMBHSTCFG 0x040 #define TCOBASE 0x050 #define TCOCTL 0x054 /* Other settings */ #define MAX_RETRIES 400 /* I801 command constants */ #define I801_QUICK 0x00 #define I801_BYTE 0x04 #define I801_BYTE_DATA 0x08 #define I801_WORD_DATA 0x0C #define I801_PROC_CALL 0x10 /* unimplemented */ #define I801_BLOCK_DATA 0x14 #define I801_I2C_BLOCK_DATA 0x18 /* ICH5 and later */ #define HECI_PEC_FLAG 0x80 #define PCI_DEVICE_ID_INTEL_LPT_H 0x8C3A /* Lynx Point H */ struct mei_i2c_mux_config { char *gpio_chip; unsigned values[3]; int n_values; unsigned classes[3]; unsigned gpios[2]; /* Relative to gpio_chip->base */ int n_gpios; }; #define FEATURE_SMBUS_PEC (1 << 0) #define FEATURE_BLOCK_BUFFER (1 << 1) #define FEATURE_BLOCK_PROC (1 << 2) #define FEATURE_I2C_BLOCK_READ (1 << 3) #define FEATURE_IRQ (1 << 4) /* Not really a feature, but it's convenient to handle it as such */ #define FEATURE_IDF (1 << 15) #define FEATURE_TCO (1 << 16) static const char *mei_i2c_feature_names[] = { "SMBus PEC", "Block buffer", "Block process call", "I2C block read", "Interrupt", }; static unsigned int disable_features; module_param(disable_features, uint, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(disable_features, "Disable selected driver features:\n" "\t\t 0x01 disable SMBus PEC\n" "\t\t 0x02 disable the block buffer\n" "\t\t 0x08 disable the I2C block read functionality\n" "\t\t 0x10 don't use interrupts "); /*MEI SMB Sensor Bus (when MEI_FLAG_SMB_DEV_ADD_FMAT_EXT)*/ #define MEI_SMB_BUS_SMBUS 0x0 #define MEI_SMB_BUS_SMLINK0 0x1 #define MEI_SMB_BUS_SMLINK1 0x2 #define MEI_SMB_BUS_SMLINK2 0x3 #define MEI_SMB_BUS_SMLINK3 0x4 #define MEI_SMB_BUS_SMLINK4 0x5 struct mei_smb_priv{ struct i2c_adapter adapter; unsigned long smba; unsigned char original_hstcfg; struct pci_dev *pci_dev; unsigned int features; unsigned char sensorbus; /* isr processing */ wait_queue_head_t waitq; u8 status; /* Command state used by isr for byte-by-byte block transactions */ u8 cmd; bool is_read; int count; int len; u8 *data; }; struct mei_i2c_data_ext{ char Cmd; char Flag; char Sensor_Bus; char Psu_Addr; char Mux_Addr; char Mux_Channel; char Mux_Conf; char Reserved; char W_Length; char R_Length; char PMbus_data[21]; }; struct mei_msg{ struct mei_msg_hdr hdr; struct mei_i2c_data_ext data; }; #define DEBUG_MSG 0 static int mei_TxRx(u8 sensor_bus, u16 addr, u8 command, char read_write, int size, union i2c_smbus_data * data, int pec) { struct mei_msg_hdr mei_hdr; int rets; unsigned char * recv_buf; int retry = 0; int len = 0; int i = 0; struct mei_msg * msg; unsigned char blen; UINT32 timeout, dwTimeout; HECI_DEVICE sHeciDev; recv_buf = kmalloc(sizeof(unsigned char) * (32), GFP_KERNEL); msg = kmalloc(sizeof(struct mei_msg), GFP_KERNEL); dwTimeout = 2000000 / HECI_TIMEOUT_UNIT; sHeciDev.Bus = HECI_BUS; sHeciDev.Dev = HECI_DEV; sHeciDev.Fun = HECI_FUN; sHeciDev.Hidm = HECI_HIDM_MSI; sHeciDev.Mbar = HECI_MBAR_DEFAULT; HeciInit(&sHeciDev, &dwTimeout); msg->data.Cmd = 0x0A; if(read_write){ if(size == I2C_SMBUS_WORD_DATA){ msg->data.Flag = 0x56; msg->data.W_Length = 1; msg->data.R_Length = 2; } else if(size == I2C_SMBUS_BYTE_DATA || size == I2C_SMBUS_QUICK){ msg->data.Flag = 0x52; msg->data.W_Length = 1; msg->data.R_Length = 1; }else if(size == I2C_SMBUS_BYTE){ msg->data.Flag = 0x50; msg->data.W_Length = 0; msg->data.R_Length = 1; }else if(size == I2C_SMBUS_BLOCK_DATA){ msg->data.Flag = 0x5A; msg->data.W_Length = 1; } } else{ if(size == I2C_SMBUS_WORD_DATA){ msg->data.Flag = 0x58; msg->data.W_Length = 3; } else if(size == I2C_SMBUS_BYTE_DATA){ msg->data.Flag = 0x54; msg->data.W_Length = 2; } else if((size == I2C_SMBUS_BYTE) || (size == I2C_SMBUS_QUICK)){ msg->data.Flag = 0x50; msg->data.W_Length = 1; }else if(size == I2C_SMBUS_BLOCK_DATA){ msg->data.Flag = 0x5C; msg->data.W_Length = data->block[0]; } msg->data.R_Length = 0x0; if(data !=NULL){ if(size == I2C_SMBUS_WORD_DATA){ msg->data.PMbus_data[1] = data->word & 0xff; msg->data.PMbus_data[2] = (data->word & 0xff00) >> 8; }else if(size == I2C_SMBUS_BYTE_DATA){ msg->data.PMbus_data[1] = data->byte; }else if(size == I2C_SMBUS_BLOCK_DATA){ for (i = 0; i < msg->data.W_Length; i++) msg->data.PMbus_data[i+1] = data->block[i+1]; } }else{ msg->data.PMbus_data[1] = 0; } } if (pec == 1) msg->data.Flag |= HECI_PEC_FLAG; msg->data.Sensor_Bus = sensor_bus; msg->data.Psu_Addr =(char) addr << 1; msg->data.Mux_Addr = 0x0; msg->data.Mux_Channel = 0x0; msg->data.Mux_Conf = 0x0; msg->data.Reserved = 0x0; msg->data.PMbus_data[0] = command; msg->hdr.host_addr = 0;//mei_cl_host_addr(cl); msg->hdr.me_addr = 0x20; msg->hdr.reserved = 0; msg->hdr.msg_complete = 0; msg->hdr.internal = 0; //cb->internal; msg->hdr.length = 10 + msg->data.W_Length; msg->hdr.msg_complete = 1; #if (DEBUG_MSG) printk("Cmd : 0x%02x , Flag : 0x%02x , Sensor_Bus : 0x%02x , Psu_Addr : 0x%02x\n" , msg->data.Cmd, msg->data.Flag, msg->data.Sensor_Bus, msg->data.Psu_Addr); printk("Mux_Addr : 0x%02x , Mux_Channel : 0x%02x , Mux_Conf : 0x%02x , W_Length : 0x%02x\n" , msg->data.Mux_Addr, msg->data.Mux_Channel, msg->data.Mux_Conf, msg->data.W_Length); printk("R_Length : 0x%02x , PMbus_data[0] : 0x%02x , size : 0x%x\n" , msg->data.R_Length, msg->data.PMbus_data[0], size); if(!read_write){ if(size == I2C_SMBUS_BLOCK_DATA){ for (i = 0; i < msg->data.W_Length; i++){ printk("PMbus_data[%d] : 0x%02x , ", i, msg->data.PMbus_data[i]); } printk("\n"); }else{ printk("PMbus_data[1] : 0x%02x , PMbus_data[2] : 0x%02x\n", msg->data.PMbus_data[1], msg->data.PMbus_data[2]); } } #endif retry = 3; while(retry){ timeout = HECI_SEND_TIMEOUT / HECI_TIMEOUT_UNIT; rets = HeciMsgSend(&sHeciDev, &timeout, (HECI_MSG_HEADER *)msg); if (rets != 0){ printk("HeciMsgSend ret: %d\n",rets); retry --; continue; }else{ break; } } if(read_write) { if(size == I2C_SMBUS_WORD_DATA){ blen = 8; HeciMsgRecv(&sHeciDev, &timeout, (HECI_MSG_HEADER *)recv_buf, &blen); } else if(size == I2C_SMBUS_BYTE_DATA || size == I2C_SMBUS_QUICK || size == I2C_SMBUS_BYTE){ blen = 7; HeciMsgRecv(&sHeciDev, &timeout, (HECI_MSG_HEADER *)recv_buf, &blen); } #if (DEBUG_MSG) if(size == I2C_SMBUS_BLOCK_DATA){ printk("recv_len %d hdr: 0x%02x%02x%02x%02x\n", blen, recv_buf[3], recv_buf[2], recv_buf[1], recv_buf[0]); for (i = 0; i < blen ; i++){ printk("0x%02x , ", recv_buf[4 + i]); } printk("\n"); }else{ printk("recv_len %d recv: 0x%02x%02x%02x%02x\n0x%02x , 0x%02x , 0x%02x, 0x%02x \n", blen, recv_buf[3], recv_buf[2], recv_buf[1], recv_buf[0], recv_buf[4], recv_buf[5], recv_buf[6], recv_buf[7]); } #endif if(data !=NULL){ if(size == I2C_SMBUS_WORD_DATA){ data->word = ((recv_buf[7] << 8) & 0xff00) | (recv_buf[6] & 0xff); } else if(size == I2C_SMBUS_BYTE_DATA){ data->byte = recv_buf[6] & 0xff; } else if(size == I2C_SMBUS_BLOCK_DATA){ for (i = 0; i < blen; i++){ data->block[i] = recv_buf[6+i] & 0xff; } } } } else { blen = 6; HeciMsgRecv(&sHeciDev, &timeout, (HECI_MSG_HEADER *)recv_buf, &blen); #if (DEBUG_MSG) printk("recv: 0x%02x%02x%02x%02x , 0x%02x , 0x%02x \n", recv_buf[3], recv_buf[2], recv_buf[1], recv_buf[0], recv_buf[4], recv_buf[5]); #endif } rets = recv_buf[5]; kfree(recv_buf); kfree(msg); if(rets) return -1; else return 0; } /* Return negative errno on error. */ static s32 mei_i2c_access(struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data) { int hwpec; int block = 0; int ret = 0, xact = 0; int pec = 0; char byte = 0; struct mei_smb_priv *priv = i2c_get_adapdata(adap); if (flags & I2C_CLIENT_PEC) pec = 1; switch (size) { case I2C_SMBUS_QUICK: command = 0; read_write = 1; ret = mei_TxRx(priv->sensorbus, addr, command, read_write, size, NULL, pec); xact = I801_QUICK; break; case I2C_SMBUS_BYTE: if (read_write == I2C_SMBUS_READ) command = 0; ret = mei_TxRx(priv->sensorbus, addr, command, read_write, size, data, pec); xact = I801_BYTE; break; case I2C_SMBUS_BYTE_DATA: ret = mei_TxRx(priv->sensorbus, addr, command, read_write, size, data, pec); xact = I801_BYTE_DATA; break; case I2C_SMBUS_WORD_DATA: ret = mei_TxRx(priv->sensorbus, addr, command, read_write, size, data, pec); xact = I801_WORD_DATA; break; case I2C_SMBUS_BLOCK_DATA: ret = mei_TxRx(priv->sensorbus, addr, command, read_write, size, data, pec); break; case I2C_SMBUS_I2C_BLOCK_DATA: printk("I2C_SMBUS_I2C_BLOCK_DATA unsupported!!%d\n",size); break; default: dev_err(&priv->pci_dev->dev, "Unsupported transaction %d\n", size); return -EOPNOTSUPP; } if (ret) return ret; return 0; } static u32 mei_i2c_func(struct i2c_adapter *adapter) { struct mei_smb_priv *priv = i2c_get_adapdata(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 = mei_i2c_access, .functionality = mei_i2c_func, }; static const struct pci_device_id mei_i2c_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_LPT_H) }, { 0, } }; MODULE_DEVICE_TABLE(pci, mei_i2c_ids); /* richard + priv_table */ struct mei_smb_priv_table { struct mei_smb_priv *priv_tbl[MEI_SMB_BUS_SMLINK4]; int count; }; static int mei_i2c_probe(struct pci_dev *pdev, const struct pci_device_id *id) { unsigned char temp; int err, i; struct mei_smb_priv *priv_sml_0, *priv_sml_1, *priv_sml_2, *priv_sml_3, *priv_sml_4, *priv_smb; //richard + priv_table struct mei_smb_priv_table *priv_table; priv_table = kzalloc(sizeof(*priv_table), GFP_KERNEL); if(!priv_table) return -ENOMEM; priv_sml_0= kzalloc(sizeof(struct mei_smb_priv), GFP_KERNEL); if (!priv_sml_0) return -ENOMEM; i2c_set_adapdata(&priv_sml_0->adapter, priv_sml_0); priv_sml_0->adapter.owner = THIS_MODULE; priv_sml_0->adapter.algo = &smbus_algorithm; priv_sml_0->adapter.dev.parent = &pdev->dev; priv_sml_0->adapter.retries = 3; priv_sml_0->sensorbus = MEI_SMB_BUS_SMLINK0; priv_sml_0->pci_dev = pdev; priv_sml_1 = kzalloc(sizeof(*priv_sml_1), GFP_KERNEL); if (!priv_sml_1) return -ENOMEM; i2c_set_adapdata(&priv_sml_1->adapter, priv_sml_1); priv_sml_1->adapter.owner = THIS_MODULE; priv_sml_1->adapter.algo = &smbus_algorithm; priv_sml_1->adapter.dev.parent = &pdev->dev; priv_sml_1->adapter.retries = 3; priv_sml_1->sensorbus = MEI_SMB_BUS_SMLINK1; priv_sml_1->pci_dev = pdev; priv_sml_2 = kzalloc(sizeof(*priv_sml_2), GFP_KERNEL); if (!priv_sml_2) return -ENOMEM; i2c_set_adapdata(&priv_sml_2->adapter, priv_sml_2); priv_sml_2->adapter.owner = THIS_MODULE; priv_sml_2->adapter.algo = &smbus_algorithm; priv_sml_2->adapter.dev.parent = &pdev->dev; priv_sml_2->adapter.retries = 3; priv_sml_2->sensorbus = MEI_SMB_BUS_SMLINK2; priv_sml_2->pci_dev = pdev; priv_sml_3 = kzalloc(sizeof(*priv_sml_3), GFP_KERNEL); if (!priv_sml_3) return -ENOMEM; i2c_set_adapdata(&priv_sml_3->adapter, priv_sml_3); priv_sml_3->adapter.owner = THIS_MODULE; priv_sml_3->adapter.algo = &smbus_algorithm; priv_sml_3->adapter.dev.parent = &pdev->dev; priv_sml_3->adapter.retries = 3; priv_sml_3->sensorbus = MEI_SMB_BUS_SMLINK3; priv_sml_3->pci_dev = pdev; priv_sml_4 = kzalloc(sizeof(*priv_sml_4), GFP_KERNEL); if (!priv_sml_4) return -ENOMEM; i2c_set_adapdata(&priv_sml_4->adapter, priv_sml_4); priv_sml_4->adapter.owner = THIS_MODULE; priv_sml_4->adapter.algo = &smbus_algorithm; priv_sml_4->adapter.dev.parent = &pdev->dev; priv_sml_4->adapter.retries = 3; priv_sml_4->sensorbus = MEI_SMB_BUS_SMLINK4; priv_sml_4->pci_dev = pdev; printk("mei_i2c_probe 0x%x 0x%x\n", pdev->device, pdev->dev.id); snprintf(priv_sml_0->adapter.name, sizeof(priv_sml_0->adapter.name), "ME-SMLINK0"); err = i2c_add_adapter(&priv_sml_0->adapter); printk("i2c nr : %d \n", priv_sml_0->adapter.nr); if (err) { dev_err(&pdev->dev, "Failed to add SMBus adapter ME-SMLINK0\n"); return err; } snprintf(priv_sml_1->adapter.name, sizeof(priv_sml_1->adapter.name), "ME-SMLINK1"); err = i2c_add_adapter(&priv_sml_1->adapter); if (err) { dev_err(&pdev->dev, "Failed to add SMBus adapter ME-SMLINK1\n"); return err; } snprintf(priv_sml_2->adapter.name, sizeof(priv_sml_2->adapter.name), "ME-SMLINK2"); err = i2c_add_adapter(&priv_sml_2->adapter); if (err) { dev_err(&pdev->dev, "Failed to add SMBus adapter ME-SMLINK2\n"); return err; } snprintf(priv_sml_3->adapter.name, sizeof(priv_sml_3->adapter.name), "ME-SMLINK3"); err = i2c_add_adapter(&priv_sml_3->adapter); if (err) { dev_err(&pdev->dev, "Failed to add SMBus adapter ME-SMLINK3\n"); return err; } snprintf(priv_sml_4->adapter.name, sizeof(priv_sml_4->adapter.name), "ME-SMLINK4"); err = i2c_add_adapter(&priv_sml_4->adapter); if (err) { dev_err(&pdev->dev, "Failed to add SMBus adapter ME-SMLINK4\n"); return err; } priv_table->count = 0; priv_table->priv_tbl[priv_table->count++] = priv_sml_0; priv_table->priv_tbl[priv_table->count++] = priv_sml_1; priv_table->priv_tbl[priv_table->count++] = priv_sml_2; priv_table->priv_tbl[priv_table->count++] = priv_sml_3; priv_table->priv_tbl[priv_table->count++] = priv_sml_4; pci_set_drvdata(pdev, priv_table); return 0; } static void mei_i2c_remove(struct pci_dev *dev) { struct mei_smb_priv *priv = pci_get_drvdata(dev); // richard + priv_table struct mei_smb_priv_table *priv_table = pci_get_drvdata(dev); int i; for(i=0; icount; i++) { i2c_del_adapter(&priv_table->priv_tbl[i]->adapter); } pci_write_config_byte(dev, SMBHSTCFG, priv->original_hstcfg); /* * do not call pci_disable_device(dev) since it can cause hard hangs on * some systems during power-off (eg. Fujitsu-Siemens Lifebook E8010) */ } #define mei_i2c_suspend NULL #define mei_i2c_resume NULL static struct pci_driver mei_i2c_driver = { .name = "mei_i2c", .id_table = mei_i2c_ids, .probe = mei_i2c_probe, .remove = mei_i2c_remove, .suspend = mei_i2c_suspend, .resume = mei_i2c_resume, }; static int __init mei_i2c_init(void) { int ret = 16; u32 status = 0; struct pci_dev *pdev = NULL; struct mei_device *dev; struct pci_driver *pci_drv; return pci_register_driver(&mei_i2c_driver); } static void __exit mei_i2c_exit(void) { pci_unregister_driver(&mei_i2c_driver); } MODULE_AUTHOR("Delta Networks, Inc."); MODULE_DESCRIPTION("MEI SMBus driver"); MODULE_LICENSE("GPL"); module_init(mei_i2c_init); module_exit(mei_i2c_exit);