| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (C) IBM Corporation 2023 */ |
| |
| #include <linux/cdev.h> |
| #include <linux/device.h> |
| #include <linux/fs.h> |
| #include <linux/fsi.h> |
| #include <linux/module.h> |
| #include <linux/mod_devicetable.h> |
| |
| #include "fsi-master-i2cr.h" |
| #include "fsi-slave.h" |
| |
| struct i2cr_scom { |
| struct device dev; |
| struct cdev cdev; |
| struct fsi_master_i2cr *i2cr; |
| }; |
| |
| static loff_t i2cr_scom_llseek(struct file *file, loff_t offset, int whence) |
| { |
| switch (whence) { |
| case SEEK_CUR: |
| break; |
| case SEEK_SET: |
| file->f_pos = offset; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return offset; |
| } |
| |
| static ssize_t i2cr_scom_read(struct file *filep, char __user *buf, size_t len, loff_t *offset) |
| { |
| struct i2cr_scom *scom = filep->private_data; |
| u64 data; |
| int ret; |
| |
| if (len != sizeof(data)) |
| return -EINVAL; |
| |
| ret = fsi_master_i2cr_read(scom->i2cr, (u32)*offset, &data); |
| if (ret) |
| return ret; |
| |
| ret = copy_to_user(buf, &data, len); |
| if (ret) |
| return ret; |
| |
| return len; |
| } |
| |
| static ssize_t i2cr_scom_write(struct file *filep, const char __user *buf, size_t len, |
| loff_t *offset) |
| { |
| struct i2cr_scom *scom = filep->private_data; |
| u64 data; |
| int ret; |
| |
| if (len != sizeof(data)) |
| return -EINVAL; |
| |
| ret = copy_from_user(&data, buf, len); |
| if (ret) |
| return ret; |
| |
| ret = fsi_master_i2cr_write(scom->i2cr, (u32)*offset, data); |
| if (ret) |
| return ret; |
| |
| return len; |
| } |
| |
| static const struct file_operations i2cr_scom_fops = { |
| .owner = THIS_MODULE, |
| .open = simple_open, |
| .llseek = i2cr_scom_llseek, |
| .read = i2cr_scom_read, |
| .write = i2cr_scom_write, |
| }; |
| |
| static int i2cr_scom_probe(struct device *dev) |
| { |
| struct fsi_device *fsi_dev = to_fsi_dev(dev); |
| struct i2cr_scom *scom; |
| int didx; |
| int ret; |
| |
| if (!is_fsi_master_i2cr(fsi_dev->slave->master)) |
| return -ENODEV; |
| |
| scom = devm_kzalloc(dev, sizeof(*scom), GFP_KERNEL); |
| if (!scom) |
| return -ENOMEM; |
| |
| scom->i2cr = to_fsi_master_i2cr(fsi_dev->slave->master); |
| dev_set_drvdata(dev, scom); |
| |
| scom->dev.type = &fsi_cdev_type; |
| scom->dev.parent = dev; |
| device_initialize(&scom->dev); |
| |
| ret = fsi_get_new_minor(fsi_dev, fsi_dev_scom, &scom->dev.devt, &didx); |
| if (ret) |
| return ret; |
| |
| dev_set_name(&scom->dev, "scom%d", didx); |
| cdev_init(&scom->cdev, &i2cr_scom_fops); |
| ret = cdev_device_add(&scom->cdev, &scom->dev); |
| if (ret) |
| fsi_free_minor(scom->dev.devt); |
| |
| return ret; |
| } |
| |
| static int i2cr_scom_remove(struct device *dev) |
| { |
| struct i2cr_scom *scom = dev_get_drvdata(dev); |
| |
| cdev_device_del(&scom->cdev, &scom->dev); |
| fsi_free_minor(scom->dev.devt); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id i2cr_scom_of_ids[] = { |
| { .compatible = "ibm,i2cr-scom" }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(of, i2cr_scom_of_ids); |
| |
| static const struct fsi_device_id i2cr_scom_ids[] = { |
| { 0x5, FSI_VERSION_ANY }, |
| { } |
| }; |
| |
| static struct fsi_driver i2cr_scom_driver = { |
| .id_table = i2cr_scom_ids, |
| .drv = { |
| .name = "i2cr_scom", |
| .bus = &fsi_bus_type, |
| .of_match_table = i2cr_scom_of_ids, |
| .probe = i2cr_scom_probe, |
| .remove = i2cr_scom_remove, |
| } |
| }; |
| |
| module_fsi_driver(i2cr_scom_driver); |
| |
| MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>"); |
| MODULE_DESCRIPTION("IBM I2C Responder SCOM driver"); |
| MODULE_LICENSE("GPL"); |