blob: 8d11fb013a87bca5579eed6b6e3d935ae28d00b5 [file] [log] [blame]
/*
* drivers/amlogic/efuse/efuse_hw.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/sched.h>
#include <linux/platform_device.h>
#include <linux/amlogic/iomap.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#ifndef CONFIG_ARM64
#include <asm/opcodes-sec.h>
#endif
#include <linux/amlogic/meson-secure.h>
#include <linux/amlogic/efuse.h>
#include "efuse_regs.h"
#include "efuse.h"
int efuse_active_version = -1;
#ifdef EFUSE_DEBUG
static unsigned long efuse_test_buf_32[EFUSE_DWORDS] = {0};
static unsigned char *efuse_test_buf_8 = (unsigned char *)efuse_test_buf_32;
static void __efuse_write_byte_debug(unsigned long addr, unsigned char data)
{
efuse_test_buf_8[addr] = data;
}
static void __efuse_read_dword_debug(unsigned long addr, unsigned long *data)
{
*data = efuse_test_buf_32[addr >> 2];
}
#endif
#ifndef EFUSE_DEBUG
static void __efuse_write_byte(unsigned long addr, unsigned long data)
{
unsigned long auto_wr_is_enabled = 0;
/* cpu after M8 */
unsigned int byte_sel;
clk_prepare_enable(efuse_clk);
//set efuse PD=0
aml_set_reg32_bits(P_EFUSE_CNTL1, 0, 27, 1);
if (readl((void *) P_EFUSE_CNTL1) & (1 << CNTL1_AUTO_WR_ENABLE_BIT)) {
auto_wr_is_enabled = 1;
} else {
/* temporarily enable Write mode */
aml_set_reg32_bits(P_EFUSE_CNTL1, CNTL1_AUTO_WR_ENABLE_ON,
CNTL1_AUTO_WR_ENABLE_BIT, CNTL1_AUTO_WR_ENABLE_SIZE);
}
/* cpu after M8 */
byte_sel = addr % 4;
addr = addr / 4;
/* write the address */
aml_set_reg32_bits(P_EFUSE_CNTL1, addr,
CNTL1_BYTE_ADDR_BIT, CNTL1_BYTE_ADDR_SIZE);
//auto write byte select (0-3), for m8
aml_set_reg32_bits(P_EFUSE_CNTL3, byte_sel,
CNTL1_AUTO_WR_START_BIT, 2);
/* set starting byte address */
aml_set_reg32_bits(P_EFUSE_CNTL1, CNTL1_BYTE_ADDR_SET_ON,
CNTL1_BYTE_ADDR_SET_BIT, CNTL1_BYTE_ADDR_SET_SIZE);
aml_set_reg32_bits(P_EFUSE_CNTL1, CNTL1_BYTE_ADDR_SET_OFF,
CNTL1_BYTE_ADDR_SET_BIT, CNTL1_BYTE_ADDR_SET_SIZE);
/* write the byte */
aml_set_reg32_bits(P_EFUSE_CNTL1, data,
CNTL1_BYTE_WR_DATA_BIT, CNTL1_BYTE_WR_DATA_SIZE);
/* start the write process */
aml_set_reg32_bits(P_EFUSE_CNTL1, CNTL1_AUTO_WR_START_ON,
CNTL1_AUTO_WR_START_BIT, CNTL1_AUTO_WR_START_SIZE);
aml_set_reg32_bits(P_EFUSE_CNTL1, CNTL1_AUTO_WR_START_OFF,
CNTL1_AUTO_WR_START_BIT, CNTL1_AUTO_WR_START_SIZE);
/* dummy read */
readl((void *) P_EFUSE_CNTL1);
while (readl((void *)P_EFUSE_CNTL1) & (1 << CNTL1_AUTO_WR_BUSY_BIT))
udelay(1);
/* if auto write wasn't enabled and we enabled it,
* then disable it upon exit
*/
if (auto_wr_is_enabled == 0) {
aml_set_reg32_bits(P_EFUSE_CNTL1,
CNTL1_AUTO_WR_ENABLE_OFF,
CNTL1_AUTO_WR_ENABLE_BIT,
CNTL1_AUTO_WR_ENABLE_SIZE);
}
//set efuse PD=1
aml_set_reg32_bits(P_EFUSE_CNTL1, 1, 27, 1);
clk_disable_unprepare(efuse_clk);
pr_debug("__efuse_write_byte: addr=0x%lx, data=0x%lx\n", addr, data);
}
static void __efuse_read_dword(unsigned long addr, unsigned long *data)
{
//unsigned long auto_rd_is_enabled = 0;
//if( aml_read_reg32(EFUSE_CNTL1) & ( 1 << CNTL1_AUTO_RD_ENABLE_BIT ) ){
// auto_rd_is_enabled = 1;
//} else {
/* temporarily enable Read mode */
//aml_set_reg32_bits( P_EFUSE_CNTL1, CNTL1_AUTO_RD_ENABLE_ON,
// CNTL1_AUTO_RD_ENABLE_BIT, CNTL1_AUTO_RD_ENABLE_SIZE );
//}
//set efuse PD=0
aml_set_reg32_bits(P_EFUSE_CNTL1, 0, 27, 1);
/* cpu after M8 */
addr = addr / 4; //each address have 4 bytes in m8
/* write the address */
aml_set_reg32_bits(P_EFUSE_CNTL1, addr,
CNTL1_BYTE_ADDR_BIT, CNTL1_BYTE_ADDR_SIZE);
/* set starting byte address */
aml_set_reg32_bits(P_EFUSE_CNTL1, CNTL1_BYTE_ADDR_SET_ON,
CNTL1_BYTE_ADDR_SET_BIT, CNTL1_BYTE_ADDR_SET_SIZE);
aml_set_reg32_bits(P_EFUSE_CNTL1, CNTL1_BYTE_ADDR_SET_OFF,
CNTL1_BYTE_ADDR_SET_BIT, CNTL1_BYTE_ADDR_SET_SIZE);
/* start the read process */
aml_set_reg32_bits(P_EFUSE_CNTL1, CNTL1_AUTO_WR_START_ON,
CNTL1_AUTO_RD_START_BIT, CNTL1_AUTO_RD_START_SIZE);
aml_set_reg32_bits(P_EFUSE_CNTL1, CNTL1_AUTO_WR_START_OFF,
CNTL1_AUTO_RD_START_BIT, CNTL1_AUTO_RD_START_SIZE);
/* dummy read */
readl((void *)P_EFUSE_CNTL1);
while (readl((void *)P_EFUSE_CNTL1) & (1 << CNTL1_AUTO_RD_BUSY_BIT))
udelay(1);
/* read the 32-bits value */
(*data) = readl((void *)P_EFUSE_CNTL2);
//set efuse PD=1
aml_set_reg32_bits(P_EFUSE_CNTL1, 1, 27, 1);
/* if auto read wasn't enabled and we enabled it,
* then disable it upon exit
*/
//if ( auto_rd_is_enabled == 0 ){
//aml_set_reg32_bits( P_EFUSE_CNTL1, CNTL1_AUTO_RD_ENABLE_OFF,
// CNTL1_AUTO_RD_ENABLE_BIT, CNTL1_AUTO_RD_ENABLE_SIZE );
//}
pr_debug("__efuse_read_dword: addr=%ld, data=0x%lx\n", addr, *data);
}
#endif
static ssize_t __efuse_read(char *buf, size_t count, loff_t *ppos)
{
unsigned long *contents = kzalloc(sizeof(unsigned long)*EFUSE_DWORDS,
GFP_KERNEL);
unsigned int pos = *ppos;
unsigned long *pdw;
char *tmp_p;
/*pos may not align to 4*/
unsigned int dwsize = (count + 3 + pos%4) >> 2;
struct efuse_hal_api_arg arg;
unsigned long retcnt;
int ret;
if (!contents) {
pr_info("memory not enough\n");
return -ENOMEM;
}
if (pos >= EFUSE_BYTES)
return 0;
if (count > EFUSE_BYTES - pos)
count = EFUSE_BYTES - pos;
if (count > EFUSE_BYTES)
return -EFAULT;
if (!meson_secure_enabled()) {
clk_prepare_enable(efuse_clk);
aml_set_reg32_bits(P_EFUSE_CNTL1, CNTL1_AUTO_RD_ENABLE_ON,
CNTL1_AUTO_RD_ENABLE_BIT, CNTL1_AUTO_RD_ENABLE_SIZE);
for (pdw = contents + pos/4;
dwsize-- > 0 && pos < EFUSE_BYTES;
pos += 4, ++pdw) {
#ifdef EFUSE_DEBUG
__efuse_read_dword_debug(pos, pdw);
#else
/* if pos does not align to 4, __efuse_read_dword
* read from next dword, so, discount this un-aligned
* partition
*/
__efuse_read_dword((pos - pos%4), pdw);
#endif
}
aml_set_reg32_bits(P_EFUSE_CNTL1,
CNTL1_AUTO_RD_ENABLE_OFF,
CNTL1_AUTO_RD_ENABLE_BIT,
CNTL1_AUTO_RD_ENABLE_SIZE);
clk_disable_unprepare(efuse_clk);
tmp_p = (char *)contents;
tmp_p += *ppos;
memcpy(buf, tmp_p, count);
*ppos += count;
} else {
arg.cmd = EFUSE_HAL_API_READ;
arg.offset = pos;
arg.size = count;
arg.buffer = virt_to_phys(contents);
arg.retcnt = virt_to_phys(&retcnt);
ret = meson_trustzone_efuse((void *)&arg);
if (ret == 0) {
count = retcnt;
*ppos += retcnt;
memcpy(buf, contents, retcnt);
} else
count = 0;
}
/*if (contents)*/
kfree(contents);
return count;
}
static ssize_t __efuse_write(const char *buf, size_t count, loff_t *ppos)
{
unsigned int pos = *ppos;
unsigned char *pc;
struct efuse_hal_api_arg arg;
unsigned int retcnt;
int ret;
if (pos >= EFUSE_BYTES)
return 0; /* Past EOF */
if (count > EFUSE_BYTES - pos)
count = EFUSE_BYTES - pos;
if (count > EFUSE_BYTES)
return -EFAULT;
if (!meson_secure_enabled()) {
for (pc = (char *)buf; count--; ++pos, ++pc)
#ifdef EFUSE_DEBUG
__efuse_write_byte_debug(pos, *pc);
#else
__efuse_write_byte(pos, *pc);
#endif
*ppos = pos;
ret = (const char *)pc - buf;
} else {
arg.cmd = EFUSE_HAL_API_WRITE;
arg.offset = pos;
arg.size = count;
arg.buffer = virt_to_phys(buf);
arg.retcnt = virt_to_phys(&retcnt);
ret = meson_trustzone_efuse((void *)&arg);
if (ret == 0) {
*ppos = pos+retcnt;
ret = retcnt;
} else
ret = 0;
}
return ret;
}
ssize_t aml__efuse_read(char *buf, size_t count, loff_t *ppos)
{
return __efuse_read(buf, count, ppos);
}
ssize_t aml__efuse_write(const char *buf, size_t count, loff_t *ppos)
{
return __efuse_write(buf, count, ppos);
}
/* ================================================ */
/* #define SOC_CHIP_TYPE_TEST */
#ifdef SOC_CHIP_TYPE_TEST
static char *soc_chip[] = {
{"efuse soc chip m8baby"},
{"efuse soc chip unknown"},
};
#endif
struct efuse_chip_identify_t {
unsigned int chiphw_mver;
unsigned int chiphw_subver;
unsigned int chiphw_thirdver;
enum efuse_socchip_type_e type;
};
static const struct efuse_chip_identify_t efuse_chip_hw_info[] = {
{
.chiphw_mver = 27,
.chiphw_subver = 0,
.chiphw_thirdver = 0,
.type = EFUSE_SOC_CHIP_M8BABY
},
};
#define EFUSE_CHIP_HW_INFO_NUM (sizeof(efuse_chip_hw_info)/ \
sizeof(efuse_chip_hw_info[0]))
enum efuse_socchip_type_e efuse_get_socchip_type(void)
{
enum efuse_socchip_type_e type;
unsigned int regval;
int i;
struct efuse_chip_identify_t *pinfo =
(struct efuse_chip_identify_t *)&efuse_chip_hw_info[0];
type = EFUSE_SOC_CHIP_UNKNOWN;
regval = aml_read_cbus(ASSIST_HW_REV);
/* pr_info("chip ASSIST_HW_REV reg:%d\n",regval); */
for (i = 0; i < EFUSE_CHIP_HW_INFO_NUM; i++) {
if (pinfo->chiphw_mver == regval) {
type = pinfo->type;
break;
}
pinfo++;
}
#ifdef SOC_CHIP_TYPE_TEST
pr_info("%s\n", soc_chip[type]);
#endif
return type;
}
static int efuse_checkversion(char *buf)
{
enum efuse_socchip_type_e soc_type;
int i;
int ver = buf[0];
for (i = 0; i < efuseinfo_num; i++) {
if (efuseinfo[i].version == ver) {
soc_type = efuse_get_socchip_type();
switch (soc_type) {
case EFUSE_SOC_CHIP_M8BABY:
if (ver != M8_EFUSE_VERSION_SERIALNUM_V1)
ver = -1;
break;
case EFUSE_SOC_CHIP_UNKNOWN:
default:
pr_info("%s:%d soc is unknown\n",
__func__, __LINE__);
ver = -1;
break;
}
return ver;
}
}
return -1;
}
static int efuse_set_versioninfo(struct efuseinfo_item_t *info)
{
int ret = -1;
enum efuse_socchip_type_e soc_type;
strcpy(info->title, "version");
info->id = EFUSE_VERSION_ID;
soc_type = efuse_get_socchip_type();
switch (soc_type) {
case EFUSE_SOC_CHIP_M8BABY:
info->offset = M8_EFUSE_VERSION_OFFSET; /* 509 */
info->data_len = M8_EFUSE_VERSION_DATA_LEN;
ret = 0;
break;
case EFUSE_SOC_CHIP_UNKNOWN:
default:
pr_info("%s:%d chip is unknown, use default M8 chip\n",
__func__, __LINE__);
info->offset = M8_EFUSE_VERSION_OFFSET; /* 509 */
info->data_len = M8_EFUSE_VERSION_DATA_LEN;
ret = 0;
break;
break;
}
return ret;
}
static int efuse_readversion(void)
{
char ver_buf[4], buf[4];
struct efuseinfo_item_t info;
int ret;
if (efuse_active_version != -1)
return efuse_active_version;
ret = efuse_set_versioninfo(&info);
if (ret < 0)
return ret;
memset(ver_buf, 0, sizeof(ver_buf));
memset(buf, 0, sizeof(buf));
__efuse_read(buf, info.data_len, &info.offset);
memcpy(ver_buf, buf, sizeof(buf));
ret = efuse_checkversion(ver_buf); /* m3,m6,m8 */
if ((ret > 0) && (ver_buf[0] != 0)) {
efuse_active_version = ver_buf[0];
return ver_buf[0]; /* version right */
} else
return -1; /* version err */
}
static int efuse_getinfo_byPOS(unsigned int pos, struct efuseinfo_item_t *info)
{
int ver;
int i;
struct efuseinfo_t *vx = NULL;
struct efuseinfo_item_t *item = NULL;
int size;
int ret = -1;
enum efuse_socchip_type_e soc_type;
unsigned int versionPOS;
soc_type = efuse_get_socchip_type();
switch (soc_type) {
case EFUSE_SOC_CHIP_M8BABY:
versionPOS = M8_EFUSE_VERSION_OFFSET; /* 509 */
break;
case EFUSE_SOC_CHIP_UNKNOWN:
default:
pr_info("%s:%d chip is unknown\n", __func__, __LINE__);
return -1;
/* break; */
}
if (pos == versionPOS) {
ret = efuse_set_versioninfo(info);
return ret;
}
ver = efuse_readversion();
if (ver < 0) {
pr_info("efuse version is not selected.\n");
return -1;
}
for (i = 0; i < efuseinfo_num; i++) {
if (efuseinfo[i].version == ver) {
vx = &(efuseinfo[i]);
break;
}
}
if (!vx) {
pr_info("efuse version %d is not supported.\n", ver);
return -1;
}
item = vx->efuseinfo_version;
size = vx->size;
ret = -1;
for (i = 0; i < size; i++, item++) {
if (pos == item->offset) {
strcpy(info->title, item->title);
info->offset = item->offset;
info->id = item->id;
info->data_len = item->data_len;
/* /what's up ? typo error? */
ret = 0;
break;
}
}
if (ret < 0)
pr_info("POS:%d is not found.\n", pos);
return ret;
}
/* ================================================ */
/* public interface */
/* ================================================ */
int efuse_getinfo_byTitle(unsigned char *title, struct efuseinfo_item_t *info)
{
int ver;
int i;
struct efuseinfo_t *vx = NULL;
struct efuseinfo_item_t *item = NULL;
int size;
int ret = -1;
if (!strcmp(title, "version")) {
ret = efuse_set_versioninfo(info);
return ret;
}
ver = efuse_readversion();
if (ver < 0) {
pr_info("efuse version is not selected.\n");
return -1;
}
for (i = 0; i < efuseinfo_num; i++) {
if (efuseinfo[i].version == ver) {
vx = &(efuseinfo[i]);
break;
}
}
if (!vx) {
pr_info("efuse version %d is not supported.\n", ver);
return -1;
}
item = vx->efuseinfo_version;
size = vx->size;
ret = -1;
for (i = 0; i < size; i++, item++) {
if (!strcmp(title, item->title)) {
info->id = item->id;
strcpy(info->title, item->title);
info->offset = item->offset;
info->id = item->id;
info->data_len = item->data_len;
ret = 0;
break;
}
}
if (ret < 0)
pr_info("title: %s is not found.\n", title);
return ret;
}
int check_if_efused(loff_t pos, size_t count)
{
loff_t local_pos = pos;
int i;
unsigned char *buf = NULL;
struct efuseinfo_item_t info;
if (efuse_getinfo_byPOS(pos, &info) < 0) {
pr_info("not found the position:%lld.\n", pos);
return -1;
}
if (count > info.data_len) {
pr_info("data length: %zd is out of EFUSE layout!\n", count);
return -1;
}
if (count == 0) {
pr_info("data length: 0 is error!\n");
return -1;
}
buf = kcalloc(info.data_len, sizeof(char), GFP_KERNEL);
if (buf) {
if (__efuse_read(buf, info.data_len, &local_pos)
== info.data_len) {
for (i = 0; i < info.data_len; i++) {
if (buf[i]) {
pr_info("pos %zd value is %d",
(size_t)(pos + i), buf[i]);
return 1;
}
}
}
} else {
pr_info("no memory\n");
return -ENOMEM;
}
kfree(buf);
buf = NULL;
return 0;
}
int efuse_read_item(char *buf, size_t count, loff_t *ppos)
{
char *data_buf = NULL;
char *pdata = NULL;
unsigned int pos = (unsigned int)*ppos;
struct efuseinfo_item_t info;
if (efuse_getinfo_byPOS(pos, &info) < 0) {
pr_info("not found the position:%d.\n", pos);
return -1;
}
if (count > info.data_len) {
pr_info("data length: %zd is out of EFUSE layout!\n", count);
return -1;
}
if (count == 0) {
pr_info("data length: 0 is error!\n");
return -1;
}
data_buf = kzalloc(sizeof(char)*EFUSE_BYTES, GFP_KERNEL);
if (!data_buf) {
/* pr_info("memory not enough\n");*/
return -ENOMEM;
}
pdata = data_buf;
__efuse_read(pdata, info.data_len, ppos);
memcpy(buf, data_buf, count);
/*if (data_buf)*/
kfree(data_buf);
return count;
}
int efuse_write_item(char *buf, size_t count, loff_t *ppos)
{
char *data_buf = NULL;
char *pdata = NULL;
unsigned int data_len;
unsigned int pos = (unsigned int)*ppos;
struct efuseinfo_item_t info;
if (efuse_getinfo_byPOS(pos, &info) < 0) {
pr_info("not found the position:%d.\n", pos);
return -1;
}
#ifndef CONFIG_AMLOGIC_EFUSE_WRITE_VERSION_PERMIT
if (strcmp(info.title, "version") == 0) {
pr_info("prohibit write version in kernel\n");
return 0;
}
#endif
if (count > info.data_len) {
pr_info("data length: %zd is out of EFUSE layout!\n", count);
return -1;
}
if (count == 0) {
pr_info("data length: 0 is error!\n");
return -1;
}
data_buf = kzalloc(sizeof(char)*EFUSE_BYTES, GFP_KERNEL);
if (!data_buf) {
/* pr_info("memory not enough\n");*/
return -ENOMEM;
}
memcpy(data_buf, buf, count);
pdata = data_buf;
data_len = info.data_len;
__efuse_write(data_buf, data_len, ppos);
kfree(data_buf);
return data_len;
}
/* function: efuse_read_intlItem
* intl_item: item name,name is [temperature,cvbs_trimming,temper_cvbs]
* [temperature: 2byte]
* [cvbs_trimming: 2byte]
* [temper_cvbs: 4byte]
* buf: output para
* size: buf size
* return: <0 fail, >=0 ok
*/
int efuse_read_intlItem(char *intl_item, char *buf, int size)
{
enum efuse_socchip_type_e soc_type;
loff_t pos;
int len;
int ret = -1;
soc_type = efuse_get_socchip_type();
switch (soc_type) {
case EFUSE_SOC_CHIP_M8BABY:
if (strcasecmp(intl_item, "temperature") == 0) {
pos = 502;
len = 2;
if (size <= 0) {
pr_err("%s input size:%d is error\n",
intl_item, size);
return -1;
}
if (len > size)
len = size;
ret = __efuse_read(buf, len, &pos);
return ret;
}
if (strcasecmp(intl_item, "cvbs_trimming") == 0) {
/* cvbs note:
* cvbs has 2 bytes, position is 504 and 505,
* 504 is low byte,505 is high byte
* p504[bit2~0] is cvbs trimming CDAC_GSW<2:0>
* p505[bit7-6] : 10--wrote cvbs,
* 00-- not wrote cvbs
*/
pos = 504;
len = 2;
if (size <= 0) {
pr_err("%s input size:%d is error\n",
intl_item, size);
return -1;
}
if (len > size) {
len = size;
ret = __efuse_read(buf, len, &pos);
return ret;
}
}
if (strcasecmp(intl_item, "temper_cvbs") == 0) {
pos = 502;
len = 4;
if (size <= 0) {
pr_err("%s input size:%d is error\n",
intl_item, size);
return -1;
}
if (len > size)
len = size;
ret = __efuse_read(buf, len, &pos);
return ret;
}
break;
case EFUSE_SOC_CHIP_UNKNOWN:
default:
pr_err("%s:%d chip is unknown\n", __func__, __LINE__);
//return -1;
break;
}
return ret;
}