#include <linux/module.h> | |
#include <linux/kernel.h> | |
#include <linux/platform_device.h> | |
#include <linux/of.h> | |
#include <linux/clk-provider.h> | |
#include <linux/clk.h> | |
#include <linux/io.h> | |
#include <linux/slab.h> | |
#include <linux/sched.h> | |
#include <linux/fs.h> | |
#include <linux/delay.h> | |
#include <linux/miscdevice.h> | |
#include <linux/uaccess.h> | |
#include <linux/proc_fs.h> | |
#include <linux/cputype.h> | |
#include <uapi/linux/geu_ioctl.h> | |
#include "asr-geu.h" | |
#define to_asr_fuse(p) container_of(p, struct asr_geu_fuse, fuse_misc) | |
static inline u32 fuse_readl(struct asr_geu_fuse *fuse, u32 offset) | |
{ | |
return readl(fuse->io_base + offset); | |
} | |
static inline void fuse_writel(struct asr_geu_fuse *fuse, u32 offset, | |
u32 val) | |
{ | |
writel(val, fuse->io_base + offset); | |
} | |
static int asr_fuse_wait_for_complete(struct asr_geu_fuse *fuse, | |
unsigned int bit_mask, int action /*0: clr, 1: set*/) | |
{ | |
unsigned int status; | |
unsigned int timeout = 1000000; | |
while(timeout) | |
{ | |
status = fuse_readl(fuse, GEU_FUSE_STATUS); | |
if((action == 1) && (status & bit_mask)) | |
return 0; | |
if((action == 0) && ((status & bit_mask) == 0)) | |
return 0; | |
udelay(1); | |
timeout --; | |
} | |
return 1; | |
} | |
static int geu_software_reset(struct asr_geu_fuse *fuse) | |
{ | |
unsigned int config; | |
config = fuse_readl(fuse, GEU_CONFIG); | |
config |= GEU_FUSE_SOFTWARE_RESET; | |
fuse_writel(fuse, GEU_CONFIG, config); | |
udelay(10); | |
config &= ~GEU_FUSE_SOFTWARE_RESET; | |
fuse_writel(fuse, GEU_CONFIG, config); | |
if(asr_fuse_wait_for_complete(fuse, GEU_FUSE_READY, 1)) | |
return -1; | |
return 0; | |
} | |
static void asr_fuse_read_oem_key_hash(struct asr_geu_fuse *fuse, | |
unsigned int *key_hash, unsigned int len) | |
{ | |
int i; | |
for(i = 0; i < (len / 4); i++) | |
{ | |
key_hash[i] = fuse_readl(fuse, (GEU_FUSE_VAL_OEM_HASH_KEY + (i << 2))); | |
} | |
} | |
/* | |
* tb_mode(trusted boot mode): | |
* 0 - non-trust boot | |
* 1 - trusted boot, normal configuration | |
* 2 - trusted boot, op mode is not burned normally, still work | |
* -1 - op mode is non-trused but oem key hash is burned, wrong mode | |
* -2 - op mode is burned as trusted boot, but oem key hash is not burned | |
* -3 - dis_aib_override and dis_strap_override is not burned, but op mode or oem hey hash is burned, wrong mode | |
* -4 - wrong mode which can be decoded | |
* -5 - the platform doesn't support to check trusted boot mode | |
* Normally, we should get 0 or 1 which stands for non-trusted or trusted mode seperately, | |
* other modes should not happen unless our SW to enable trusted boot doesn't work properly. | |
*/ | |
static int geu_get_trusted_mode(struct asr_geu_fuse *fuse, int *tb_mode) | |
{ | |
unsigned value; | |
struct device *dev; | |
unsigned int fused_key_hash[8]; | |
unsigned int zeros[8]; | |
int oem_key_burned, op_mode, fused_burned; | |
dev = fuse->dev; | |
asr_fuse_read_oem_key_hash(fuse, fused_key_hash, ASR_OEM_KEY_SHA256_SIZE); | |
memset(zeros, 0, sizeof(unsigned int) * 8); | |
if(memcmp(fused_key_hash, zeros, sizeof(unsigned int)*8) == 0) | |
oem_key_burned = 0; | |
else | |
oem_key_burned = 1; | |
if( cpu_is_pxa1826 ()) | |
{ | |
value = fuse_readl(fuse, GEU_FUSE_VAL_APCFG3); | |
fused_burned = (value >> 30) & 3; | |
value = fuse_readl(fuse, GEU_SECURITY_CONFIG); | |
op_mode = (value >> 24) & 3; | |
} else if(cpu_is_asr1802s() || cpu_is_asr1828() || | |
cpu_is_asr1803() || cpu_is_asr1806() || cpu_is_asr1903()) | |
{ | |
value = fuse_readl(fuse, GEU_FUSE_VAL_APCFG3); | |
fused_burned = (value >> 14) & 3; | |
op_mode = (value >> 10) & 3; | |
} else if(cpu_is_asr1901() || cpu_is_asr1906()) | |
{ | |
value = fuse_readl(fuse, GEU_FUSE_VAL_APCFG3); | |
fused_burned = (value >> 30) & 3; | |
op_mode = (value >> 26) & 3; | |
} else { | |
dev_err(dev, "unsupported platform\n"); | |
*tb_mode = -5; | |
goto done; | |
} | |
/* dis_strap_override and dis_aid_strap_override are not burned | |
* usually, fuse of this chip is not burned. | |
*/ | |
if(fused_burned != 0x3) { | |
if( oem_key_burned || (op_mode != 0) ){ | |
dev_warn(dev, "weird fuse status\n"); | |
*tb_mode = -3; //op mode or oem key hash should not be burned | |
} else { | |
dev_warn_ratelimited(dev, "fuse is not burned\n"); | |
*tb_mode = 0; | |
} | |
goto done; | |
} | |
if( oem_key_burned && (op_mode == 2) ) | |
*tb_mode = 1; /* trusted boot is enabled */ | |
else if ( oem_key_burned && (op_mode == 1) ) | |
*tb_mode = -1; //non-trused mode but oem key hash burned | |
else if ( oem_key_burned && (op_mode == 0) ) | |
*tb_mode = 2; /* trusted boot is enabled, but op mode is not burned, can work */ | |
else if( !oem_key_burned && ( (op_mode == 0) || (op_mode == 1) ) ) | |
*tb_mode = 0; /* non-trusted mode */ | |
else if( !oem_key_burned && (op_mode == 2) ) | |
*tb_mode = -2; /* op mode burned as trusted boot, but oem key hash is not burned */ | |
else | |
*tb_mode = -4; /* unknown status */ | |
done: | |
return *tb_mode; | |
} | |
static int geu_get_sim_Lock(struct asr_geu_fuse *fuse, int *sim_lock) | |
{ | |
unsigned value; | |
struct device *dev = fuse->dev; | |
value = fuse_readl(fuse, GEU_FUSE_VAL_APCFG1); | |
if( cpu_is_pxa1826 ()) | |
{ | |
value = (value >> 16) & 1; | |
} else if(cpu_is_asr1802s() || cpu_is_asr1828() || | |
cpu_is_asr1803() || cpu_is_asr1806() || cpu_is_asr1903()) | |
{ | |
value &= 1; | |
} else if (cpu_is_asr1901() || cpu_is_asr1906()) { | |
value &= (1 << 23); /* bit23: sim lock */ | |
value = (value) ? 1:0; | |
} else { | |
dev_err(dev, "sim lock is not supported by this platform\n"); | |
value = -1; | |
} | |
*sim_lock = value; | |
return value; | |
} | |
static int geu_nzas_simlock_burn_enable(struct asr_geu_fuse *fuse) | |
{ | |
int i; | |
unsigned int value; | |
for(i = 0; i < 8; i++) | |
fuse_writel(fuse, GEU_FUSE_PROG_VAL1+i*4, 0); | |
fuse_writel(fuse, GEU_FUSE_PROG_VAL1, (1 << 16)); //sim lock bit | |
fuse_writel(fuse, GEU_CONFIG, 0); | |
value = (1 << 25) | (1 << 17) | (0 << 18); | |
fuse_writel(fuse, GEU_CONFIG, value); | |
value |= (1 << 20); | |
fuse_writel(fuse, GEU_CONFIG, value); | |
udelay(2); | |
value |= (1 << 21); | |
fuse_writel(fuse, GEU_CONFIG, value); | |
udelay(2); | |
value |= (1 << 16); //trigger fuse burning | |
fuse_writel(fuse, GEU_CONFIG, value); | |
return 0; | |
} | |
static int geu_nza3_simlock_burn_enable(struct asr_geu_fuse *fuse) | |
{ | |
int i; | |
unsigned int value; | |
struct device *dev = fuse->dev; | |
fuse_writel(fuse, GEU_REGULATOR_CNT, 0x00f00205); | |
fuse_writel(fuse, GEU_SCLK_DIV_CNTR, 0xe0); | |
udelay(1000); | |
if(geu_software_reset(fuse)) { | |
dev_err(dev, "fuse software reset fails\n"); | |
return -1; | |
} | |
value = (1 << 17); | |
fuse_writel(fuse, GEU_CONFIG, value); //Enable High Voltage | |
msleep(10); | |
for(i = 0; i < 8; i++) | |
fuse_writel(fuse, GEU_FUSE_PROG_VAL1+i*4, 0); | |
fuse_writel(fuse, GEU_FUSE_PROG_VAL1, (1 << 16)); //sim lock bit | |
value |= (7 << 25); //fuse serial clock divider | |
value |= (0 << 18); //block 0 | |
value |= (1 << 14); //Enable Software Fuse Programming | |
value |= (1 << 16); //burn enable | |
fuse_writel(fuse, GEU_CONFIG, value); | |
return 0; | |
} | |
static int geu_kestrel_simlock_burn_enable(struct asr_geu_fuse *fuse) | |
{ | |
int i; | |
unsigned int value; | |
for(i = 0; i < 8; i++) | |
fuse_writel(fuse, GEU_FUSE_PROG_VAL1+i*4, 0); | |
fuse_writel(fuse, GEU_FUSE_PROG_VAL1, (1 << 23)); // bank0_bit23: sim lock bit | |
fuse_writel(fuse, GEU_CONFIG, 0); | |
value = (7 << 25) | (1 << 17) | (0 << 18) | (1 << 14); | |
fuse_writel(fuse, GEU_CONFIG, value); | |
udelay(2); | |
value |= (1 << 16); //trigger fuse burning | |
fuse_writel(fuse, GEU_CONFIG, value); | |
return 0; | |
} | |
static int geu_burn_sim_lock(struct asr_geu_fuse *fuse) | |
{ | |
int i; | |
int ret = -1; | |
unsigned int value, scratch; | |
struct device *dev = fuse->dev; | |
udelay(1000); | |
scratch = fuse_readl(fuse, GEU_CONFIG); | |
if(cpu_is_pxa1826()){ | |
ret = geu_nza3_simlock_burn_enable(fuse); | |
if(ret) | |
goto done; | |
} else if(cpu_is_asr1802s() || cpu_is_asr1828() || | |
cpu_is_asr1803() || cpu_is_asr1806() || cpu_is_asr1903()) | |
{ | |
ret = geu_nzas_simlock_burn_enable(fuse); | |
if (ret) | |
goto done; | |
} else if (cpu_is_asr1901() || cpu_is_asr1906()) { | |
ret = geu_kestrel_simlock_burn_enable(fuse); | |
if (ret) | |
goto done; | |
} else { | |
dev_err(dev, "sim lock is not supported by this platform\n"); | |
goto done; | |
} | |
/* waiting for burning done */ | |
if(asr_fuse_wait_for_complete(fuse, GEU_FUSE_BURN_DONE, 1)) { | |
dev_err(dev, "fuse burning timeout\n"); | |
goto done; | |
} | |
value = fuse_readl(fuse, GEU_CONFIG); | |
value &= ~(1 << 16); //clear burn enable | |
fuse_writel(fuse, GEU_CONFIG, value); | |
if(asr_fuse_wait_for_complete(fuse, GEU_FUSE_BURN_DONE, 0)){ | |
dev_err(dev, "clear fuse burning enable fails\n"); | |
goto done; | |
} | |
if(geu_software_reset(fuse)) { | |
dev_err(dev, "fuse software reset fails\n"); | |
goto done; | |
} | |
geu_get_sim_Lock(fuse, &value); | |
if(value != 1) { | |
dev_err(dev, "sim lock fails\n"); | |
goto done; | |
} | |
done: | |
fuse_writel(fuse, GEU_CONFIG, scratch); | |
for(i = 0; i < 8; i++) | |
fuse_writel(fuse, GEU_FUSE_PROG_VAL1+i*4, 0); | |
return ret; | |
} | |
static DEFINE_MUTEX(sim_lock_mutex); | |
static DEFINE_MUTEX(tb_mode_mutex); | |
static ssize_t sim_lock_read(struct device *dev, | |
struct device_attribute *attr, char *buf) | |
{ | |
int len = 0; | |
unsigned int sim_lock; | |
struct miscdevice *miscdev = dev_get_drvdata(dev); | |
struct asr_geu_fuse *fuse = to_asr_fuse(miscdev); | |
struct asr_geu_dev *geu_dd = dev_get_drvdata(fuse->dev); | |
struct asr_geu_ops *geu_ops = geu_dd->geu_ops; | |
mutex_lock(&sim_lock_mutex); | |
geu_ops->dev_get(geu_dd); | |
geu_get_sim_Lock(fuse, &sim_lock); | |
len = sprintf(buf, "%x\n", sim_lock); | |
geu_ops->dev_put(geu_dd); | |
mutex_unlock(&sim_lock_mutex); | |
return (ssize_t)len; | |
} | |
static ssize_t sim_lock_write(struct device *dev, | |
struct device_attribute *attr, const char *buf, size_t size) | |
{ | |
int enable, ret; | |
struct miscdevice *miscdev = dev_get_drvdata(dev); | |
struct asr_geu_fuse *fuse = to_asr_fuse(miscdev); | |
struct asr_geu_dev *geu_dd = dev_get_drvdata(fuse->dev); | |
struct asr_geu_ops *geu_ops = geu_dd->geu_ops; | |
ret = sscanf(buf, "%d", &enable); | |
if(ret != 1 || enable != 1) { | |
return size; | |
} | |
mutex_lock(&sim_lock_mutex); | |
geu_ops->dev_get(geu_dd); | |
ret = geu_burn_sim_lock(fuse); | |
if (ret) | |
dev_info(dev, "burn sim lock fails\n"); | |
else | |
dev_info(dev, "burn sim lock done\n"); | |
geu_ops->dev_put(geu_dd); | |
mutex_unlock(&sim_lock_mutex); | |
return size; | |
} | |
static ssize_t tb_mode_read(struct device *dev, | |
struct device_attribute *attr, char *buf) | |
{ | |
int len = 0, i; | |
int tb_mode; | |
struct miscdevice *miscdev; | |
struct asr_geu_dev *geu_dd; | |
struct asr_geu_ops *geu_ops; | |
struct asr_geu_fuse *fuse; | |
unsigned int fused_key_hash[8]; | |
miscdev = dev_get_drvdata(dev); | |
fuse = to_asr_fuse(miscdev); | |
geu_dd = dev_get_drvdata(fuse->dev); | |
geu_ops = geu_dd->geu_ops; | |
mutex_lock(&tb_mode_mutex); | |
geu_ops->dev_get(geu_dd); | |
geu_get_trusted_mode(fuse, &tb_mode); | |
asr_fuse_read_oem_key_hash(fuse, fused_key_hash, ASR_OEM_KEY_SHA256_SIZE); | |
len = sprintf(buf, "Trusted boot mode: %d\n", tb_mode); | |
len += sprintf(buf+len, "OEM Key Hash(High --> Low):\n"); | |
for (i = 7; i >= 0; i--) | |
len += sprintf(buf+len, "%08x ", fused_key_hash[i]); | |
len += sprintf(buf+len, "\n"); | |
geu_ops->dev_put(geu_dd); | |
mutex_unlock(&tb_mode_mutex); | |
return (ssize_t)len; | |
} | |
static DEVICE_ATTR(simlock, S_IRUGO | S_IWUSR, sim_lock_read, sim_lock_write); | |
static DEVICE_ATTR(tb_mode, S_IRUGO, tb_mode_read, NULL); | |
static struct attribute *fuse_dev_attrs[] = { | |
&dev_attr_simlock.attr, | |
&dev_attr_tb_mode.attr, | |
NULL | |
}; | |
ATTRIBUTE_GROUPS(fuse_dev); | |
static int asr_fuse_iotcl_read_key_hash(struct asr_geu_fuse *fuse, | |
void __user *key_hash, unsigned int len) | |
{ | |
unsigned int oem_key_hash[8]; | |
if (!access_ok(key_hash, len)) | |
return -EFAULT; | |
asr_fuse_read_oem_key_hash(fuse, oem_key_hash, ASR_OEM_KEY_SHA256_SIZE); | |
if (copy_to_user(key_hash, oem_key_hash, len)) { | |
return -EFAULT; | |
} | |
return 0; | |
} | |
static int asr_fuse_iotcl_read_life_cycle(struct asr_geu_fuse *fuse, | |
void __user *life_cyle, unsigned int len) | |
{ | |
unsigned int asr_life_cyle[2]; | |
if (!access_ok(life_cyle, 2 * sizeof(unsigned int))) | |
return -EFAULT; | |
#if !defined(CONFIG_CPU_ASR1901) | |
if (cpu_is_asr1802s() || cpu_is_asr1828() || | |
cpu_is_asr1803() || cpu_is_asr1806() || cpu_is_asr1903()) | |
{ | |
asr_life_cyle[0] = fuse_readl(fuse, GEU_FUSE_BANK0_192TO207) & 0x7FFF; | |
} | |
#else | |
if (cpu_is_asr1901() || cpu_is_asr1906()) | |
{ | |
asr_life_cyle[0] = fuse_readl(fuse, GEU_KSTR_BANK6_LCS) & 0xFFF; | |
} | |
#endif | |
else { | |
asr_life_cyle[0] = 0; | |
} | |
asr_life_cyle[1] = 0; | |
if (copy_to_user(life_cyle, asr_life_cyle, sizeof(unsigned int) * 2)) { | |
return -EFAULT; | |
} | |
return 0; | |
} | |
static int asr_fuse_iotcl_read_oem_uid(struct asr_geu_fuse *fuse, | |
void __user *oem_uid_hi, void __user *oem_uid_lo) | |
{ | |
unsigned int oem_uid[2]; | |
if (!access_ok(oem_uid_hi, sizeof(unsigned int)) || | |
!access_ok(oem_uid_lo, sizeof(unsigned int))) | |
return -EFAULT; | |
#if !defined(CONFIG_CPU_ASR1901) | |
if (cpu_is_asr1802s() || cpu_is_asr1828() || | |
cpu_is_asr1803() || cpu_is_asr1806() || cpu_is_asr1903()) | |
{ | |
oem_uid[0] = fuse_readl(fuse, GEU_FUSE_VAL_OEM_UID_H); | |
oem_uid[1] = fuse_readl(fuse, GEU_FUSE_VAL_OEM_UID_L); | |
} else if (cpu_is_asr1903()) | |
{ | |
oem_uid[0] = fuse_readl(fuse, GEU_FUSE_VAL_OEM_UID_H_LAPW); | |
oem_uid[1] = fuse_readl(fuse, GEU_FUSE_VAL_OEM_UID_L_LAPW); | |
} | |
#else | |
if (cpu_is_asr1901() || cpu_is_asr1906()) | |
{ | |
oem_uid[0] = fuse_readl(fuse, GEU_FUSE_VAL_OEM_UID_H); | |
oem_uid[1] = fuse_readl(fuse, GEU_FUSE_VAL_OEM_UID_L); | |
} | |
#endif | |
else { | |
oem_uid[0] = oem_uid[1] = 0; | |
} | |
if (copy_to_user(oem_uid_hi, &oem_uid[0], sizeof(unsigned int))) | |
return -EFAULT; | |
if (copy_to_user(oem_uid_lo, &oem_uid[1], sizeof(unsigned int))) | |
return -EFAULT; | |
return 0; | |
} | |
static int asr_fuse_iotcl_burn_simlock(struct asr_geu_fuse *fuse, | |
void __user *sim_lock) | |
{ | |
int simlock; | |
struct device *dev = fuse->dev; | |
if (geu_burn_sim_lock(fuse)) { | |
dev_err(dev, "burn sim lock fails\n"); | |
return -EFAULT; | |
} else { | |
geu_get_sim_Lock(fuse, &simlock); | |
if(copy_to_user(sim_lock, (void *)(&simlock), 4)) { | |
dev_err(dev, "get sim lock error\n"); | |
return -EFAULT; | |
} | |
} | |
return 0; | |
} | |
static int asr_fuse_iotcl_get_simlock(struct asr_geu_fuse *fuse, | |
void __user *sim_lock) | |
{ | |
int simlock; | |
struct device *dev = fuse->dev; | |
geu_get_sim_Lock(fuse, &simlock); | |
if(copy_to_user(sim_lock, (void *)(&simlock), 4)) { | |
dev_err(dev, "get sim lock error\n"); | |
return -EFAULT; | |
} | |
return 0; | |
} | |
static int asr_fuse_iotcl_get_trust_mode(struct asr_geu_fuse *fuse, | |
void __user *tb_mode) | |
{ | |
int trusted_mode; | |
struct device *dev = fuse->dev; | |
geu_get_trusted_mode(fuse, &trusted_mode); | |
if(copy_to_user(tb_mode, (void *)(&trusted_mode), 4)) { | |
dev_err(dev, "get trusted boot error\n"); | |
return -EFAULT; | |
} | |
return 0; | |
} | |
static long asr_fuse_ioctl(struct file *file, u_int cmd, u_long arg) | |
{ | |
int ret = 0; | |
struct miscdevice *miscdev; | |
struct asr_geu_dev *geu_dd; | |
struct asr_geu_fuse *fuse; | |
struct asr_geu_ops *geu_ops; | |
struct device *dev; | |
struct geu_arg geu_arg; | |
miscdev = file->private_data; | |
fuse = to_asr_fuse(miscdev); | |
geu_dd = dev_get_drvdata(fuse->dev); | |
geu_ops = geu_dd->geu_ops; | |
dev = fuse->dev; | |
if (copy_from_user(&geu_arg, (void __user *)arg, sizeof(geu_arg))) | |
return -EFAULT; | |
geu_ops->dev_get(geu_dd); | |
switch (cmd) { | |
case GEU_READ_OEMHASHKEY: | |
ret = asr_fuse_iotcl_read_key_hash(fuse, (void __user *)geu_arg.arg0, | |
geu_arg.arg1); | |
break; | |
case GEU_READ_LIFECYCLE: | |
ret = asr_fuse_iotcl_read_life_cycle(fuse, (void __user *)geu_arg.arg0, | |
geu_arg.arg1); | |
break; | |
case GEU_READ_OEM_UUID: | |
ret = asr_fuse_iotcl_read_oem_uid(fuse, (void __user *)geu_arg.arg0, | |
(void __user *)geu_arg.arg1); | |
break; | |
case GEU_BURN_SIM_LOCK: | |
ret = asr_fuse_iotcl_burn_simlock(fuse, (void __user *)arg); | |
break; | |
case GEU_GET_SIM_LOCK: | |
ret = asr_fuse_iotcl_get_simlock(fuse, (void __user *)arg); | |
break; | |
case GEU_GET_TRUST_MODE: | |
ret = asr_fuse_iotcl_get_trust_mode(fuse, (void __user *)arg); | |
break; | |
default: | |
dev_err(dev, "asr geu: iotcl invald command %x\n", cmd); | |
ret = -EINVAL; | |
} | |
geu_ops->dev_put(geu_dd); | |
return ret; | |
} | |
static int asr_fuse_open(struct inode *inode, struct file *file) | |
{ | |
return 0; | |
} | |
static int asr_fuse_close(struct inode *inode, struct file *file) | |
{ | |
return 0; | |
} | |
static const struct file_operations asr_fuse_fops = { | |
.owner = THIS_MODULE, | |
.open = asr_fuse_open, | |
.release = asr_fuse_close, | |
.unlocked_ioctl = asr_fuse_ioctl, | |
}; | |
int asr_geu_fuse_register(struct asr_geu_dev *geu_dd) | |
{ | |
int ret = 0; | |
struct asr_geu_fuse *pfuse; | |
struct miscdevice *misc; | |
struct device *dev = geu_dd->dev; | |
pfuse = &geu_dd->asr_fuse; | |
misc = &pfuse->fuse_misc; | |
misc->name = "geu"; /* to be compatiable with old apps */ | |
misc->minor = MISC_DYNAMIC_MINOR; | |
misc->fops = &asr_fuse_fops; | |
misc->this_device = NULL; | |
misc->groups = fuse_dev_groups; | |
pfuse->io_base = geu_dd->io_base; | |
pfuse->dev = geu_dd->dev; | |
pfuse->geu_dd = geu_dd; | |
/* register the device */ | |
ret = misc_register(misc); | |
if (ret < 0) { | |
dev_err(dev, | |
"asr fuse: unable to register device node /dev/geu\n"); | |
return ret; | |
} | |
return 0; | |
} | |
EXPORT_SYMBOL_GPL(asr_geu_fuse_register); | |
int asr_geu_fuse_unregister(struct asr_geu_dev *geu_dd) | |
{ | |
struct miscdevice *miscdev; | |
miscdev = &geu_dd->asr_fuse.fuse_misc; | |
misc_deregister(miscdev); | |
device_remove_file(geu_dd->dev, &dev_attr_simlock); | |
device_remove_file(geu_dd->dev, &dev_attr_tb_mode); | |
return 0; | |
} | |
EXPORT_SYMBOL_GPL(asr_geu_fuse_unregister); | |
MODULE_LICENSE("GPL"); | |
MODULE_AUTHOR("Yu Zhang <yuzhang@asrmicro.com>"); | |
MODULE_DESCRIPTION("ASR eFuse driver"); |