blob: 55b4e4ca635b9f2ee04d4d658e1aa8537259eb6b [file] [log] [blame]
/*
* drivers/amlogic/efuse/efuse.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/of_address.h>
#include <linux/amlogic/secmon.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include "efuse.h"
#include "efuse_regs.h"
#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 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 *efuse_base;
struct clk *efuse_clk;
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 efuseinfo_item_t 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;
}
if (efuse_getinfo_byTitle(info.title, &info) < 0)
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;
}
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) {
/* pr_info("memory not enough\n"); */
return -ENOMEM;
}
local_count = efuse_read_item(local_buf, count, ppos);
if (local_count < 0) {
ret = -EFAULT;
goto error_exit;
}
if (copy_to_user((void *)buf, (void *)local_buf, local_count)) {
ret = -EFAULT;
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)
{
unsigned int pos = (unsigned int)*ppos;
int ret, size;
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;
ret = check_if_efused(pos, count);
if (ret) {
pr_info("check if has been efused failed\n");
if (ret == 1)
return -EROFS;
else if (ret < 0)
return ret;
}
contents = kzalloc(sizeof(unsigned char)*EFUSE_BYTES, GFP_KERNEL);
if (!contents) {
/* pr_info("memory not enough\n"); */
return -ENOMEM;
}
size = sizeof(contents);
memset(contents, 0, size);
if (copy_from_user(contents, buf, count)) {
/*if (contents)*/
kfree(contents);
return -EFAULT;
}
if (efuse_write_item(contents, count, ppos) < 0) {
kfree(contents);
return -EFAULT;
}
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,
};
/* Sysfs Files */
ssize_t efuse_attr_store(char *name, const char *buf, size_t count)
{
#ifndef EFUSE_READ_ONLY
char *local_buf;
ssize_t ret;
int i;
const unsigned char *s;
struct efuseinfo_item_t info;
unsigned int uint_val;
if (efuse_getinfo_byTitle(name, &info) < 0) {
pr_err("%s is not found\n", name);
return -EFAULT;
}
if (count < info.data_len) {
pr_info("%s:item: %s: size: %d too few arguments to store\n",
__func__, name, info.data_len);
count = info.data_len + 1;
}
if (check_if_efused(info.offset, info.data_len)) {
pr_err("%s error!data_verify failed!\n", __func__);
return -1;
}
local_buf = kzalloc(sizeof(char)*(count), GFP_KERNEL);
memcpy(local_buf, buf, (strlen(buf) < info.data_len)
? strlen(buf):count);
s = local_buf;
strim(local_buf);
/* only support separated with whitespace or colon
* eg. A1:B2:C3 or D4 E5 F6 in hexadecimal
*/
if ((strstr(s, ":") != NULL) || (strstr(s, " ") != NULL)) {
for (i = 0; i < info.data_len; i++) {
while ((!strncmp(s, ":", 1)) ||
!strncmp(s, " ", 1))
s++;
if (!*s) {
local_buf[i] = 0;
continue;
}
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;
}
pr_debug("local_buf[%d]: 0x%x\n",
i, local_buf[i]);
}
} else {/* only support separated with whitespace */
for (i = 0; i < info.data_len; i++) {
uint_val = 0;
if (!*s) {
local_buf[i] = 0;
continue;
}
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;
}
pr_debug("local_buf[%d]: 0x%x\n",
i, local_buf[i]);
}
}
ret = efuse_write_item(local_buf, info.data_len,
(loff_t *)&(info.offset));
if (ret == -1) {
pr_err("ERROR: efuse write user data fail!\n");
goto error_exit;
}
if (ret != info.data_len)
pr_err("ERROR: write %zd byte(s) not %d byte(s) data\n",
ret, info.data_len);
pr_info("efuse write %zd data OK\n", ret);
error_exit:
kfree(local_buf);
return ret;
#else
pr_err("no permission to write!!\n");
return -1;
#endif
}
ssize_t efuse_attr_show(char *name, char *buf)
{
char *local_buf;
ssize_t ret;
int i;
struct efuseinfo_item_t info;
if (efuse_getinfo_byTitle(name, &info) < 0) {
pr_err("%s is not found\n", name);
return -EFAULT;
}
local_buf = kzalloc(sizeof(char)*(info.data_len), GFP_KERNEL);
memset(local_buf, 0, info.data_len);
ret = efuse_read_item(local_buf, info.data_len,
(loff_t *)&(info.offset));
if (ret == -1) {
pr_err("ERROR: efuse read user data fail!\n");
goto error_exit;
}
if (ret != info.data_len)
pr_err("ERROR: read %zd byte(s) not %d byte(s) data\n",
ret, info.data_len);
for (i = 0; i < info.data_len; i++)
buf[i] = local_buf[i];
error_exit:
kfree(local_buf);
return ret;
}
#define DEFINE_EFUE_SHOW_ATTR(name) \
static ssize_t show_##name(struct class *cla, \
struct class_attribute *attr, \
char *buf) \
{ \
ssize_t ret; \
int i = 0; \
\
ret = efuse_attr_show(#name, buf); \
if (ret > 0) {\
pr_info("efuse read data\n"); \
for (; i < ret; i++) \
pr_info("%02x%s", buf[i], ((i+1)%16 == 0)?"\n":":"); \
pr_info("\n"); \
} \
return ret; \
}
DEFINE_EFUE_SHOW_ATTR(version)
DEFINE_EFUE_SHOW_ATTR(mac)
DEFINE_EFUE_SHOW_ATTR(mac_bt)
DEFINE_EFUE_SHOW_ATTR(mac_wifi)
DEFINE_EFUE_SHOW_ATTR(usid)
#define DEFINE_EFUE_STORE_ATTR(name) \
static ssize_t store_##name(struct class *cla, \
struct class_attribute *attr, \
const char *buf, \
size_t count) \
{ \
ssize_t ret; \
\
ret = efuse_attr_store(#name, buf, count); \
return ret; \
}
#ifdef CONFIG_AMLOGIC_EFUSE_WRITE_VERSION_PERMIT
DEFINE_EFUE_STORE_ATTR(version)
#endif
//DEFINE_EFUE_STORE_ATTR(mac)
//DEFINE_EFUE_STORE_ATTR(mac_bt)
//DEFINE_EFUE_STORE_ATTR(mac_wifi)
DEFINE_EFUE_STORE_ATTR(usid)
static struct class_attribute efuse_class_attrs[] = {
#ifdef CONFIG_AMLOGIC_EFUSE_WRITE_VERSION_PERMIT
__ATTR(version, 0700, show_version, store_version),
#else
__ATTR(version, 0500, show_version, NULL),
#endif
__ATTR(mac, 0500, show_mac, NULL),
__ATTR(mac_bt, 0500, show_mac_bt, NULL),
__ATTR(mac_wifi, 0500, show_mac_wifi, NULL),
__ATTR(usid, 0700, show_usid, store_usid),
__ATTR_NULL
};
static struct class efuse_class = {
.name = EFUSE_CLASS_NAME,
.class_attrs = efuse_class_attrs,
};
static int efuse_probe(struct platform_device *pdev)
{
int ret;
struct device *devp;
struct device_node *np = pdev->dev.of_node;
ret = alloc_chrdev_region(&efuse_devno, 0, 1, EFUSE_DEVICE_NAME);
if (ret < 0) {
dev_err(&pdev->dev, "efuse: failed to allocate major number\n");
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) {
/* dev_err(&pdev->dev, "efuse: failed to allocate memory\n"); */
ret = -ENOMEM;
goto error2;
}
efuse_base = of_iomap(pdev->dev.of_node, 0);
if (!efuse_base) {
pr_err("%s: Unable to map efuse base\n", __func__);
ret = -ENXIO;
goto error3;
}
/* 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, "efuse: failed to add device\n");
goto error4;
}
devp = device_create(&efuse_class, NULL, efuse_devno, NULL, "efuse");
if (IS_ERR(devp)) {
dev_err(&pdev->dev, "efuse: failed to create device node\n");
ret = PTR_ERR(devp);
goto error5;
}
dev_dbg(&pdev->dev, "device %s created\n", EFUSE_DEVICE_NAME);
if (pdev->dev.of_node) {
of_node_get(np);
#if 0
ret = of_property_read_u32(np, "plat-pos", &pos);
if (ret) {
dev_err(&pdev->dev, "please config plat-pos item\n");
return -1;
}
ret = of_property_read_u32(np, "plat-count", &count);
if (ret) {
dev_err(&pdev->dev, "please config plat-count item\n");
return -1;
}
ret = of_property_read_u32(np, "usid-min", &usid_min);
if (ret) {
dev_err(&pdev->dev, "please config usid-min item\n");
return -1;
}
ret = of_property_read_u32(np, "usid-max", &usid_max);
if (ret) {
dev_err(&pdev->dev, "please config usid-max item\n");
return -1;
}
#endif
/* todo reserved for user id <usid-min ~ usid max> */
}
/* 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");
}
dev_info(&pdev->dev, "probe ok!\n");
return 0;
error5:
cdev_del(&efuse_devp->cdev);
error4:
kfree(efuse_devp);
error3:
iounmap(efuse_base);
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);
iounmap(efuse_base);
/* 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("Yun Cai <yun.cai@amlogic.com>");