| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #define pr_fmt(fmt) "rtosfmw: " fmt |
| |
| #include <linux/init.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/sysfs.h> |
| #include <linux/device.h> |
| #include <linux/slab.h> |
| #include <linux/types.h> |
| #include <linux/uaccess.h> |
| #include <linux/platform_device.h> |
| #include <linux/io.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/of_gpio.h> |
| #include <linux/of_address.h> |
| #include <linux/of_platform.h> |
| #include <linux/printk.h> |
| #include <linux/string.h> |
| #include <linux/arm-smccc.h> |
| #include <linux/psci.h> |
| #include <linux/workqueue.h> |
| #include <linux/mutex.h> |
| #include <linux/firmware.h> |
| #include <uapi/linux/psci.h> |
| #include <linux/debugfs.h> |
| #include <linux/sched/signal.h> |
| #include <linux/highmem.h> |
| #include <linux/miscdevice.h> |
| |
| #include <linux/amlogic/scpi_protocol.h> |
| #include <linux/amlogic/rtosfmw.h> |
| |
| #define AML_RTOSFMW_NAME "rtosfmw" |
| |
| #define SIP_FREERTOS_FID 0x8200004B |
| #define RTOSFMW_START 3 |
| #define RTOSFMW_LOAD 4 |
| #define RTOSFMW_FINISH 5 |
| #define SMC_UNK 0xffffffff |
| |
| struct rtosfmw_attr { |
| enum rtosfmw_type type; |
| char *fmwname; |
| int status; |
| struct class_attribute attr; |
| }; |
| |
| struct rtosfmw_data_st { |
| int disabled; |
| int inited; |
| struct platform_device *pdev; |
| struct class cls; |
| struct rtosfmw_attr attr[RFT_MAX]; |
| }; |
| |
| static struct rtosfmw_data_st rtosfmw_data; |
| static DEFINE_MUTEX(rtosfmw_lock); |
| #define FIRMWARE_DIR "rtos" |
| #define CONFIG_PATH_LENG 128 |
| |
| static const char *rtosfmw_type_name[RFT_MAX][2] = { |
| [RFT_SECPU] = {"TYPE_SECPU", "secpu"} |
| }; |
| |
| static struct attribute *rtosfmw_class_attrs[RFT_MAX + 1]; |
| ATTRIBUTE_GROUPS(rtosfmw_class); |
| |
| static int _rtosfmw_start(enum rtosfmw_type type, int size, |
| unsigned long *sbuf, unsigned long *ssize) |
| { |
| struct arm_smccc_res res; |
| |
| if (type >= RFT_MAX || size <= 0) |
| return -1; |
| |
| arm_smccc_smc(SIP_FREERTOS_FID, RTOSFMW_START, |
| (unsigned long)type, (unsigned long)size, |
| 0, 0, 0, 0, &res); |
| |
| if (res.a0 == 0) { |
| *sbuf = res.a1; |
| *ssize = res.a2; |
| } |
| |
| return (int)res.a0; |
| } |
| |
| static int _rtosfmw_load(int size) |
| { |
| struct arm_smccc_res res; |
| |
| if (size <= 0) |
| return -1; |
| |
| arm_smccc_smc(SIP_FREERTOS_FID, RTOSFMW_LOAD, |
| size, 0, 0, 0, 0, 0, &res); |
| |
| return (int)res.a0; |
| } |
| |
| static int _rtosfmw_finish(void) |
| { |
| struct arm_smccc_res res; |
| |
| arm_smccc_smc(SIP_FREERTOS_FID, RTOSFMW_FINISH, |
| 0, 0, 0, 0, 0, 0, &res); |
| |
| return (int)res.a0; |
| } |
| |
| static inline int _rtosfmw_chk_mem(unsigned long phy, unsigned long size) |
| { |
| unsigned long pfn_ed, pfn; |
| int valid = 0, high = 0; |
| |
| if (phy == 0 && size == 0) |
| return -1; |
| |
| if (!PAGE_ALIGNED(phy)) |
| return -1; |
| |
| pfn = __phys_to_pfn(phy); |
| pfn_ed = __phys_to_pfn(phy + size - 1); |
| valid = pfn_valid(pfn); |
| high = valid ? PageHighMem(pfn_to_page(pfn)) : 0; |
| for (pfn++; pfn <= pfn_ed; pfn++) { |
| if (valid != pfn_valid(pfn)) |
| return -1; |
| if (!valid) |
| continue; |
| if (PageHighMem(pfn_to_page(pfn))) |
| high = 1; |
| } |
| |
| return valid + high; |
| } |
| |
| static void _rtosfmw_memcpy_phy(unsigned long phy, const char *data, int size) |
| { |
| unsigned long pfn; |
| char *pbuf; |
| int len = 0, rdlen; |
| |
| pfn = __phys_to_pfn(phy); |
| while (size > 0) { |
| if (size >= PAGE_SIZE) |
| rdlen = PAGE_SIZE; |
| else |
| rdlen = size; |
| pbuf = kmap(pfn_to_page(pfn)); |
| memcpy(pbuf, data + len, rdlen); |
| kunmap(pfn_to_page(pfn)); |
| pfn++; |
| len += rdlen; |
| size -= rdlen; |
| } |
| } |
| |
| static int _rtosfmw_run_firmware(enum rtosfmw_type type, |
| const char *data, int size) |
| { |
| unsigned long phy = 0, ssize = 0; |
| int memtype = 0; |
| char *pbuf = NULL; |
| int ret = -1, len = 0, rdlen; |
| struct rtosfmw_attr *fmwattr; |
| |
| if (type >= RFT_MAX || !data || size <= 0) |
| return -1; |
| |
| fmwattr = &rtosfmw_data.attr[type]; |
| |
| mutex_lock(&rtosfmw_lock); |
| if (fmwattr->status) { |
| pr_err("can't run firmware again\n"); |
| goto exit; |
| } |
| |
| if (_rtosfmw_start(type, size, &phy, &ssize) != 0) { |
| pr_err("firmware start fail\n"); |
| goto update_status; |
| } |
| |
| pr_info("firmware buf: [%lx,%lx]\n", phy, ssize); |
| |
| memtype = _rtosfmw_chk_mem(phy, ssize); |
| if (memtype < 0) { |
| pr_err("check memory fail\n"); |
| goto finish; |
| } |
| |
| if (memtype == 1) { |
| pbuf = phys_to_virt(phy); |
| } else if (memtype == 0) { |
| pbuf = ioremap(phy, ssize); |
| if (!pbuf) { |
| pr_err("map firmware buf fail\n"); |
| goto finish; |
| } |
| } |
| |
| while (size > 0) { |
| if (size >= ssize) |
| rdlen = ssize; |
| else |
| rdlen = size; |
| if (memtype < 2) |
| memcpy(pbuf, data + len, rdlen); |
| else |
| _rtosfmw_memcpy_phy(phy, data + len, rdlen); |
| _rtosfmw_load(rdlen); |
| len += rdlen; |
| size -= rdlen; |
| } |
| |
| if (memtype == 0) |
| iounmap(pbuf); |
| ret = 0; |
| finish: |
| ret |= _rtosfmw_finish(); |
| update_status: |
| if (!ret) |
| fmwattr->status = 1; |
| else |
| fmwattr->status = -1; |
| exit: |
| mutex_unlock(&rtosfmw_lock); |
| return ret; |
| } |
| |
| int aml_rtosfmw_run(enum rtosfmw_type type, const char *name) |
| { |
| const struct firmware *firmware; |
| int ret = -1; |
| char *path = NULL; |
| |
| if (rtosfmw_data.inited == 0 || rtosfmw_data.disabled) { |
| pr_err("not inited or disabled\n"); |
| return -1; |
| } |
| |
| if (type >= RFT_MAX || !name) { |
| pr_err("invalid arguments\n"); |
| return -1; |
| } |
| |
| path = kzalloc(CONFIG_PATH_LENG, GFP_KERNEL); |
| if (!path) { |
| pr_err("%s: kzalloc failed!\n", __func__); |
| return -1; |
| } |
| snprintf(path, (CONFIG_PATH_LENG - 1), "%s/%s", FIRMWARE_DIR, name); |
| |
| ret = request_firmware(&firmware, path, &rtosfmw_data.pdev->dev); |
| if (ret < 0) { |
| pr_err("request '/lib/firmware/%s' failed %d\n", path, ret); |
| kfree(path); |
| return ret; |
| } |
| |
| pr_info("'/lib/firmware/%s' size %lu\n", path, |
| (unsigned long)firmware->size); |
| |
| ret = _rtosfmw_run_firmware(type, firmware->data, firmware->size); |
| if (ret < 0) |
| goto exit; |
| |
| ret = 0; |
| exit: |
| release_firmware(firmware); |
| kfree(path); |
| return ret; |
| } |
| EXPORT_SYMBOL(aml_rtosfmw_run); |
| |
| static enum rtosfmw_type _rtosfmw_type(const char *tname) |
| { |
| int i; |
| |
| if (!tname) |
| return RFT_MAX; |
| for (i = 0; i < RFT_MAX; i++) { |
| if (rtosfmw_type_name[i][0] && |
| !strcmp(tname, rtosfmw_type_name[i][0])) |
| return i; |
| } |
| return RFT_MAX; |
| } |
| |
| static ssize_t rtosfmw_attr_show(struct class *cls, |
| struct class_attribute *attr, |
| char *buf) |
| { |
| struct rtosfmw_attr *fmwattr; |
| ssize_t n = 0; |
| |
| fmwattr = container_of(attr, struct rtosfmw_attr, attr); |
| n = sprintf(buf, "%d\n", fmwattr->status); |
| return n + 1; |
| } |
| |
| static ssize_t rtosfmw_attr_store(struct class *cls, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct rtosfmw_attr *fmwattr; |
| int ret; |
| |
| if (count < 1) |
| return -EINVAL; |
| |
| if (buf[0] != '1' && buf[0] != 1) { |
| pr_err("invalid value\n"); |
| return -EINVAL; |
| } |
| |
| fmwattr = container_of(attr, struct rtosfmw_attr, attr); |
| |
| ret = aml_rtosfmw_run(fmwattr->type, fmwattr->fmwname); |
| pr_info("run firmware \"%s\" result = %d\n", fmwattr->fmwname, ret); |
| if (ret < 0) |
| return -EIO; |
| return count; |
| } |
| |
| static int _rtosfmw_create_file(void) |
| { |
| struct platform_device *pdev; |
| int cnt, i, ret = 0, idx = 0; |
| const char *fwlst[RFT_MAX * 2]; |
| |
| pdev = rtosfmw_data.pdev; |
| |
| for (i = 0; i < RFT_MAX ; i++) { |
| struct rtosfmw_attr *attr = rtosfmw_data.attr + i; |
| const char *name = rtosfmw_type_name[i][1]; |
| |
| attr->type = i; |
| attr->fmwname = NULL; |
| attr->status = 0; |
| attr->attr.attr.name = name; |
| attr->attr.attr.mode = 0644; |
| attr->attr.show = rtosfmw_attr_show; |
| attr->attr.store = rtosfmw_attr_store; |
| } |
| |
| cnt = device_property_read_string_array(&pdev->dev, |
| "firmware", |
| fwlst, |
| RFT_MAX * 2); |
| |
| if (cnt <= 0) |
| return 0; |
| |
| for (i = 0, idx = 0; (i + 2) <= cnt; i += 2) { |
| enum rtosfmw_type type = _rtosfmw_type(fwlst[i]); |
| struct rtosfmw_attr *attr = rtosfmw_data.attr + type; |
| |
| if (type >= RFT_MAX) |
| continue; |
| if (!attr->attr.attr.name) |
| continue; |
| if (attr->fmwname) |
| continue; |
| attr->fmwname = kstrdup(fwlst[i + 1], GFP_KERNEL); |
| if (!attr->fmwname) |
| continue; |
| rtosfmw_class_attrs[idx] = &attr->attr.attr; |
| idx++; |
| } |
| rtosfmw_class_attrs[idx] = NULL; |
| |
| rtosfmw_data.cls.name = AML_RTOSFMW_NAME; |
| rtosfmw_data.cls.owner = THIS_MODULE; |
| rtosfmw_data.cls.class_groups = rtosfmw_class_groups; |
| ret = class_register(&rtosfmw_data.cls); |
| if (ret) { |
| pr_err("register class fail\n"); |
| goto fail_exit; |
| } |
| |
| return 0; |
| fail_exit: |
| for (i = 0; i < RFT_MAX ; i++) { |
| struct rtosfmw_attr *attr = rtosfmw_data.attr + i; |
| |
| kfree(attr->fmwname); |
| attr->fmwname = NULL; |
| } |
| return -1; |
| } |
| |
| static int secpu_dev_open_flag; |
| static DEFINE_MUTEX(secpu_dev_lock); |
| struct secpu_msg_buf *msg_buf; |
| struct secpu_spi_device *secure_spi; |
| struct secpu_spi_device cur_secure_spi; |
| struct secpu_spi_device last_secure_spi; |
| static int secpu_dev_fops_open(struct inode *inode, struct file *f) |
| { |
| int ret; |
| |
| ret = mutex_lock_interruptible(&secpu_dev_lock); |
| if (ret < 0) |
| return ret; |
| |
| if (secpu_dev_open_flag) { |
| pr_err("open fail, already opened\n"); |
| ret = -EBUSY; |
| } else { |
| secpu_dev_open_flag = 1; |
| msg_buf = kzalloc(sizeof(*msg_buf), GFP_KERNEL); |
| secure_spi = kzalloc(sizeof(*secure_spi), GFP_KERNEL); |
| if (!msg_buf || !secure_spi) { |
| ret = -ENOMEM; |
| secpu_dev_open_flag = 0; |
| kfree(msg_buf); |
| kfree(secure_spi); |
| } |
| } |
| |
| mutex_unlock(&secpu_dev_lock); |
| |
| return ret; |
| } |
| |
| static int secpu_dev_fops_release(struct inode *inode, struct file *f) |
| { |
| int ret; |
| |
| ret = mutex_lock_interruptible(&secpu_dev_lock); |
| if (ret < 0) |
| return ret; |
| |
| if (secpu_dev_open_flag) { |
| secpu_dev_open_flag = 0; |
| } else { |
| pr_err("release fail, wrong flag\n"); |
| ret = -EINVAL; |
| } |
| |
| if (msg_buf) |
| kfree(msg_buf); |
| if (secure_spi) |
| kfree(secure_spi); |
| |
| mutex_unlock(&secpu_dev_lock); |
| |
| return ret; |
| } |
| |
| static ssize_t secpu_dev_fops_write(struct file *filp, |
| const char __user *userbuf, |
| size_t count, loff_t *ppos) |
| { |
| if (!msg_buf) { |
| pr_err("%s: 'msg_buf' is NULL\n", __func__); |
| return -EFAULT; |
| } |
| |
| memset(msg_buf->send_msg, 0, SECPU_MSG_LEN); |
| if (count > SECPU_MSG_LEN) { |
| pr_info("%s: wr_len=%d greater than max_len=%d\n", |
| __func__, (int)count, SECPU_MSG_LEN); |
| count = SECPU_MSG_LEN; |
| } |
| |
| if (copy_from_user(msg_buf->send_msg, userbuf, count)) { |
| pr_err("%s: Failed to copy_from_user\n", __func__); |
| return -EFAULT; |
| } |
| |
| return count; |
| } |
| |
| static ssize_t secpu_dev_fops_read(struct file *filp, |
| char __user *userbuf, |
| size_t count, loff_t *ppos) |
| { |
| if (!msg_buf) { |
| pr_err("%s: 'msg_buf' is NULL\n", __func__); |
| return -EFAULT; |
| } |
| |
| if (count > SECPU_MSG_LEN) { |
| pr_info("%s: rd_len=%d greater than max_len=%d\n", |
| __func__, (int)count, SECPU_MSG_LEN); |
| count = SECPU_MSG_LEN; |
| } |
| |
| if (copy_to_user(userbuf, msg_buf->rev_msg, count)) { |
| pr_err("%s: Failed to copy_to_user\n", __func__); |
| return -EFAULT; |
| } |
| |
| return count; |
| } |
| |
| static long secpu_dev_fops_ioctl(struct file *filp, unsigned int cmd, |
| unsigned long arg) |
| { |
| long rtn = 0; |
| void __user *argp = (void __user *)arg; |
| int ret = 0; |
| struct uintcase secpu_msg_packet; |
| size_t msg_packet_len; |
| struct rtosfmw_attr *secpu_attr = rtosfmw_data.attr; |
| u32 tmp; |
| |
| memset(&secpu_msg_packet, 0, sizeof(secpu_msg_packet)); |
| secpu_msg_packet.ultaskdelay = 100; |
| |
| switch (cmd) { |
| case REE_SECPU_MSG: //REE |
| msg_packet_len = sizeof(secpu_msg_packet.data) / sizeof(char); |
| memcpy(secpu_msg_packet.data, msg_buf->send_msg, msg_packet_len); |
| ret = scpi_send_data(&secpu_msg_packet, sizeof(secpu_msg_packet), |
| SCPI_SECPU, MBX_CMD_RPCUINTREE_TEST, |
| msg_buf->rev_msg, sizeof(msg_buf->rev_msg)); |
| if (copy_to_user(argp, &ret, sizeof(int))) { |
| rtn = -1; |
| pr_err("%s :Failed to copy\n", __func__); |
| } |
| break; |
| case TEE_SECPU_MSG: //TEE |
| msg_packet_len = sizeof(secpu_msg_packet.data) / sizeof(char); |
| memcpy(secpu_msg_packet.data, msg_buf->send_msg, msg_packet_len); |
| ret = scpi_send_data(&secpu_msg_packet, sizeof(secpu_msg_packet), |
| SCPI_SECPU, MBX_CMD_RPCUINTTEE_TEST, |
| msg_buf->rev_msg, sizeof(msg_buf->rev_msg)); |
| if (copy_to_user(argp, &ret, sizeof(int))) { |
| rtn = -1; |
| pr_err("%s :Failed to copy\n", __func__); |
| } |
| break; |
| case TRIGGER_SECPU_RUN: |
| rtn = aml_rtosfmw_run(secpu_attr->type, secpu_attr->fmwname); |
| pr_info("run firmware \"%s\" result = %d\n", secpu_attr->fmwname, ret); |
| if (rtn < 0) |
| rtn = -1; |
| |
| if (copy_to_user(argp, &secpu_attr->status, sizeof(int))) { |
| rtn = -1; |
| pr_err("%s :Failed to copy\n", __func__); |
| } |
| break; |
| case GET_RUN_STATUS: |
| if (copy_to_user(argp, &secpu_attr->status, sizeof(int))) { |
| rtn = -1; |
| pr_err("%s :Failed to copy\n", __func__); |
| } |
| break; |
| |
| /*get*/ |
| case SPI_MODE_RD_32: |
| if (put_user(last_secure_spi.mode & 0xfff, (__u32 __user *)arg)) { |
| rtn = -1; |
| pr_err("%s :Failed to get mode\n", __func__); |
| } |
| break; |
| case SPI_BITS_PER_WORD_RD: |
| if (put_user(last_secure_spi.bits_per_word, (__u8 __user *)arg)) { |
| rtn = -1; |
| pr_err("%s :Failed to get bpw\n", __func__); |
| } |
| break; |
| case SPI_MAX_SPEED_HZ_RD: |
| if (put_user(last_secure_spi.max_speed_hz, (__u32 __user *)arg)) { |
| rtn = -1; |
| pr_err("%s :Failed to get speed\n", __func__); |
| } |
| break; |
| case SPI_CFG_RD: |
| memset(&cur_secure_spi, 0, sizeof(cur_secure_spi)); |
| secure_spi->direction = SECPU2ARM; |
| ret = scpi_send_data(secure_spi, sizeof(*secure_spi), |
| SCPI_SECPU, MBX_CMD_SPI_CFG_RW, |
| &cur_secure_spi, sizeof(cur_secure_spi)); |
| // update spi cfg status; |
| if (ret == 0) { |
| last_secure_spi.mode = cur_secure_spi.mode; |
| last_secure_spi.bits_per_word = cur_secure_spi.bits_per_word; |
| last_secure_spi.max_speed_hz = cur_secure_spi.max_speed_hz; |
| } else { |
| rtn = -1; |
| pr_err("%s :Failed to get spi config\n", __func__); |
| } |
| break; |
| /*set*/ |
| case SPI_MODE_WR_32: |
| if (!get_user(tmp, (u32 __user *)arg)) { |
| if (tmp & ~0xfff) { |
| rtn = -EINVAL; |
| break; |
| } |
| secure_spi->mode = (u16)tmp; |
| } else { |
| secure_spi->mode = last_secure_spi.mode; |
| rtn = -1; |
| } |
| break; |
| case SPI_BITS_PER_WORD_WR: |
| ret = get_user(tmp, (__u8 __user *)arg); |
| if (ret == 0) { |
| secure_spi->bits_per_word = tmp; |
| } else { |
| secure_spi->bits_per_word = last_secure_spi.bits_per_word; |
| rtn = -1; |
| } |
| break; |
| case SPI_MAX_SPEED_HZ_WR: |
| ret = get_user(tmp, (__u32 __user *)arg); |
| if (ret == 0) { |
| secure_spi->max_speed_hz = tmp; |
| } else { |
| secure_spi->max_speed_hz = last_secure_spi.max_speed_hz; |
| rtn = -1; |
| } |
| break; |
| case SPI_CFG_WR: |
| memset(&cur_secure_spi, 0, sizeof(cur_secure_spi)); |
| secure_spi->direction = ARM2SECPU; |
| ret = scpi_send_data(secure_spi, sizeof(*secure_spi), |
| SCPI_SECPU, MBX_CMD_SPI_CFG_RW, |
| &cur_secure_spi, sizeof(cur_secure_spi)); |
| // update spi cfg status; |
| if (ret == 0) { |
| last_secure_spi.mode = cur_secure_spi.mode; |
| last_secure_spi.bits_per_word = cur_secure_spi.bits_per_word; |
| last_secure_spi.max_speed_hz = cur_secure_spi.max_speed_hz; |
| } else { |
| rtn = -1; |
| pr_err("%s :Failed to set spi config\n", __func__); |
| } |
| break; |
| default: |
| break; |
| } |
| |
| return rtn; |
| } |
| |
| static const struct file_operations secpu_dev_fops = { |
| .owner = THIS_MODULE, |
| .open = secpu_dev_fops_open, |
| .release = secpu_dev_fops_release, |
| .unlocked_ioctl = secpu_dev_fops_ioctl, |
| .write = secpu_dev_fops_write, |
| .read = secpu_dev_fops_read, |
| }; |
| |
| #define SECPU_DEV "secpu_dev" |
| static struct miscdevice secpu_misc_dev = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = SECPU_DEV, |
| .fops = &secpu_dev_fops, |
| }; |
| |
| static int scpi_channel; |
| static int scpi_cmd; |
| static ssize_t scpi_read_file(struct file *file, char __user *userbuf, |
| size_t count, loff_t *ppos) |
| { |
| struct scpitest { |
| char data[20]; |
| u32 ulTaskDelay; |
| } scpitest; |
| char rx_data[40] = {0}; |
| |
| if (scpi_channel != SCPI_SECPU || |
| (scpi_cmd != MBX_CMD_RPCUINTREE_TEST && |
| scpi_cmd != MBX_CMD_RPCUINTTEE_TEST)) { |
| pr_err("%s: wrong command.\n", __func__); |
| pr_err("example: REE-0x6 TEE-0x7\n"); |
| pr_err("echo 0x3 0x6 > /sys/kernel/debug/scpi\n"); |
| pr_err("cat /sys/kernel/debug/scpi\n"); |
| return 0; |
| } |
| |
| strcpy(scpitest.data, "Hello RISC-V."); |
| scpitest.ulTaskDelay = 100; |
| |
| pr_info("%s: arm2risc-v - Send a message :%s\n", |
| __func__, scpitest.data); |
| scpi_send_data(&scpitest, sizeof(scpitest), scpi_channel, scpi_cmd, |
| rx_data, sizeof(rx_data)); |
| pr_info("%s: arm2risc-v - Received response:%s\n", |
| __func__, rx_data); |
| return 0; |
| } |
| |
| static ssize_t scpi_write_file(struct file *file, const char __user *userbuf, |
| size_t count, loff_t *ppos) |
| { |
| unsigned int reg, val; |
| char buf[80]; |
| int ret; |
| |
| count = min_t(size_t, count, (sizeof(buf) - 1)); |
| if (copy_from_user(buf, userbuf, count)) |
| return -EFAULT; |
| |
| buf[count] = 0; |
| |
| ret = sscanf(buf, "%x %x", ®, &val); |
| |
| if (ret == 1) |
| scpi_channel = reg; |
| |
| if (ret == 2) { |
| scpi_channel = reg; |
| scpi_cmd = val; |
| } |
| |
| return count; |
| } |
| |
| static const struct file_operations scpi_file_ops = { |
| .open = simple_open, |
| .read = scpi_read_file, |
| .write = scpi_write_file, |
| }; |
| |
| static int aml_rtos_secpu_dev_init(void) |
| { |
| int rc = 0; |
| |
| rc = misc_register(&secpu_misc_dev); |
| if (rc) |
| pr_err("%s: register status data dev fail\n", __func__); |
| |
| debugfs_create_file("scpi", S_IFREG | 0440, |
| NULL, NULL, &scpi_file_ops); |
| |
| return rc; |
| } |
| |
| static void aml_rtos_secpu_dev_deinit(void) |
| { |
| misc_deregister(&secpu_misc_dev); |
| } |
| |
| static int aml_rtosfmw_probe(struct platform_device *pdev) |
| { |
| if (rtosfmw_data.inited) { |
| pr_err("already inited\n"); |
| return -1; |
| } |
| |
| rtosfmw_data.pdev = pdev; |
| |
| if (rtosfmw_data.disabled) |
| return 0; |
| |
| if (!fwnode_device_is_available(dev_fwnode(&pdev->dev))) |
| return 0; |
| |
| if (_rtosfmw_create_file() < 0) |
| return -1; |
| |
| aml_rtos_secpu_dev_init(); |
| |
| memset(&cur_secure_spi, 0, sizeof(cur_secure_spi)); |
| memset(&last_secure_spi, 0, sizeof(last_secure_spi)); |
| rtosfmw_data.inited = 1; |
| return 0; |
| } |
| |
| static int __exit aml_rtosfmw_remove(struct platform_device *pdev) |
| { |
| int i; |
| |
| if (!rtosfmw_data.inited) |
| return 0; |
| |
| class_unregister(&rtosfmw_data.cls); |
| |
| for (i = 0; i < RFT_MAX ; i++) { |
| struct rtosfmw_attr *attr = rtosfmw_data.attr + i; |
| |
| kfree(attr->fmwname); |
| attr->fmwname = NULL; |
| } |
| |
| aml_rtos_secpu_dev_deinit(); |
| |
| rtosfmw_data.inited = 0; |
| rtosfmw_data.pdev = NULL; |
| return 0; |
| } |
| |
| static const struct of_device_id aml_rtosfmw_dt_match[] = { |
| { |
| .compatible = "amlogic,rtosfmw", |
| }, |
| {}, |
| }; |
| |
| static struct platform_driver aml_rtosfmw_driver = { |
| .driver = { |
| .name = AML_RTOSFMW_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = aml_rtosfmw_dt_match, |
| }, |
| .probe = aml_rtosfmw_probe, |
| .remove = __exit_p(aml_rtosfmw_remove), |
| }; |
| |
| static int __init disable_rtosfmw(char *str) |
| { |
| rtosfmw_data.disabled = 1; |
| return 0; |
| } |
| |
| early_param("disable_rtosfmw", disable_rtosfmw); |
| |
| static int __init aml_rtosfmw_init(void) |
| { |
| return platform_driver_register(&aml_rtosfmw_driver); |
| } |
| module_init(aml_rtosfmw_init); |
| |
| static void __exit aml_rtosfmw_exit(void) |
| { |
| platform_driver_unregister(&aml_rtosfmw_driver); |
| } |
| module_exit(aml_rtosfmw_exit); |
| |
| MODULE_DESCRIPTION("RtosFMW Driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Amlogic, Inc."); |