162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci/* 462306a36Sopenharmony_ci * IPMB driver to receive a request and send a response 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Copyright (C) 2019 Mellanox Techologies, Ltd. 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * This was inspired by Brendan Higgins' ipmi-bmc-bt-i2c driver. 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/acpi.h> 1262306a36Sopenharmony_ci#include <linux/errno.h> 1362306a36Sopenharmony_ci#include <linux/i2c.h> 1462306a36Sopenharmony_ci#include <linux/miscdevice.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/mutex.h> 1762306a36Sopenharmony_ci#include <linux/poll.h> 1862306a36Sopenharmony_ci#include <linux/slab.h> 1962306a36Sopenharmony_ci#include <linux/spinlock.h> 2062306a36Sopenharmony_ci#include <linux/wait.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define MAX_MSG_LEN 240 2362306a36Sopenharmony_ci#define IPMB_REQUEST_LEN_MIN 7 2462306a36Sopenharmony_ci#define NETFN_RSP_BIT_MASK 0x4 2562306a36Sopenharmony_ci#define REQUEST_QUEUE_MAX_LEN 256 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#define IPMB_MSG_LEN_IDX 0 2862306a36Sopenharmony_ci#define RQ_SA_8BIT_IDX 1 2962306a36Sopenharmony_ci#define NETFN_LUN_IDX 2 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#define GET_7BIT_ADDR(addr_8bit) (addr_8bit >> 1) 3262306a36Sopenharmony_ci#define GET_8BIT_ADDR(addr_7bit) ((addr_7bit << 1) & 0xff) 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#define IPMB_MSG_PAYLOAD_LEN_MAX (MAX_MSG_LEN - IPMB_REQUEST_LEN_MIN - 1) 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci#define SMBUS_MSG_HEADER_LENGTH 2 3762306a36Sopenharmony_ci#define SMBUS_MSG_IDX_OFFSET (SMBUS_MSG_HEADER_LENGTH + 1) 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistruct ipmb_msg { 4062306a36Sopenharmony_ci u8 len; 4162306a36Sopenharmony_ci u8 rs_sa; 4262306a36Sopenharmony_ci u8 netfn_rs_lun; 4362306a36Sopenharmony_ci u8 checksum1; 4462306a36Sopenharmony_ci u8 rq_sa; 4562306a36Sopenharmony_ci u8 rq_seq_rq_lun; 4662306a36Sopenharmony_ci u8 cmd; 4762306a36Sopenharmony_ci u8 payload[IPMB_MSG_PAYLOAD_LEN_MAX]; 4862306a36Sopenharmony_ci /* checksum2 is included in payload */ 4962306a36Sopenharmony_ci} __packed; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistruct ipmb_request_elem { 5262306a36Sopenharmony_ci struct list_head list; 5362306a36Sopenharmony_ci struct ipmb_msg request; 5462306a36Sopenharmony_ci}; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistruct ipmb_dev { 5762306a36Sopenharmony_ci struct i2c_client *client; 5862306a36Sopenharmony_ci struct miscdevice miscdev; 5962306a36Sopenharmony_ci struct ipmb_msg request; 6062306a36Sopenharmony_ci struct list_head request_queue; 6162306a36Sopenharmony_ci atomic_t request_queue_len; 6262306a36Sopenharmony_ci size_t msg_idx; 6362306a36Sopenharmony_ci spinlock_t lock; 6462306a36Sopenharmony_ci wait_queue_head_t wait_queue; 6562306a36Sopenharmony_ci struct mutex file_mutex; 6662306a36Sopenharmony_ci bool is_i2c_protocol; 6762306a36Sopenharmony_ci}; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cistatic inline struct ipmb_dev *to_ipmb_dev(struct file *file) 7062306a36Sopenharmony_ci{ 7162306a36Sopenharmony_ci return container_of(file->private_data, struct ipmb_dev, miscdev); 7262306a36Sopenharmony_ci} 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic ssize_t ipmb_read(struct file *file, char __user *buf, size_t count, 7562306a36Sopenharmony_ci loff_t *ppos) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci struct ipmb_dev *ipmb_dev = to_ipmb_dev(file); 7862306a36Sopenharmony_ci struct ipmb_request_elem *queue_elem; 7962306a36Sopenharmony_ci struct ipmb_msg msg; 8062306a36Sopenharmony_ci ssize_t ret = 0; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci memset(&msg, 0, sizeof(msg)); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci spin_lock_irq(&ipmb_dev->lock); 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci while (list_empty(&ipmb_dev->request_queue)) { 8762306a36Sopenharmony_ci spin_unlock_irq(&ipmb_dev->lock); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci if (file->f_flags & O_NONBLOCK) 9062306a36Sopenharmony_ci return -EAGAIN; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci ret = wait_event_interruptible(ipmb_dev->wait_queue, 9362306a36Sopenharmony_ci !list_empty(&ipmb_dev->request_queue)); 9462306a36Sopenharmony_ci if (ret) 9562306a36Sopenharmony_ci return ret; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci spin_lock_irq(&ipmb_dev->lock); 9862306a36Sopenharmony_ci } 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci queue_elem = list_first_entry(&ipmb_dev->request_queue, 10162306a36Sopenharmony_ci struct ipmb_request_elem, list); 10262306a36Sopenharmony_ci memcpy(&msg, &queue_elem->request, sizeof(msg)); 10362306a36Sopenharmony_ci list_del(&queue_elem->list); 10462306a36Sopenharmony_ci kfree(queue_elem); 10562306a36Sopenharmony_ci atomic_dec(&ipmb_dev->request_queue_len); 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci spin_unlock_irq(&ipmb_dev->lock); 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci count = min_t(size_t, count, msg.len + 1); 11062306a36Sopenharmony_ci if (copy_to_user(buf, &msg, count)) 11162306a36Sopenharmony_ci ret = -EFAULT; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci return ret < 0 ? ret : count; 11462306a36Sopenharmony_ci} 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_cistatic int ipmb_i2c_write(struct i2c_client *client, u8 *msg, u8 addr) 11762306a36Sopenharmony_ci{ 11862306a36Sopenharmony_ci struct i2c_msg i2c_msg; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci /* 12162306a36Sopenharmony_ci * subtract 1 byte (rq_sa) from the length of the msg passed to 12262306a36Sopenharmony_ci * raw i2c_transfer 12362306a36Sopenharmony_ci */ 12462306a36Sopenharmony_ci i2c_msg.len = msg[IPMB_MSG_LEN_IDX] - 1; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci /* Assign message to buffer except first 2 bytes (length and address) */ 12762306a36Sopenharmony_ci i2c_msg.buf = msg + 2; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci i2c_msg.addr = addr; 13062306a36Sopenharmony_ci i2c_msg.flags = client->flags & I2C_CLIENT_PEC; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci return i2c_transfer(client->adapter, &i2c_msg, 1); 13362306a36Sopenharmony_ci} 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_cistatic ssize_t ipmb_write(struct file *file, const char __user *buf, 13662306a36Sopenharmony_ci size_t count, loff_t *ppos) 13762306a36Sopenharmony_ci{ 13862306a36Sopenharmony_ci struct ipmb_dev *ipmb_dev = to_ipmb_dev(file); 13962306a36Sopenharmony_ci u8 rq_sa, netf_rq_lun, msg_len; 14062306a36Sopenharmony_ci struct i2c_client *temp_client; 14162306a36Sopenharmony_ci u8 msg[MAX_MSG_LEN]; 14262306a36Sopenharmony_ci ssize_t ret; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci if (count > sizeof(msg)) 14562306a36Sopenharmony_ci return -EINVAL; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci if (copy_from_user(&msg, buf, count)) 14862306a36Sopenharmony_ci return -EFAULT; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci if (count < msg[0]) 15162306a36Sopenharmony_ci return -EINVAL; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci rq_sa = GET_7BIT_ADDR(msg[RQ_SA_8BIT_IDX]); 15462306a36Sopenharmony_ci netf_rq_lun = msg[NETFN_LUN_IDX]; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci /* Check i2c block transfer vs smbus */ 15762306a36Sopenharmony_ci if (ipmb_dev->is_i2c_protocol) { 15862306a36Sopenharmony_ci ret = ipmb_i2c_write(ipmb_dev->client, msg, rq_sa); 15962306a36Sopenharmony_ci return (ret == 1) ? count : ret; 16062306a36Sopenharmony_ci } 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci /* 16362306a36Sopenharmony_ci * subtract rq_sa and netf_rq_lun from the length of the msg. Fill the 16462306a36Sopenharmony_ci * temporary client. Note that its use is an exception for IPMI. 16562306a36Sopenharmony_ci */ 16662306a36Sopenharmony_ci msg_len = msg[IPMB_MSG_LEN_IDX] - SMBUS_MSG_HEADER_LENGTH; 16762306a36Sopenharmony_ci temp_client = kmemdup(ipmb_dev->client, sizeof(*temp_client), GFP_KERNEL); 16862306a36Sopenharmony_ci if (!temp_client) 16962306a36Sopenharmony_ci return -ENOMEM; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci temp_client->addr = rq_sa; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci ret = i2c_smbus_write_block_data(temp_client, netf_rq_lun, msg_len, 17462306a36Sopenharmony_ci msg + SMBUS_MSG_IDX_OFFSET); 17562306a36Sopenharmony_ci kfree(temp_client); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci return ret < 0 ? ret : count; 17862306a36Sopenharmony_ci} 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_cistatic __poll_t ipmb_poll(struct file *file, poll_table *wait) 18162306a36Sopenharmony_ci{ 18262306a36Sopenharmony_ci struct ipmb_dev *ipmb_dev = to_ipmb_dev(file); 18362306a36Sopenharmony_ci __poll_t mask = EPOLLOUT; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci mutex_lock(&ipmb_dev->file_mutex); 18662306a36Sopenharmony_ci poll_wait(file, &ipmb_dev->wait_queue, wait); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci if (atomic_read(&ipmb_dev->request_queue_len)) 18962306a36Sopenharmony_ci mask |= EPOLLIN; 19062306a36Sopenharmony_ci mutex_unlock(&ipmb_dev->file_mutex); 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci return mask; 19362306a36Sopenharmony_ci} 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_cistatic const struct file_operations ipmb_fops = { 19662306a36Sopenharmony_ci .owner = THIS_MODULE, 19762306a36Sopenharmony_ci .read = ipmb_read, 19862306a36Sopenharmony_ci .write = ipmb_write, 19962306a36Sopenharmony_ci .poll = ipmb_poll, 20062306a36Sopenharmony_ci}; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci/* Called with ipmb_dev->lock held. */ 20362306a36Sopenharmony_cistatic void ipmb_handle_request(struct ipmb_dev *ipmb_dev) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci struct ipmb_request_elem *queue_elem; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci if (atomic_read(&ipmb_dev->request_queue_len) >= 20862306a36Sopenharmony_ci REQUEST_QUEUE_MAX_LEN) 20962306a36Sopenharmony_ci return; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci queue_elem = kmalloc(sizeof(*queue_elem), GFP_ATOMIC); 21262306a36Sopenharmony_ci if (!queue_elem) 21362306a36Sopenharmony_ci return; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci memcpy(&queue_elem->request, &ipmb_dev->request, 21662306a36Sopenharmony_ci sizeof(struct ipmb_msg)); 21762306a36Sopenharmony_ci list_add(&queue_elem->list, &ipmb_dev->request_queue); 21862306a36Sopenharmony_ci atomic_inc(&ipmb_dev->request_queue_len); 21962306a36Sopenharmony_ci wake_up_all(&ipmb_dev->wait_queue); 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_cistatic u8 ipmb_verify_checksum1(struct ipmb_dev *ipmb_dev, u8 rs_sa) 22362306a36Sopenharmony_ci{ 22462306a36Sopenharmony_ci /* The 8 lsb of the sum is 0 when the checksum is valid */ 22562306a36Sopenharmony_ci return (rs_sa + ipmb_dev->request.netfn_rs_lun + 22662306a36Sopenharmony_ci ipmb_dev->request.checksum1); 22762306a36Sopenharmony_ci} 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci/* 23062306a36Sopenharmony_ci * Verify if message has proper ipmb header with minimum length 23162306a36Sopenharmony_ci * and correct checksum byte. 23262306a36Sopenharmony_ci */ 23362306a36Sopenharmony_cistatic bool is_ipmb_msg(struct ipmb_dev *ipmb_dev, u8 rs_sa) 23462306a36Sopenharmony_ci{ 23562306a36Sopenharmony_ci if ((ipmb_dev->msg_idx >= IPMB_REQUEST_LEN_MIN) && 23662306a36Sopenharmony_ci (!ipmb_verify_checksum1(ipmb_dev, rs_sa))) 23762306a36Sopenharmony_ci return true; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci return false; 24062306a36Sopenharmony_ci} 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci/* 24362306a36Sopenharmony_ci * The IPMB protocol only supports I2C Writes so there is no need 24462306a36Sopenharmony_ci * to support I2C_SLAVE_READ* events. 24562306a36Sopenharmony_ci * This i2c callback function only monitors IPMB request messages 24662306a36Sopenharmony_ci * and adds them in a queue, so that they can be handled by 24762306a36Sopenharmony_ci * receive_ipmb_request. 24862306a36Sopenharmony_ci */ 24962306a36Sopenharmony_cistatic int ipmb_slave_cb(struct i2c_client *client, 25062306a36Sopenharmony_ci enum i2c_slave_event event, u8 *val) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci struct ipmb_dev *ipmb_dev = i2c_get_clientdata(client); 25362306a36Sopenharmony_ci u8 *buf = (u8 *)&ipmb_dev->request; 25462306a36Sopenharmony_ci unsigned long flags; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci spin_lock_irqsave(&ipmb_dev->lock, flags); 25762306a36Sopenharmony_ci switch (event) { 25862306a36Sopenharmony_ci case I2C_SLAVE_WRITE_REQUESTED: 25962306a36Sopenharmony_ci memset(&ipmb_dev->request, 0, sizeof(ipmb_dev->request)); 26062306a36Sopenharmony_ci ipmb_dev->msg_idx = 0; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci /* 26362306a36Sopenharmony_ci * At index 0, ipmb_msg stores the length of msg, 26462306a36Sopenharmony_ci * skip it for now. 26562306a36Sopenharmony_ci * The len will be populated once the whole 26662306a36Sopenharmony_ci * buf is populated. 26762306a36Sopenharmony_ci * 26862306a36Sopenharmony_ci * The I2C bus driver's responsibility is to pass the 26962306a36Sopenharmony_ci * data bytes to the backend driver; it does not 27062306a36Sopenharmony_ci * forward the i2c slave address. 27162306a36Sopenharmony_ci * Since the first byte in the IPMB message is the 27262306a36Sopenharmony_ci * address of the responder, it is the responsibility 27362306a36Sopenharmony_ci * of the IPMB driver to format the message properly. 27462306a36Sopenharmony_ci * So this driver prepends the address of the responder 27562306a36Sopenharmony_ci * to the received i2c data before the request message 27662306a36Sopenharmony_ci * is handled in userland. 27762306a36Sopenharmony_ci */ 27862306a36Sopenharmony_ci buf[++ipmb_dev->msg_idx] = GET_8BIT_ADDR(client->addr); 27962306a36Sopenharmony_ci break; 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci case I2C_SLAVE_WRITE_RECEIVED: 28262306a36Sopenharmony_ci if (ipmb_dev->msg_idx >= sizeof(struct ipmb_msg) - 1) 28362306a36Sopenharmony_ci break; 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci buf[++ipmb_dev->msg_idx] = *val; 28662306a36Sopenharmony_ci break; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci case I2C_SLAVE_STOP: 28962306a36Sopenharmony_ci ipmb_dev->request.len = ipmb_dev->msg_idx; 29062306a36Sopenharmony_ci if (is_ipmb_msg(ipmb_dev, GET_8BIT_ADDR(client->addr))) 29162306a36Sopenharmony_ci ipmb_handle_request(ipmb_dev); 29262306a36Sopenharmony_ci break; 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci default: 29562306a36Sopenharmony_ci break; 29662306a36Sopenharmony_ci } 29762306a36Sopenharmony_ci spin_unlock_irqrestore(&ipmb_dev->lock, flags); 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci return 0; 30062306a36Sopenharmony_ci} 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_cistatic int ipmb_probe(struct i2c_client *client) 30362306a36Sopenharmony_ci{ 30462306a36Sopenharmony_ci struct ipmb_dev *ipmb_dev; 30562306a36Sopenharmony_ci int ret; 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci ipmb_dev = devm_kzalloc(&client->dev, sizeof(*ipmb_dev), 30862306a36Sopenharmony_ci GFP_KERNEL); 30962306a36Sopenharmony_ci if (!ipmb_dev) 31062306a36Sopenharmony_ci return -ENOMEM; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci spin_lock_init(&ipmb_dev->lock); 31362306a36Sopenharmony_ci init_waitqueue_head(&ipmb_dev->wait_queue); 31462306a36Sopenharmony_ci atomic_set(&ipmb_dev->request_queue_len, 0); 31562306a36Sopenharmony_ci INIT_LIST_HEAD(&ipmb_dev->request_queue); 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci mutex_init(&ipmb_dev->file_mutex); 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci ipmb_dev->miscdev.minor = MISC_DYNAMIC_MINOR; 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci ipmb_dev->miscdev.name = devm_kasprintf(&client->dev, GFP_KERNEL, 32262306a36Sopenharmony_ci "%s%d", "ipmb-", 32362306a36Sopenharmony_ci client->adapter->nr); 32462306a36Sopenharmony_ci ipmb_dev->miscdev.fops = &ipmb_fops; 32562306a36Sopenharmony_ci ipmb_dev->miscdev.parent = &client->dev; 32662306a36Sopenharmony_ci ret = misc_register(&ipmb_dev->miscdev); 32762306a36Sopenharmony_ci if (ret) 32862306a36Sopenharmony_ci return ret; 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci ipmb_dev->is_i2c_protocol 33162306a36Sopenharmony_ci = device_property_read_bool(&client->dev, "i2c-protocol"); 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci ipmb_dev->client = client; 33462306a36Sopenharmony_ci i2c_set_clientdata(client, ipmb_dev); 33562306a36Sopenharmony_ci ret = i2c_slave_register(client, ipmb_slave_cb); 33662306a36Sopenharmony_ci if (ret) { 33762306a36Sopenharmony_ci misc_deregister(&ipmb_dev->miscdev); 33862306a36Sopenharmony_ci return ret; 33962306a36Sopenharmony_ci } 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci return 0; 34262306a36Sopenharmony_ci} 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_cistatic void ipmb_remove(struct i2c_client *client) 34562306a36Sopenharmony_ci{ 34662306a36Sopenharmony_ci struct ipmb_dev *ipmb_dev = i2c_get_clientdata(client); 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci i2c_slave_unregister(client); 34962306a36Sopenharmony_ci misc_deregister(&ipmb_dev->miscdev); 35062306a36Sopenharmony_ci} 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_cistatic const struct i2c_device_id ipmb_id[] = { 35362306a36Sopenharmony_ci { "ipmb-dev", 0 }, 35462306a36Sopenharmony_ci {}, 35562306a36Sopenharmony_ci}; 35662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, ipmb_id); 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_cistatic const struct acpi_device_id acpi_ipmb_id[] = { 35962306a36Sopenharmony_ci { "IPMB0001", 0 }, 36062306a36Sopenharmony_ci {}, 36162306a36Sopenharmony_ci}; 36262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, acpi_ipmb_id); 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_cistatic struct i2c_driver ipmb_driver = { 36562306a36Sopenharmony_ci .driver = { 36662306a36Sopenharmony_ci .name = "ipmb-dev", 36762306a36Sopenharmony_ci .acpi_match_table = ACPI_PTR(acpi_ipmb_id), 36862306a36Sopenharmony_ci }, 36962306a36Sopenharmony_ci .probe = ipmb_probe, 37062306a36Sopenharmony_ci .remove = ipmb_remove, 37162306a36Sopenharmony_ci .id_table = ipmb_id, 37262306a36Sopenharmony_ci}; 37362306a36Sopenharmony_cimodule_i2c_driver(ipmb_driver); 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ciMODULE_AUTHOR("Mellanox Technologies"); 37662306a36Sopenharmony_ciMODULE_DESCRIPTION("IPMB driver"); 37762306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 378