blob: 814b9a531c7d96a9cf75fd1cfc0d31c31b10bde6 [file] [log] [blame]
/*
* drivers/amlogic/efuse/efuse64.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/amlogic/secmon.h>
#include "efuse.h"
#if defined(CONFIG_ARM64) || defined(CONFIG_ARM64_A32)
#include <linux/amlogic/efuse.h>
#endif
#define EFUSE_MODULE_NAME "efuse"
#define EFUSE_DRIVER_NAME "efuse"
#define EFUSE_DEVICE_NAME "efuse"
#define EFUSE_CLASS_NAME "efuse"
#define EFUSE_IS_OPEN (0x01)
struct efusekey_info *efusekey_infos;
int efusekeynum = -1;
struct efuse_dev_t {
struct cdev cdev;
unsigned int flags;
};
static struct efuse_dev_t *efuse_devp;
/* static struct class *efuse_clsp; */
static dev_t efuse_devno;
void __iomem *sharemem_input_base;
void __iomem *sharemem_output_base;
unsigned int efuse_read_cmd;
unsigned int efuse_write_cmd;
unsigned int efuse_get_max_cmd;
#define DEFINE_EFUEKEY_SHOW_ATTR(keyname) \
static ssize_t show_##keyname(struct class *cla, \
struct class_attribute *attr, \
char *buf) \
{ \
ssize_t ret; \
\
ret = efuse_user_attr_show(#keyname, buf); \
return ret; \
}
DEFINE_EFUEKEY_SHOW_ATTR(mac)
DEFINE_EFUEKEY_SHOW_ATTR(mac_bt)
DEFINE_EFUEKEY_SHOW_ATTR(mac_wifi)
DEFINE_EFUEKEY_SHOW_ATTR(usid)
#define DEFINE_EFUEKEY_STORE_ATTR(keyname) \
static ssize_t store_##keyname(struct class *cla, \
struct class_attribute *attr, \
const char *buf, \
size_t count) \
{ \
ssize_t ret; \
\
ret = efuse_user_attr_store(#keyname, buf, count); \
return ret; \
}
DEFINE_EFUEKEY_STORE_ATTR(mac)
DEFINE_EFUEKEY_STORE_ATTR(mac_bt)
DEFINE_EFUEKEY_STORE_ATTR(mac_wifi)
DEFINE_EFUEKEY_STORE_ATTR(usid)
int efuse_getinfo(char *item, struct efusekey_info *info)
{
int i;
int ret = -1;
for (i = 0; i < efusekeynum; i++) {
if (strcmp(efusekey_infos[i].keyname, item) == 0) {
strcpy(info->keyname, efusekey_infos[i].keyname);
info->offset = efusekey_infos[i].offset;
info->size = efusekey_infos[i].size;
ret = 0;
break;
}
}
if (ret < 0)
pr_err("%s item not found.\n", item);
return ret;
}
static int efuse_open(struct inode *inode, struct file *file)
{
int ret = 0;
struct efuse_dev_t *devp;
devp = container_of(inode->i_cdev, struct efuse_dev_t, cdev);
file->private_data = devp;
return ret;
}
static int efuse_release(struct inode *inode, struct file *file)
{
int ret = 0;
struct efuse_dev_t *devp;
unsigned long efuse_status = 0;
devp = file->private_data;
efuse_status &= ~EFUSE_IS_OPEN;
return ret;
}
loff_t efuse_llseek(struct file *filp, loff_t off, int whence)
{
loff_t newpos;
switch (whence) {
case 0: /* SEEK_SET */
newpos = off;
break;
case 1: /* SEEK_CUR */
newpos = filp->f_pos + off;
break;
case 2: /* SEEK_END */
newpos = EFUSE_BYTES + off;
break;
default: /* can't happen */
return -EINVAL;
}
if (newpos < 0)
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
static long efuse_unlocked_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
void __user *argp = (void __user *)arg;
struct efusekey_info info;
int ret;
switch (cmd) {
case EFUSE_INFO_GET:
ret = copy_from_user(&info, argp, sizeof(info));
if (ret != 0) {
pr_err("%s:%d,copy_from_user fail\n",
__func__, __LINE__);
return ret;
}
info.keyname[sizeof(info.keyname) - 1] = '\0';
if (efuse_getinfo(info.keyname, &info) < 0) {
pr_err("%s if not found\n", info.keyname);
return -EFAULT;
}
ret = copy_to_user(argp, &info, sizeof(info));
if (ret != 0) {
pr_err("%s:%d,copy_to_user fail\n",
__func__, __LINE__);
return ret;
}
break;
default:
return -ENOTTY;
}
return 0;
}
#ifdef CONFIG_COMPAT
static long efuse_compat_ioctl(struct file *filp,
unsigned int cmd, unsigned long args)
{
unsigned long ret;
args = (unsigned long)compat_ptr(args);
ret = efuse_unlocked_ioctl(filp, cmd, args);
return ret;
}
#endif
static ssize_t efuse_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
int ret;
int local_count = 0;
unsigned char *local_buf = kcalloc(count, sizeof(char), GFP_KERNEL);
if (!local_buf)
return -ENOMEM;
local_count = efuse_read_usr(local_buf, count, ppos);
if (local_count < 0) {
ret = -EFAULT;
pr_err("%s: read user error!!--%d\n", __func__, __LINE__);
goto error_exit;
}
if (copy_to_user((void *)buf, (void *)local_buf, local_count)) {
ret = -EFAULT;
pr_err("%s: copy_to_user error!!--%d\n", __func__, __LINE__);
goto error_exit;
}
ret = local_count;
error_exit:
/*if (local_buf)*/
kfree(local_buf);
return ret;
}
static ssize_t efuse_write(struct file *file,
const char __user *buf, size_t count, loff_t *ppos)
{
int ret, size;
unsigned int pos = (unsigned int)*ppos;
unsigned char *contents = NULL;
if (pos >= EFUSE_BYTES)
return 0; /* Past EOF */
if (count > EFUSE_BYTES - pos)
count = EFUSE_BYTES - pos;
if (count > EFUSE_BYTES)
return -EFAULT;
contents = kzalloc(sizeof(unsigned char)*EFUSE_BYTES, GFP_KERNEL);
if (!contents)
return -ENOMEM;
size = sizeof(contents);
memset(contents, 0, size);
if (copy_from_user(contents, buf, count)) {
pr_err("efuse_write copy_from_user ERROR!!\n");
kfree(contents);
return -EFAULT;
}
ret = efuse_write_usr(contents, count, ppos);
if (ret < 0) {
pr_err("write user area %Zd bytes data, fail!!!\n", count);
kfree(contents);
return -EFAULT;
}
pr_info("user area %d bytes data, write OK\n", ret);
kfree(contents);
return count;
}
static const struct file_operations efuse_fops = {
.owner = THIS_MODULE,
.llseek = efuse_llseek,
.open = efuse_open,
.release = efuse_release,
.read = efuse_read,
.write = efuse_write,
.unlocked_ioctl = efuse_unlocked_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = efuse_compat_ioctl,
#endif
};
ssize_t efuse_user_attr_store(char *name, const char *buf, size_t count)
{
#ifndef EFUSE_READ_ONLY
char *local_buf;
ssize_t ret;
int i;
const char *c, *s;
struct efusekey_info info;
unsigned int uint_val;
loff_t pos;
if (efuse_getinfo(name, &info) < 0) {
pr_err("%s is not found\n", name);
return -EFAULT;
}
local_buf = kzalloc(sizeof(char)*(count), GFP_KERNEL);
if (!local_buf) {
ret = -ENOMEM;
pr_err("efuse: failed to allocate memory!\n");
return ret;
}
memcpy(local_buf, buf, count);
c = ":";
s = local_buf;
if (strstr(s, c) != NULL) {
for (i = 0; i < info.size; i++) {
uint_val = 0;
ret = sscanf(s, "%x", &uint_val);
if (ret < 0) {
pr_err("ERROR: efuse get user data fail!\n");
goto error_exit;
} else
local_buf[i] = uint_val;
s += 2;
if (!strncmp(s, c, 1))
s++;
pr_debug("local_buf[%d]: 0x%x\n",
i, local_buf[i]);
}
}
pos = ((loff_t)(info.offset)) & 0xffffffff;
ret = efuse_write_usr(local_buf, info.size, &pos);
if (ret == -1) {
pr_err("ERROR: efuse write user data fail!\n");
goto error_exit;
}
if (ret != info.size)
pr_err("ERROR: write %zd byte(s) not %d byte(s) data\n",
ret, info.size);
pr_info("efuse write %zd data OK\n", ret);
error_exit:
kfree(local_buf);
return count;
#else
pr_err("no permission to write!!\n");
return -1;
#endif
}
ssize_t efuse_user_attr_show(char *name, char *buf)
{
char *local_buf;
ssize_t ret;
ssize_t len = 0;
int i;
struct efusekey_info info;
loff_t pos;
if (efuse_getinfo(name, &info) < 0) {
pr_err("%s is not found\n", name);
return -EFAULT;
}
local_buf = kzalloc(sizeof(char)*(info.size), GFP_KERNEL);
if (!local_buf) {
ret = -ENOMEM;
pr_err("efuse: failed to allocate memory!\n");
return ret;
}
memset(local_buf, 0, info.size);
pos = ((loff_t)(info.offset)) & 0xffffffff;
ret = efuse_read_usr(local_buf, info.size, &pos);
if (ret == -1) {
pr_err("ERROR: efuse read user data fail!\n");
goto error_exit;
}
if (ret != info.size)
pr_err("ERROR: read %zd byte(s) not %d byte(s) data\n",
ret, info.size);
for (i = 0; i < info.size; i++) {
if (i%16 == 0)
len += sprintf(buf + len, "\n");
if (i%16 == 0)
len += sprintf(buf + len, "0x%02x: ", i);
len += sprintf(buf + len, "%02x ", local_buf[i]);
}
len += sprintf(buf + len, "\n");
ret = len;
error_exit:
kfree(local_buf);
return ret;
}
ssize_t efuse_user_attr_read(char *name, char *buf)
{
char *local_buf;
ssize_t ret;
struct efusekey_info info;
loff_t pos;
if (efuse_getinfo(name, &info) < 0) {
pr_err("%s is not found\n", name);
return -EFAULT;
}
local_buf = kzalloc(sizeof(char)*(info.size), GFP_KERNEL);
if (!local_buf) {
ret = -ENOMEM;
pr_err("efuse: failed to allocate memory!\n");
return ret;
}
memset(local_buf, 0, info.size);
pos = ((loff_t)(info.offset)) & 0xffffffff;
ret = efuse_read_usr(local_buf, info.size, &pos);
if (ret == -1) {
pr_err("ERROR: efuse read user data fail!\n");
goto error_exit;
}
if (ret != info.size)
pr_err("ERROR: read %zd byte(s) not %d byte(s) data\n",
ret, info.size);
memcpy(buf, local_buf, info.size);
error_exit:
kfree(local_buf);
return ret;
}
static ssize_t userdata_show(struct class *cla,
struct class_attribute *attr, char *buf)
{
char *op;
int ret;
ssize_t len = 0;
int i;
loff_t offset;
unsigned int max_size;
ret = efuse_get_max();
if (ret < 0) {
pr_err("efuse: failed to get max size\n");
return -1;
}
max_size = (unsigned int)ret;
op = kmalloc_array(max_size, sizeof(char), GFP_KERNEL);
if (!op) {
ret = -ENOMEM;
return ret;
}
memset(op, 0, max_size);
offset = 0;
ret = efuse_read_usr(op, max_size, &offset);
if (ret < 0) {
pr_err("%s: efuse read user data error!!\n", __func__);
kfree(op);
return -1;
}
for (i = 0; i < ret; i++) {
if ((i != 0) && (i%16 == 0))
len += sprintf(buf + len, "\n");
if (i%16 == 0)
len += sprintf(buf + len, "0x%02x: ", i);
len += sprintf(buf + len, "%02x ", op[i]);
}
len += sprintf(buf + len, "\n");
kfree(op);
return len;
}
#ifndef EFUSE_READ_ONLY
static ssize_t userdata_write(struct class *cla,
struct class_attribute *attr, const char *buf, size_t count)
{
int i;
int ret;
loff_t offset;
char *op = NULL;
unsigned int max_size;
ret = efuse_get_max();
if (ret < 0) {
pr_err("efuse: failed to get max size\n");
return -1;
}
max_size = (unsigned int)ret;
op = kzalloc((sizeof(char)*max_size), GFP_KERNEL);
if (!op) {
pr_err("efuse: failed to allocate memory\n");
ret = -ENOMEM;
return ret;
}
memset(op, 0, max_size);
for (i = 0; i < (count-1); i++)
op[i] = buf[i];
offset = 0;
pr_debug("user area %d bytes data, write...\n", max_size);
ret = efuse_write_usr(op, count, &offset);
if (ret < 0) {
kfree(op);
pr_err("write user area %d bytes data, fail!!!\n", max_size);
return -1;
}
pr_info("user area %d bytes data, write OK\n", ret);
kfree(op);
return count;
}
#endif
static ssize_t amlogic_set_store(struct class *cla,
struct class_attribute *attr, const char *buf, size_t count)
{
int i;
int ret;
char *op = NULL;
if (count != GXB_EFUSE_PATTERN_SIZE) {
pr_err("efuse: bad size, only support size %d!\n",
GXB_EFUSE_PATTERN_SIZE);
return -EINVAL;
}
op = kzalloc((sizeof(char)*count), GFP_KERNEL);
if (!op) {
ret = -ENOMEM;
pr_err("efuse: failed to allocate memory!\n");
return ret;
}
memset(op, 0, count);
for (i = 0; i < count; i++)
op[i] = buf[i];
ret = efuse_amlogic_set(op, count);
kfree(op);
if (ret) {
pr_err("EFUSE pattern programming fail! ret: %d\n", ret);
return -EINVAL;
}
pr_info("EFUSE pattern programming success!\n");
return count;
}
static struct class_attribute efuse_class_attrs[] = {
#ifndef EFUSE_READ_ONLY
/*make the efuse can not be write through sysfs */
__ATTR(userdata, 0700, userdata_show, userdata_write),
#else
__ATTR_RO(userdata),
#endif
__ATTR(mac, 0700, show_mac, store_mac),
__ATTR(mac_bt, 0700, show_mac_bt, store_mac_bt),
__ATTR(mac_wifi, 0700, show_mac_wifi, store_mac_wifi),
__ATTR(usid, 0700, show_usid, store_usid),
__ATTR_WO(amlogic_set),
__ATTR_NULL
};
static struct class efuse_class = {
.name = EFUSE_CLASS_NAME,
.class_attrs = efuse_class_attrs,
};
int get_efusekey_info(struct device_node *np)
{
const phandle *phandle;
struct device_node *np_efusekey, *np_key;
int index;
char *propname;
const char *uname;
int ret;
int size;
phandle = of_get_property(np, "key", NULL);
if (!phandle) {
pr_info("%s:don't find match key\n", __func__);
return -1;
}
if (phandle) {
np_efusekey = of_find_node_by_phandle(be32_to_cpup(phandle));
if (!np_efusekey) {
pr_err("can't find device node key\n");
return -1;
}
}
ret = of_property_read_u32(np_efusekey, "keynum", &efusekeynum);
if (ret) {
pr_err("please config efusekeynum item\n");
return -1;
}
pr_info("efusekeynum: %d\n", efusekeynum);
if (efusekeynum > 0) {
efusekey_infos = kzalloc((sizeof(struct efusekey_info))
*efusekeynum, GFP_KERNEL);
if (!efusekey_infos)
return -1;
}
for (index = 0; index < efusekeynum; index++) {
propname = kasprintf(GFP_KERNEL, "key%d", index);
phandle = of_get_property(np_efusekey, propname, NULL);
if (!phandle) {
pr_err("don't find match %s\n", propname);
goto err;
}
if (phandle) {
np_key = of_find_node_by_phandle(be32_to_cpup(phandle));
if (!np_key) {
pr_err("can't find device node\n");
goto err;
}
}
ret = of_property_read_string(np_key, "keyname", &uname);
if (ret) {
pr_err("please config keyname item\n");
goto err;
}
size = sizeof(efusekey_infos[index].keyname) - 1;
strncpy(efusekey_infos[index].keyname, uname,
strlen(uname) > size ? size:strlen(uname));
ret = of_property_read_u32(np_key, "offset",
&(efusekey_infos[index].offset));
if (ret) {
pr_err("please config offset item\n");
goto err;
}
ret = of_property_read_u32(np_key, "size",
&(efusekey_infos[index].size));
if (ret) {
pr_err("please config size item\n");
goto err;
}
pr_info("efusekeyname: %15s\toffset: %5d\tsize: %5d\n",
efusekey_infos[index].keyname,
efusekey_infos[index].offset,
efusekey_infos[index].size);
kfree(propname);
}
return 0;
err:
kfree(efusekey_infos);
return -1;
}
static int efuse_probe(struct platform_device *pdev)
{
int ret;
struct device *devp;
struct device_node *np = pdev->dev.of_node;
struct clk *efuse_clk;
/* open clk gate HHI_GCLK_MPEG0 bit62*/
efuse_clk = devm_clk_get(&pdev->dev, "efuse_clk");
if (IS_ERR(efuse_clk))
dev_err(&pdev->dev, " open efuse clk gate error!!\n");
else{
ret = clk_prepare_enable(efuse_clk);
if (ret)
dev_err(&pdev->dev, "enable efuse clk gate error!!\n");
}
if (pdev->dev.of_node) {
of_node_get(np);
ret = of_property_read_u32(np, "read_cmd", &efuse_read_cmd);
if (ret) {
dev_err(&pdev->dev, "please config read_cmd item\n");
return -1;
}
ret = of_property_read_u32(np, "write_cmd", &efuse_write_cmd);
if (ret) {
dev_err(&pdev->dev, "please config write_cmd item\n");
return -1;
}
ret = of_property_read_u32(np, "get_max_cmd", &efuse_get_max_cmd);
if (ret) {
dev_err(&pdev->dev, "please config get_max_cmd\n");
return -1;
}
get_efusekey_info(np);
}
ret = alloc_chrdev_region(&efuse_devno, 0, 1, EFUSE_DEVICE_NAME);
if (ret < 0) {
ret = -ENODEV;
goto out;
}
ret = class_register(&efuse_class);
if (ret)
goto error1;
efuse_devp = kmalloc(sizeof(struct efuse_dev_t), GFP_KERNEL);
if (!efuse_devp) {
ret = -ENOMEM;
goto error2;
}
/* connect the file operations with cdev */
cdev_init(&efuse_devp->cdev, &efuse_fops);
efuse_devp->cdev.owner = THIS_MODULE;
/* connect the major/minor number to the cdev */
ret = cdev_add(&efuse_devp->cdev, efuse_devno, 1);
if (ret) {
dev_err(&pdev->dev, "failed to add device\n");
goto error3;
}
devp = device_create(&efuse_class, NULL, efuse_devno, NULL, "efuse");
if (IS_ERR(devp)) {
dev_err(&pdev->dev, "failed to create device node\n");
ret = PTR_ERR(devp);
goto error4;
}
dev_dbg(&pdev->dev, "device %s created\n", EFUSE_DEVICE_NAME);
sharemem_input_base = get_secmon_sharemem_input_base();
sharemem_output_base = get_secmon_sharemem_output_base();
dev_info(&pdev->dev, "probe OK!\n");
return 0;
error4:
cdev_del(&efuse_devp->cdev);
error3:
kfree(efuse_devp);
error2:
/* class_destroy(efuse_clsp); */
class_unregister(&efuse_class);
error1:
unregister_chrdev_region(efuse_devno, 1);
out:
return ret;
}
static int efuse_remove(struct platform_device *pdev)
{
unregister_chrdev_region(efuse_devno, 1);
/* device_destroy(efuse_clsp, efuse_devno); */
device_destroy(&efuse_class, efuse_devno);
cdev_del(&efuse_devp->cdev);
kfree(efuse_devp);
/* class_destroy(efuse_clsp); */
class_unregister(&efuse_class);
return 0;
}
static const struct of_device_id amlogic_efuse_dt_match[] = {
{ .compatible = "amlogic, efuse",
},
{},
};
static struct platform_driver efuse_driver = {
.probe = efuse_probe,
.remove = efuse_remove,
.driver = {
.name = EFUSE_DEVICE_NAME,
.of_match_table = amlogic_efuse_dt_match,
.owner = THIS_MODULE,
},
};
static int __init efuse_init(void)
{
int ret = -1;
ret = platform_driver_register(&efuse_driver);
if (ret != 0) {
pr_err("failed to register efuse driver, error %d\n", ret);
return -ENODEV;
}
return ret;
}
static void __exit efuse_exit(void)
{
platform_driver_unregister(&efuse_driver);
}
module_init(efuse_init);
module_exit(efuse_exit);
MODULE_DESCRIPTION("AMLOGIC eFuse driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Cai Yun <yun.cai@amlogic.com>");