| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #include <linux/cdev.h> |
| #include <linux/types.h> |
| #include <linux/fs.h> |
| #include <linux/device.h> |
| #include <linux/slab.h> |
| #include <linux/uaccess.h> |
| #include <linux/platform_device.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/clk.h> |
| #include "efuse.h" |
| #include <linux/amlogic/efuse.h> |
| #include <linux/io.h> |
| #include <linux/compat.h> |
| #include "efuse_burn.h" |
| |
| #define EFUSE_BURN_DEVICE_NAME "efuse_burn" |
| #define EFUSE_BURN_CLASS_NAME "efuse_burn" |
| |
| static struct aml_efuse_burn_dev *pefuse_burn_dev; |
| |
| static int efuse_burn_open(struct inode *inode, struct file *file) |
| { |
| struct aml_efuse_burn_dev *devp; |
| |
| devp = container_of(inode->i_cdev, struct aml_efuse_burn_dev, cdev); |
| file->private_data = devp; |
| |
| //printk(KERN_NOTICE "%s:%d\n", __func__, __LINE__); |
| pr_notice("%s:%d\n", __func__, __LINE__); |
| |
| return 0; |
| } |
| |
| static int efuse_burn_release(struct inode *inode, struct file *file) |
| { |
| file->private_data = NULL; |
| return 0; |
| } |
| |
| static loff_t efuse_burn_llseek(struct file *filp, loff_t off, int whence) |
| { |
| return 0; |
| } |
| |
| static long efuse_burn_unlocked_ioctl(struct file *file, |
| unsigned int cmd, unsigned long arg) |
| { |
| long ret = -ENOTTY; |
| void __user *argp = (void __user *)arg; |
| struct efuse_burn_info info; |
| |
| pr_notice("%s:%d\n", __func__, __LINE__); |
| switch (cmd) { |
| case EFUSE_BURN_CHECK_KEY: |
| if (copy_from_user((void *)&info, argp, sizeof(info))) { |
| pr_err("%s: copy_from_user fail\n", __func__); |
| return -EFAULT; |
| } |
| if (efuse_burn_lockable_is_cfg(info.itemname) == 0) { |
| info.status = efuse_burn_check_burned(info.itemname); |
| } else { |
| pr_err("%s: efuse_burn check item not cfg\n", __func__); |
| return -EFAULT; |
| } |
| if (copy_to_user(argp, &info, sizeof(info))) { |
| pr_err("%s: copy_to_user fail\n", __func__); |
| return -EFAULT; |
| } |
| break; |
| } |
| |
| return ret; |
| } |
| |
| #ifdef CONFIG_COMPAT |
| static long efuse_burn_compat_ioctl(struct file *filp, |
| unsigned int cmd, unsigned long args) |
| { |
| long ret; |
| |
| args = (unsigned long)compat_ptr(args); |
| ret = efuse_burn_unlocked_ioctl(filp, cmd, args); |
| |
| return ret; |
| } |
| #endif |
| |
| static ssize_t efuse_burn_read(struct file *file, char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| pr_notice("%s:%d\n", __func__, __LINE__); |
| return 0; |
| } |
| |
| static ssize_t efuse_burn_write(struct file *file, |
| const char __user *buf, size_t count, loff_t *ppos) |
| { |
| struct aml_efuse_burn_dev *devp; |
| ssize_t ret; |
| char *op = NULL; |
| |
| devp = file->private_data; |
| //pr_notice("%s:%d, sizeof(loff_t):%d\n", |
| // __func__, __LINE__, sizeof(loff_t)); |
| //pr_notice("pos=%lld,count=%d\n", *ppos, count); |
| if (count != devp->efuse_pattern_size) { |
| ret = -EINVAL; |
| pr_err("efuse burn: bad pattern size, only support size %d!\n", |
| devp->efuse_pattern_size); |
| goto exit; |
| } |
| op = kzalloc(sizeof(char) * count, GFP_KERNEL); |
| if (!op) { |
| ret = -ENOMEM; |
| pr_err("efuse burn: failed to allocate memory!\n"); |
| goto exit; |
| } |
| |
| memset(op, 0, count); |
| if (copy_from_user(op, buf, count)) { |
| pr_err("%s: copy_from_user fail\n", __func__); |
| kfree(op); |
| ret = -EFAULT; |
| goto exit; |
| } |
| |
| ret = efuse_amlogic_set(op, count); |
| kfree(op); |
| |
| if (ret) { |
| pr_err("efuse burn: pattern programming fail! ret: %d\n", |
| (unsigned int)ret); |
| ret = -EINVAL; |
| goto exit; |
| } |
| |
| pr_info("efuse burn: pattern programming success!\n"); |
| |
| ret = count; |
| |
| exit: |
| return ret; |
| } |
| |
| static const struct file_operations efuse_burn_fops = { |
| .owner = THIS_MODULE, |
| .llseek = efuse_burn_llseek, |
| .open = efuse_burn_open, |
| .release = efuse_burn_release, |
| .read = efuse_burn_read, |
| .write = efuse_burn_write, |
| .unlocked_ioctl = efuse_burn_unlocked_ioctl, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = efuse_burn_compat_ioctl, |
| #endif |
| }; |
| |
| static ssize_t version_show(struct class *cla, |
| struct class_attribute *attr, char *buf) |
| { |
| ssize_t n = 0; |
| |
| n = sprintf(buf, "ver1.0"); |
| return n; |
| } |
| |
| static ssize_t version_store(struct class *cla, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| pr_notice("%s:%d,buf=0x%lx,count=%zu\n", |
| __func__, __LINE__, (long)buf, count); |
| return count; |
| } |
| |
| static EFUSE_CLASS_ATTR(version); |
| |
| static struct attribute *efuse_burn_class_attrs[] = { |
| &class_attr_version.attr, |
| NULL, |
| }; |
| ATTRIBUTE_GROUPS(efuse_burn_class); |
| |
| static int efuse_burn_probe(struct platform_device *pdev) |
| { |
| int ret; |
| struct device *devp; |
| struct aml_efuse_burn_dev *efuse_burn_dev; |
| struct device_node *np = pdev->dev.of_node; |
| |
| efuse_burn_dev = devm_kzalloc(&pdev->dev, sizeof(*efuse_burn_dev), |
| GFP_KERNEL); |
| if (!efuse_burn_dev) { |
| ret = -ENOMEM; |
| dev_err(&pdev->dev, "failed to alloc enough mem for efuse_dev\n"); |
| goto out; |
| } |
| |
| efuse_burn_dev->pdev = pdev; |
| platform_set_drvdata(pdev, efuse_burn_dev); |
| |
| of_node_get(np); |
| |
| ret = of_property_read_u32(np, "efuse_pattern_size", |
| &efuse_burn_dev->efuse_pattern_size); |
| if (ret) { |
| pr_err("can't get efuse_pattern_size, please configurate size\n"); |
| goto error1; |
| } |
| |
| ret = alloc_chrdev_region(&efuse_burn_dev->devno, 0, 1, |
| EFUSE_BURN_DEVICE_NAME); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "fail to allocate major number\n "); |
| goto error1; |
| } |
| |
| efuse_burn_dev->cls.name = EFUSE_BURN_CLASS_NAME; |
| efuse_burn_dev->cls.owner = THIS_MODULE; |
| efuse_burn_dev->cls.class_groups = efuse_burn_class_groups; |
| ret = class_register(&efuse_burn_dev->cls); |
| if (ret) |
| goto error2; |
| |
| cdev_init(&efuse_burn_dev->cdev, &efuse_burn_fops); |
| efuse_burn_dev->cdev.owner = THIS_MODULE; |
| |
| ret = cdev_add(&efuse_burn_dev->cdev, efuse_burn_dev->devno, 1); |
| if (ret) { |
| dev_err(&pdev->dev, "failed to add device\n"); |
| goto error3; |
| } |
| |
| devp = device_create(&efuse_burn_dev->cls, NULL, |
| efuse_burn_dev->devno, efuse_burn_dev, "efuse_burn"); |
| if (IS_ERR(devp)) { |
| dev_err(&pdev->dev, "failed to create device node\n"); |
| ret = PTR_ERR(devp); |
| goto error4; |
| } |
| pefuse_burn_dev = efuse_burn_dev; |
| |
| dev_info(&pdev->dev, "device %s created OK\n", EFUSE_BURN_DEVICE_NAME); |
| |
| return 0; |
| |
| error4: |
| cdev_del(&efuse_burn_dev->cdev); |
| error3: |
| class_unregister(&efuse_burn_dev->cls); |
| error2: |
| unregister_chrdev_region(efuse_burn_dev->devno, 1); |
| error1: |
| devm_kfree(&pdev->dev, efuse_burn_dev); |
| out: |
| return ret; |
| } |
| |
| static int efuse_burn_remove(struct platform_device *pdev) |
| { |
| struct aml_efuse_burn_dev *efuse_burn_dev; |
| |
| efuse_burn_dev = platform_get_drvdata(pdev); |
| unregister_chrdev_region(efuse_burn_dev->devno, 1); |
| device_destroy(&efuse_burn_dev->cls, efuse_burn_dev->devno); |
| cdev_del(&efuse_burn_dev->cdev); |
| class_unregister(&efuse_burn_dev->cls); |
| platform_set_drvdata(pdev, NULL); |
| devm_kfree(&pdev->dev, efuse_burn_dev); |
| return 0; |
| } |
| |
| static const struct of_device_id efuse_burn_dt_match[] = { |
| { .compatible = "amlogic, efuseburn", |
| }, |
| {}, |
| }; |
| |
| static struct platform_driver efuse_burn_driver = { |
| .probe = efuse_burn_probe, |
| .remove = efuse_burn_remove, |
| .driver = { |
| .name = EFUSE_BURN_DEVICE_NAME, |
| .of_match_table = efuse_burn_dt_match, |
| .owner = THIS_MODULE, |
| }, |
| }; |
| |
| int __init aml_efuse_burn_init(void) |
| { |
| return platform_driver_register(&efuse_burn_driver); |
| } |
| |
| void aml_efuse_burn_exit(void) |
| { |
| platform_driver_unregister(&efuse_burn_driver); |
| } |
| |