diff options
Diffstat (limited to 'drivers/misc/sprd_otp')
| -rw-r--r-- | drivers/misc/sprd_otp/Kconfig | 32 | ||||
| -rw-r--r-- | drivers/misc/sprd_otp/Makefile | 8 | ||||
| -rw-r--r-- | drivers/misc/sprd_otp/__regs_ana_efuse.h | 81 | ||||
| -rw-r--r-- | drivers/misc/sprd_otp/__regs_efuse.h | 114 | ||||
| -rw-r--r-- | drivers/misc/sprd_otp/ana_efuse.c | 216 | ||||
| -rw-r--r-- | drivers/misc/sprd_otp/core.c | 214 | ||||
| -rw-r--r-- | drivers/misc/sprd_otp/efuse.c | 352 | ||||
| -rw-r--r-- | drivers/misc/sprd_otp/laser_fuse.c | 158 | ||||
| -rw-r--r-- | drivers/misc/sprd_otp/otp-bitmap.c | 83 | ||||
| -rw-r--r-- | drivers/misc/sprd_otp/otp-helper.c | 449 | ||||
| -rw-r--r-- | drivers/misc/sprd_otp/sprd_otp.h | 51 |
11 files changed, 1758 insertions, 0 deletions
diff --git a/drivers/misc/sprd_otp/Kconfig b/drivers/misc/sprd_otp/Kconfig new file mode 100644 index 00000000..740bab3b --- /dev/null +++ b/drivers/misc/sprd_otp/Kconfig @@ -0,0 +1,32 @@ +menuconfig OTP_SPRD + + bool "Spreadtrum OTP Efuse" + depends on ARCH_SCX30G || ARCH_SCX35L + default y + help + Spreadtrum OTP Efuse Support. + +if OTP_SPRD + +config OTP_SPRD_EFUSE + tristate "Spreadtrum D-die AON Efuse driver (since from r3p0)" + depends on ARCH_SCX30G || ARCH_SCX35L + default y + help + Say Y here to support spreadtrum ddie efuse for ip r3p0 version. + +config OTP_SPRD_ADIE_EFUSE + tristate "Spreadtrum A-die eFuse/eMemory driver" + depends on ADIE_SC2723 || ADIE_SC2723S + default y + help + Say Y here to support spreadtrum adie efuse. + +config OTP_SPRD_LASER_FUSE + tristate "Spreadtrum A-die laser fuse driver (no longer recommended)" + depends on ADIE_SC2713 || ADIE_SC2713S + default y + help + Say Y here to support spreadtrum adie laser fuse. + +endif diff --git a/drivers/misc/sprd_otp/Makefile b/drivers/misc/sprd_otp/Makefile new file mode 100644 index 00000000..17978b9c --- /dev/null +++ b/drivers/misc/sprd_otp/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for spredtrum efuse drivers. +# + +obj-$(CONFIG_OTP_SPRD) += core.o otp-helper.o +obj-$(CONFIG_OTP_SPRD_EFUSE) += efuse.o +obj-$(CONFIG_OTP_SPRD_ADIE_EFUSE) += ana_efuse.o otp-bitmap.o +obj-$(CONFIG_OTP_SPRD_LASER_FUSE) += laser_fuse.o diff --git a/drivers/misc/sprd_otp/__regs_ana_efuse.h b/drivers/misc/sprd_otp/__regs_ana_efuse.h new file mode 100644 index 00000000..5a4d6cd0 --- /dev/null +++ b/drivers/misc/sprd_otp/__regs_ana_efuse.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2013 Spreadtrum Communications Inc. + * + * 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. + * + ************************************************* + * Automatically generated C header: do not edit * + ************************************************* + */ + +#ifndef __ANA_REGS_EFUSE_H__ +#define __ANA_REGS_EFUSE_H__ + +#define ANA_REGS_EFUSE + +/* registers definitions for controller ANA_REGS_EFUSE */ +#define ANA_REG_EFUSE_GLB_CTRL SCI_ADDR(ANA_REGS_EFUSE_BASE, 0x0000) +#define ANA_REG_EFUSE_DATA_RD SCI_ADDR(ANA_REGS_EFUSE_BASE, 0x0004) +#define ANA_REG_EFUSE_DATA_WR SCI_ADDR(ANA_REGS_EFUSE_BASE, 0x0008) +#define ANA_REG_EFUSE_BLOCK_INDEX SCI_ADDR(ANA_REGS_EFUSE_BASE, 0x000c) +#define ANA_REG_EFUSE_MODE_CTRL SCI_ADDR(ANA_REGS_EFUSE_BASE, 0x0010) +#define ANA_REG_EFUSE_STATUS SCI_ADDR(ANA_REGS_EFUSE_BASE, 0x0014) +#define ANA_REG_EFUSE_WR_TIMING_CTRL SCI_ADDR(ANA_REGS_EFUSE_BASE, 0x0028) +#define ANA_REG_EFUSE_RD_TIMING_CTRL SCI_ADDR(ANA_REGS_EFUSE_BASE, 0x002c) +#define ANA_REG_EFUSE_EFUSE_DEB_CTRL SCI_ADDR(ANA_REGS_EFUSE_BASE, 0x0030) + +/* bits definitions for register ANA_REG_EFUSE_GLB_CTRL */ +/* Efuse SW programme enable. + */ +#define BIT_EFUSE_PGM_EN ( BIT(0) ) +/* Efuse type select, 00:TSMC, 01, 1x reserved. + */ +#define BITS_EFUSE_TYPE(_x_) ( (_x_) << 1 & (BIT(1)|BIT(2)) ) + +/* bits definitions for register ANA_REG_EFUSE_DATA_RD */ +#define BITS_EFUSE_DATA_RD(_x_) ( (_x_) << 0 & (BIT(0)|BIT(1)|BIT(2)|BIT(3)|BIT(4)|BIT(5)|BIT(6)|BIT(7)) ) + +/* bits definitions for register ANA_REG_EFUSE_DATA_WR */ +#define BITS_EFUSE_DATA_WR(_x_) ( (_x_) << 0 & (BIT(0)|BIT(1)|BIT(2)|BIT(3)|BIT(4)|BIT(5)|BIT(6)|BIT(7)) ) + +/* bits definitions for register ANA_REG_EFUSE_BLOCK_INDEX */ +#define BITS_READ_WRITE_INDEX(_x_) ( (_x_) << 0 & (BIT(0)|BIT(1)|BIT(2)|BIT(3)|BIT(4)) ) + +#define SHFT_READ_WRITE_INDEX ( 0 ) +#define MASK_READ_WRITE_INDEX ( BIT(0)|BIT(1)|BIT(2)|BIT(3)|BIT(4) ) + +/* bits definitions for register ANA_REG_EFUSE_MODE_CTRL */ +/* Write 1 to this bit start A_PGM mode(array PGM mode). + * This bit is self-clear, read this bit will always get 0. + */ +#define BIT_PG_START ( BIT(0) ) +#define BIT_RD_START ( BIT(1) ) +#define BIT_STANDBY_START ( BIT(2) ) + +/* bits definitions for register ANA_REG_EFUSE_STATUS */ +#define BIT_PGM_BUSY ( BIT(0) ) +#define BIT_READ_BUSY ( BIT(1) ) +#define BIT_STANDBY_BUSY ( BIT(2) ) + +/* bits definitions for register ANA_REG_EFUSE_WR_TIMING_CTRL */ +#define BITS_EFUSE_WR_TIMING(_x_) ( (_x_) << 0 & (BIT(0)|BIT(1)|BIT(2)|BIT(3)|BIT(4)|BIT(5)|BIT(6)|BIT(7)|BIT(8)|BIT(9)|BIT(10)|BIT(11)|BIT(12)|BIT(13)) ) + +/* bits definitions for register ANA_REG_EFUSE_RD_TIMING_CTRL */ +#define BITS_EFUSE_RD_TIMING(_x_) ( (_x_) << 0 & (BIT(0)|BIT(1)|BIT(2)|BIT(3)|BIT(4)|BIT(5)|BIT(6)|BIT(7)|BIT(8)|BIT(9)) ) + +/* bits definitions for register ANA_REG_EFUSE_EFUSE_DEB_CTRL */ +#define BIT_MARGIN_MODE_EN ( BIT(1) ) +#define BIT_DOUBLE_BIT_DISABLE ( BIT(0) ) + +/* vars definitions for controller ANA_REGS_EFUSE */ +#define EFUSE_MAGIC_NUMBER ( 0x2723 ) + +#endif /* __ANA_REGS_EFUSE_H__ */ diff --git a/drivers/misc/sprd_otp/__regs_efuse.h b/drivers/misc/sprd_otp/__regs_efuse.h new file mode 100644 index 00000000..d597ef97 --- /dev/null +++ b/drivers/misc/sprd_otp/__regs_efuse.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2014 Spreadtrum Communications Inc. + * + * 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. + * + ************************************************* + * Automatically generated C header: do not edit * + ************************************************* + */ + +#ifndef __REGS_EFUSE_H__ +#define __REGS_EFUSE_H__ + +#define REGS_EFUSE + +/* registers definitions for controller REGS_EFUSE */ +#define REG_EFUSE_DATA_RD SCI_ADDR(REGS_EFUSE_BASE, 0x0000) +#define REG_EFUSE_DATA_WR SCI_ADDR(REGS_EFUSE_BASE, 0x0004) +#define REG_EFUSE_READ_WRITE_INDEX SCI_ADDR(REGS_EFUSE_BASE, 0x0008) +#define REG_EFUSE_MODE_CTRL SCI_ADDR(REGS_EFUSE_BASE, 0x000c) +#define REG_EFUSE_CFG0 SCI_ADDR(REGS_EFUSE_BASE, 0x0010) +#define REG_EFUSE_CFG1 SCI_ADDR(REGS_EFUSE_BASE, 0x0014) +#define REG_EFUSE_STATUS SCI_ADDR(REGS_EFUSE_BASE, 0x0020) +#define REG_EFUSE_BLK_FLAG0 SCI_ADDR(REGS_EFUSE_BASE, 0x0024) +#define REG_EFUSE_BLK_FLAG1 SCI_ADDR(REGS_EFUSE_BASE, 0x0028) +#define REG_EFUSE_BLK_FLAG0_CLR SCI_ADDR(REGS_EFUSE_BASE, 0x0030) +#define REG_EFUSE_BLK_FLAG1_CLR SCI_ADDR(REGS_EFUSE_BASE, 0x0034) +#define REG_EFUSE_MAGIC_NUMBER SCI_ADDR(REGS_EFUSE_BASE, 0x0040) +#define REG_EFUSE_STROBE_LOW_WIDTH SCI_ADDR(REGS_EFUSE_BASE, 0x0044) +#define REG_EFUSE_EFUSE_DEB_CTRL SCI_ADDR(REGS_EFUSE_BASE, 0x0048) + +/* bits definitions for register REG_EFUSE_READ_WRITE_INDEX */ +#define BITS_READ_WRITE_INDEX(_x_) ( (_x_) << 0 & (BIT(0)|BIT(1)|BIT(2)|BIT(3)|BIT(4)) ) + +#define SHFT_READ_WRITE_INDEX ( 0 ) +#define MASK_READ_WRITE_INDEX ( BIT(0)|BIT(1)|BIT(2)|BIT(3)|BIT(4) ) + +/* bits definitions for register REG_EFUSE_MODE_CTRL */ +/* Write 1 to this bit start A_PGM mode(array PGM mode). + * This bit is self-clear, read this bit will always get 0. + */ +#define BIT_PG_START ( BIT(0) ) +#define BIT_RD_START ( BIT(1) ) +#define BIT_STANDBY_START ( BIT(2) ) + +/* bits definitions for register REG_EFUSE_CFG0 */ +#define BITS_TPGM_TIME_CNT(_x_) ( (_x_) << 0 & (BIT(0)|BIT(1)|BIT(2)|BIT(3)|BIT(4)|BIT(5)|BIT(6)|BIT(7)|BIT(8)) ) +#define BITS_EFS_TYPE(_x_) ( (_x_) << 16 & (BIT(16)|BIT(17)) ) +#define BIT_EFS_VDDQ_K1_ON ( BIT(28) ) +#define BIT_EFS_VDDQ_K2_ON ( BIT(29) ) +/* Set this bit will open 0.9v static power supply for efuse memory, + * before any operation towards to efuse memory this bit have to set to 1. + * Once this bit is cleared, the efuse will go to power down mode. + */ +#define BIT_EFS_VDD_ON ( BIT(30) ) +/* ONly set this bit can SW write register field of TPGM_TIME_CNT and start PGM mode, + * for protect sw unexpectedly programmed efuse memory. + */ +#define BIT_PGM_EN ( BIT(31) ) + +/* bits definitions for register REG_EFUSE_CFG1 */ +#define BIT_BLK0_AUTO_TEST_EN ( BIT(0) ) + +/* bits definitions for register REG_EFUSE_STATUS */ +#define BIT_PGM_BUSY ( BIT(0) ) +#define BIT_READ_BUSY ( BIT(1) ) +#define BIT_STANDBY_BUSY ( BIT(2) ) + +/* bits definitions for register REG_EFUSE_BLK_FLAG0 */ +#define BIT_BLK0_PROT_FLAG ( BIT(0) ) + +/* bits definitions for register REG_EFUSE_BLK_FLAG1 */ +#define BIT_BLK0_ERR_FLAG ( BIT(0) ) + +/* bits definitions for register REG_EFUSE_BLK_FLAG0_CLR */ +#define BIT_BLK0_PROT_FLAG_CLR ( BIT(0) ) + +/* bits definitions for register REG_EFUSE_BLK_FLAG1_CLR */ +#define BIT_BLK0_ERR_FLAG_CLR ( BIT(0) ) + +/* bits definitions for register REG_EFUSE_MAGIC_NUMBER */ +/* Magic number, only when this field is 0x8810, the efuse programming command can be handle. + * So, if SW want to program efuse memory, except open clocks and power, the follow conditions must be met: + * 1. PGM_EN = 1; + * 2. EFUSE_MAGIC_NUMBER = 0x8810 + */ +#define BITS_MAGIC_NUMBER(_x_) ( (_x_) << 0 & (BIT(0)|BIT(1)|BIT(2)|BIT(3)|BIT(4)|BIT(5)|BIT(6)|BIT(7)|BIT(8)|BIT(9)|BIT(10)|BIT(11)|BIT(12)|BIT(13)|BIT(14)|BIT(15)) ) +#define BITS_DEB_MAGIC_NUMBER(_x_) ( (_x_) << 16 & (BIT(16)|BIT(17)|BIT(18)|BIT(19)|BIT(20)|BIT(21)|BIT(22)|BIT(23)|BIT(24)|BIT(25)|BIT(26)|BIT(27)|BIT(28)|BIT(29)|BIT(30)|BIT(31)) ) + +/* bits definitions for register REG_EFUSE_STROBE_LOW_WIDTH */ +#define BITS_EFUSE_STROBE_LOW_WIDTH(_x_)( (_x_) << 0 & (BIT(0)|BIT(1)|BIT(2)|BIT(3)|BIT(4)|BIT(5)|BIT(6)|BIT(7)) ) +#define BITS_CLK_EFS_DIV(_x_) ( (_x_) << 16 & (BIT(16)|BIT(17)|BIT(18)|BIT(19)|BIT(20)|BIT(21)|BIT(22)|BIT(23)) ) + +#define SHFT_CLK_EFS_DIV ( 16 ) +#define MASK_CLK_EFS_DIV ( BIT(16)|BIT(17)|BIT(18)|BIT(19)|BIT(20)|BIT(21)|BIT(22)|BIT(23) ) + +/* bits definitions for register REG_EFUSE_EFUSE_DEB_CTRL */ +#define BIT_MARGIN_MODE_EN ( BIT(1) ) +#define BIT_DOUBLE_BIT_DISABLE ( BIT(0) ) + +/* vars definitions for controller REGS_EFUSE */ +#define EFUSE_MAGIC_NUMBER ( 0x8810 ) +#define EFUSE_DEB_MAGIC_NUMBER ( 0x6868 ) +#define BIT_EFUSE_PROT_LOCK ( BIT(31) ) + +#endif /* __REGS_EFUSE_H__ */ diff --git a/drivers/misc/sprd_otp/ana_efuse.c b/drivers/misc/sprd_otp/ana_efuse.c new file mode 100644 index 00000000..6e344f61 --- /dev/null +++ b/drivers/misc/sprd_otp/ana_efuse.c @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2014 Spreadtrum Communications Inc. + * + * 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. + * + */ + +/* IMPORTANT: + * + * TODO: + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/stat.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/of.h> +#include <linux/of_device.h> +/* FIXME: */ +#include <soc/sprd/arch_lock.h> +#include <soc/sprd/adi.h> +#include <soc/sprd/sci_glb_regs.h> +#include <soc/sprd/sci.h> + +#include "sprd_otp.h" +#include "__regs_ana_efuse.h" + +#define ANA_REGS_EFUSE_BASE ( SPRD_ADISLAVE_BASE + 0x200 ) + +#define EFUSE_BLOCK_MAX ( 32 ) +#define EFUSE_BLOCK_WIDTH ( 8 ) /* bit counts */ + +static DEFINE_MUTEX(adie_efuse_mtx); + +void adie_efuse_workaround(void) +{ +#define REG_ADI_GSSI_CFG0 (SPRD_ADI_PHYS + 0x1C) +/** BIT_RF_GSSI_SCK_ALL_IN + * 0: sclk auto gate + * 1: sclk always on +*/ +#define BIT_RF_GSSI_SCK_ALL_IN BIT(30) + sci_glb_write(REG_ADI_GSSI_CFG0, + BIT_RF_GSSI_SCK_ALL_IN, BIT_RF_GSSI_SCK_ALL_IN); +} + +static void adie_efuse_lock(void) +{ + mutex_lock(&adie_efuse_mtx); +} + +static void adie_efuse_unlock(void) +{ + mutex_unlock(&adie_efuse_mtx); +} + +static void adie_efuse_reset(void) +{ +} + +static void __adie_efuse_power_on(void) +{ + sci_adi_set(ANA_REG_GLB_ARM_MODULE_EN, BIT_ANA_EFS_EN); + /* FIXME: rtc_efs only for prog + sci_adi_set(ANA_REG_GLB_RTC_CLK_EN, BIT_RTC_EFS_EN); + */ + + /* FIXME: sclk always on or not ? */ + /* adie_efuse_workaround(); */ +} + +static void __adie_efuse_power_off(void) +{ + /* FIXME: rtc_efs only for prog + sci_adi_clr(ANA_REG_GLB_RTC_CLK_EN, BIT_RTC_EFS_EN); + */ + sci_adi_clr(ANA_REG_GLB_ARM_MODULE_EN, BIT_ANA_EFS_EN); +} + +static __inline int __adie_efuse_wait_clear(u32 bits) +{ + int ret = 0; + unsigned long timeout; + + pr_debug("wait %x\n", sci_adi_read(ANA_REG_EFUSE_STATUS)); + + /* wait for maximum of 3000 msec */ + timeout = jiffies + msecs_to_jiffies(3000); + while (sci_adi_read(ANA_REG_EFUSE_STATUS) & bits) { + if (time_after(jiffies, timeout)) { + WARN_ON(1); + ret = -ETIMEDOUT; + break; + } + cpu_relax(); + } + return ret; +} + +static u32 adie_efuse_read(int blk_index) +{ + u32 val = 0; + + pr_debug("adie efuse read %d\n", blk_index); + adie_efuse_lock(); + __adie_efuse_power_on(); + /* enable adie_efuse module clk and power before */ + + /* FIXME: set read timing, why 0x20 (default value) + sci_adi_raw_write(ANA_REG_EFUSE_RD_TIMING_CTRL, + BITS_EFUSE_RD_TIMING(0x20)); + */ + + sci_adi_raw_write(ANA_REG_EFUSE_BLOCK_INDEX, + BITS_READ_WRITE_INDEX(blk_index)); + sci_adi_raw_write(ANA_REG_EFUSE_MODE_CTRL, BIT_RD_START); + + if (IS_ERR_VALUE(__adie_efuse_wait_clear(BIT_READ_BUSY))) + goto out; + + val = sci_adi_read(ANA_REG_EFUSE_DATA_RD); + + /* FIXME: reverse the otp value */ + val = BITS_EFUSE_DATA_RD(~val); + +out: + __adie_efuse_power_off(); + adie_efuse_unlock(); + return val; +} + +static struct sprd_otp_operations adie_efuse_ops = { + .reset = adie_efuse_reset, + .read = adie_efuse_read, +}; + +u32 __adie_efuse_read(int blk_index) +{ + return adie_efuse_ops.read(blk_index); +} + +EXPORT_SYMBOL_GPL(__adie_efuse_read); + +u32 __adie_efuse_read_bits(int bit_index, int length) +{ + int i, blk_index = (int)bit_index / EFUSE_BLOCK_WIDTH; + int blk_max = DIV_ROUND_UP(bit_index + length, EFUSE_BLOCK_WIDTH); + u32 val = 0; + pr_debug("otp read blk %d - %d\n", blk_index, blk_max); + /* FIXME: length should not bigger than 8 */ + for (i = blk_index; i < blk_max; i++) { + val |= __adie_efuse_read(i) + << ((i - blk_index) * EFUSE_BLOCK_WIDTH); + } + //pr_debug("val=%08x\n", val); + val >>= (bit_index & (EFUSE_BLOCK_WIDTH - 1)); + val &= BIT(length) - 1; + pr_debug("otp read bits %d ++ %d 0x%08x\n\n", bit_index, length, val); + return val; +} +EXPORT_SYMBOL_GPL(__adie_efuse_read_bits); + +static ssize_t adie_efuse_block_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "adie efuse blocks: %uX%ubits\n", + EFUSE_BLOCK_MAX, EFUSE_BLOCK_WIDTH); +} + +static ssize_t adie_efuse_block_dump(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, idx; + char *p = buf; + + p += sprintf(p, "adie efuse blocks dump:\n"); + p += sprintf(p, " 7 6 5 4 3 2 1 0"); + for (idx = 0; idx < EFUSE_BLOCK_MAX; idx++) { + u32 val = __adie_efuse_read(idx); + p += sprintf(p, "\n%02d ", idx); + for (i = EFUSE_BLOCK_WIDTH - 1; i >= 0; --i) + p += sprintf(p, "%s ", (val & BIT(i)) ? "1" : "0"); + } + p += sprintf(p, "\n"); + return p - buf; +} + +static DEVICE_ATTR(block, S_IRUGO, adie_efuse_block_show, NULL); +static DEVICE_ATTR(dump, S_IRUGO, adie_efuse_block_dump, NULL); + +int __init sprd_adie_efuse_init(void) +{ + int ret; + void *dev; + dev = + sprd_otp_register("sprd_adie_efuse_otp", &adie_efuse_ops, + EFUSE_BLOCK_MAX, EFUSE_BLOCK_WIDTH / 8); + if (IS_ERR_OR_NULL(dev)) + return PTR_ERR(dev); + + ret = device_create_file(dev, &dev_attr_block); + ret |= device_create_file(dev, &dev_attr_dump); + if (ret) + return ret; + + return 0; +} diff --git a/drivers/misc/sprd_otp/core.c b/drivers/misc/sprd_otp/core.c new file mode 100644 index 00000000..be87085b --- /dev/null +++ b/drivers/misc/sprd_otp/core.c @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2014 Spreadtrum Communications Inc. + * + * 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/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/bug.h> +#include <linux/debugfs.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_address.h> +#include "sprd_otp.h" + +/* cached otp blocks max 32X32 */ +#define CACHED_BLOCK_MAX ( 32 ) +#define CACHED_BLOCK_WIDTH ( 32 ) /* bit counts */ +static u8 cached_otp[CACHED_BLOCK_MAX * CACHED_BLOCK_WIDTH / 8]; + +/* + * Head entry for the doubly linked sprd otp list + */ +static LIST_HEAD(sprd_otp_list); +static DEFINE_MUTEX(sprd_otp_mtx); + +static struct sprd_otp_device *__sprd_otp_find(int minor) +{ + struct sprd_otp_device *otp; + + if (unlikely(list_empty(&sprd_otp_list))) + return NULL; + + list_for_each_entry(otp, &sprd_otp_list, list) { + if (otp->misc.minor == minor) { + return otp; + } + } + return NULL; +} + +static ssize_t sprd_otp_read(struct file *file, char __user * buf, + size_t count, loff_t * f_pos) +{ + int ret; + int minor = iminor(file_inode(file)); + struct sprd_otp_device *otp = __sprd_otp_find(minor); + mutex_lock(&sprd_otp_mtx); + if (otp && otp->ops && otp->ops->read) { + int i, blk_index = (int)*f_pos / otp->blk_width; + int blk_max = blk_index + DIV_ROUND_UP(count, otp->blk_width); + pr_debug("otp read blk %d - %d\n", blk_index, blk_max); + if (blk_max > otp->blk_max) + blk_max = otp->blk_max; + for (i = blk_index; i < blk_max; i++) { + int index = i * otp->blk_width; + u32 val = otp->ops->read(i); + memcpy(&cached_otp[index], &val, otp->blk_width); + } + } + pr_debug("otp read from %d %u %d\n", (int)*f_pos, count, + (int)file->f_pos); + ret = + simple_read_from_buffer(buf, count, f_pos, cached_otp, + sizeof(cached_otp)); + + mutex_unlock(&sprd_otp_mtx); + return ret; +} + +static ssize_t sprd_otp_write(struct file *file, const char __user * buf, + size_t count, loff_t * f_pos) +{ + ssize_t ret; + int pos = (int)*f_pos; + int minor = iminor(file_inode(file)); + struct sprd_otp_device *otp = __sprd_otp_find(minor); + pr_debug("otp write to %d %u %d\n", (int)*f_pos, count, + (int)file->f_pos); + mutex_lock(&sprd_otp_mtx); + ret = simple_write_to_buffer(cached_otp, sizeof(cached_otp), f_pos, + buf, count); + if (IS_ERR_VALUE(ret)) + goto out; + + if (otp && otp->ops && otp->ops->prog) { + int blk_index = pos / otp->blk_width; + pr_debug("otp prog blk %d\n", blk_index); + BUG_ON(count != otp->blk_width); + if (blk_index < otp->blk_max) { + int index = blk_index * otp->blk_width; + u32 val = 0; + memcpy(&val, &cached_otp[index], otp->blk_width); + otp->ops->prog(blk_index, val); + } + } +out: + mutex_unlock(&sprd_otp_mtx); + return ret; +} + +static loff_t sprd_otp_llseek(struct file *file, loff_t offset, int whence) +{ + pr_debug("otp seek at %d %d\n", (int)offset, whence); + switch (whence) { + case SEEK_SET: + file->f_pos = offset; + break; + case SEEK_CUR: + file->f_pos += offset; + break; + default: + return -EINVAL; + } + return file->f_pos; +} + +static const struct file_operations sprd_otp_fops = { + .owner = THIS_MODULE, + .read = sprd_otp_read, + .write = sprd_otp_write, + .llseek = sprd_otp_llseek, +}; + +static struct miscdevice dummy_otp = { + .minor = MISC_DYNAMIC_MINOR, + .name = "sprd_dummy_otp", + .fops = &sprd_otp_fops, +}; + +void *sprd_otp_register(const char *name, void *ops, int blk_max, int blk_width) +{ + int ret; + struct sprd_otp_device *otp; + otp = kzalloc(sizeof(struct sprd_otp_device), GFP_KERNEL); + if (unlikely(!otp)) + return ERR_PTR(-ENOMEM); + INIT_LIST_HEAD(&otp->list); + otp->misc.minor = MISC_DYNAMIC_MINOR; + otp->misc.name = kstrdup(name, GFP_KERNEL); + otp->misc.fops = &sprd_otp_fops; + otp->blk_max = blk_max; + otp->blk_width = blk_width; /*MUST be bytes count */ + BUG_ON(otp->blk_width > sizeof(u32)); + BUG_ON(otp->blk_max * otp->blk_width > sizeof(cached_otp)); + otp->ops = ops; + ret = misc_register(&otp->misc); + if (ret) + return ERR_PTR(ret); + mutex_lock(&sprd_otp_mtx); + list_add(&otp->list, &sprd_otp_list); + mutex_unlock(&sprd_otp_mtx); + pr_info("otp register %s as miscdevice %d, block %uX%ubits\n", + otp->misc.name, otp->misc.minor, otp->blk_max, + otp->blk_width * 8); + return otp->misc.this_device; +} + +static ssize_t otp_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "cached otp blocks: %uX%ubits\n", + CACHED_BLOCK_MAX, CACHED_BLOCK_WIDTH); +} + +static DEVICE_ATTR(info, S_IRUGO, otp_info_show, NULL); + +static int __init sprd_otp_init(void) +{ + int ret; + ret = misc_register(&dummy_otp); + if (ret) + return ret; + + ret = device_create_file(dummy_otp.this_device, &dev_attr_info); + if (ret) + return ret; + +#if defined(CONFIG_OTP_SPRD_EFUSE) + sprd_efuse_init(); +#endif +#if defined(CONFIG_OTP_SPRD_ADIE_EFUSE) + sprd_adie_efuse_init(); +#endif +#if defined(CONFIG_OTP_SPRD_LASER_FUSE) + sprd_adie_laserfuse_init(); +#endif + return ret; +} + +static void __exit sprd_otp_exit(void) +{ + misc_deregister(&dummy_otp); +} + +module_init(sprd_otp_init); +module_exit(sprd_otp_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Spreadtrum OTP Efuse Driver"); +MODULE_AUTHOR("robot <zhulin.lian@spreadtrum.com>"); +MODULE_VERSION("0.1"); diff --git a/drivers/misc/sprd_otp/efuse.c b/drivers/misc/sprd_otp/efuse.c new file mode 100644 index 00000000..f1bb18c2 --- /dev/null +++ b/drivers/misc/sprd_otp/efuse.c @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2014 Spreadtrum Communications Inc. + * + * 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. + * + */ + +/* IMPORTANT: + * The electrical fuse is a type of non-volatile memory fabricated + * in standard CMOS logic process. This electrical fuse macro is widely + * used in chip ID, memory redundancy, security code, configuration setting, + * and feature selection, etc. + * + * This efuse controller is designed for 32*32 bits electrical fuses, + * support TSMC HPM 28nm product of "TEF28HPM32X32HD18_PHRM". + * + * and we default use double-bit, 512bits efuse are visiable for software. + * + * efuse driver ONLY support module ip version since from r3p0 + * which had integrated into scx30g and scx35l etc. + * + * TODO: + * 1. do something when block had been locked with bit31 if need + * 1. check and clear blk prog/err flag if need + * 1. wait *300ms* for read/prog ready time-out + * 1. only mutexlock and hwspinlock for efuse access sync with cp + * 1. be care not do use efuse API in interrupt function + * 1. no need soft reset after efuse read/prog and power on/off + * 1. efuse block width should less than 8bits + * 1. efuse block count should not bigger than 32! or not should expland the cached otp arrary + * 1. support efuse DT info (version, blocks, ...) later + * 1. there is no handle for efuse module removed + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/stat.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/of.h> +#include <linux/of_device.h> +/* FIXME: */ +#include <soc/sprd/arch_lock.h> + +#ifdef CONFIG_64BIT +#define SPRD_UIDEFUSE_BASE SPRD_DEV_P2V(0x40240000) +#endif + +#include "sprd_otp.h" +#include "__regs_efuse.h" + +#define REGS_EFUSE_BASE SPRD_UIDEFUSE_BASE + +#define EFUSE_BLOCK_MAX ( 16 ) +#define EFUSE_BLOCK_WIDTH ( 32 ) /* bit counts */ + +static u32 efuse_magic; +static DEFINE_MUTEX(efuse_mtx); +static void efuse_lock(void) +{ + mutex_lock(&efuse_mtx); + arch_hwlock_fast(HWLOCK_EFUSE); +} + +static void efuse_unlock(void) +{ + arch_hwunlock_fast(HWLOCK_EFUSE); + mutex_unlock(&efuse_mtx); +} + +static void efuse_reset(void) +{ + /* should enable module before soft reset efuse */ + WARN_ON(!sci_glb_read(REG_AON_APB_APB_EB0, BIT_EFUSE_EB)); + sci_glb_set(REG_AON_APB_APB_RST0, BIT_EFUSE_SOFT_RST); + udelay(5); + sci_glb_clr(REG_AON_APB_APB_RST0, BIT_EFUSE_SOFT_RST); +} + +/* FIXME: Set EFS_VDD_ON will open 0.9v static power supply for efuse memory, + * before any operation towards to efuse memory this bit have to set to 1. + * Once this bit is cleared, the efuse will go to power down mode. + * + * each time when EFS_VDD_ON changes, software should wait at least 1ms to let + * VDD become stable. + * + * For VDDQ(1.8v) power, to prevent the overshot of VDDQ, a extra power switch + * connected to ground are controlled by "EFS_VDDQ_K2_ON" + */ +void __efuse_prog_power_on(void) +{ + u32 cfg0; + sci_glb_set(REG_AON_APB_APB_EB0, BIT_EFUSE_EB); + cfg0 = __raw_readl((void *)REG_EFUSE_CFG0); + cfg0 &= ~(BIT_EFS_VDDQ_K2_ON | BIT_EFS_VDDQ_K1_ON); + cfg0 |= BIT_EFS_VDD_ON; + __raw_writel(cfg0, (void *)REG_EFUSE_CFG0); + msleep(1); + + cfg0 |= BIT_EFS_VDDQ_K1_ON; + __raw_writel(cfg0, (void *)REG_EFUSE_CFG0); + msleep(1); +} + +void __efuse_power_on(void) +{ + u32 cfg0; + sci_glb_set(REG_AON_APB_APB_EB0, BIT_EFUSE_EB); + cfg0 = __raw_readl((void *)REG_EFUSE_CFG0); + cfg0 &= ~BIT_EFS_VDDQ_K1_ON; + cfg0 |= BIT_EFS_VDD_ON | BIT_EFS_VDDQ_K2_ON; + __raw_writel(cfg0, (void *)REG_EFUSE_CFG0); + msleep(1); +} + +void __efuse_power_off(void) +{ + u32 cfg0 = __raw_readl((void *)REG_EFUSE_CFG0); + if (cfg0 & BIT_EFS_VDDQ_K1_ON) { + cfg0 &= ~BIT_EFS_VDDQ_K1_ON; + __raw_writel(cfg0, (void *)REG_EFUSE_CFG0); + msleep(1); + } + + cfg0 |= BIT_EFS_VDDQ_K2_ON; + cfg0 &= ~BIT_EFS_VDD_ON; + __raw_writel(cfg0, (void *)REG_EFUSE_CFG0); + msleep(1); + + sci_glb_clr(REG_AON_APB_APB_EB0, BIT_EFUSE_EB); +} + +static __inline int __efuse_wait_clear(u32 bits) +{ + int ret = 0; + unsigned long timeout; + + pr_debug("wait %x\n", __raw_readl((void *)REG_EFUSE_STATUS)); + + /* wait for maximum of 300 msec */ + timeout = jiffies + msecs_to_jiffies(300); + while (__raw_readl((void *)REG_EFUSE_STATUS) & bits) { + if (time_after(jiffies, timeout)) { + WARN_ON(1); + ret = -ETIMEDOUT; + break; + } + cpu_relax(); + } + return ret; +} + +static u32 __efuse_read(int blk) +{ + u32 val = 0; + + /* enable efuse module clk and power before */ + + __raw_writel(BITS_READ_WRITE_INDEX(blk), + (void *)REG_EFUSE_READ_WRITE_INDEX); + __raw_writel(BIT_RD_START, (void *)REG_EFUSE_MODE_CTRL); + + if (IS_ERR_VALUE(__efuse_wait_clear(BIT_READ_BUSY))) + goto out; + + val = __raw_readl((void *)REG_EFUSE_DATA_RD); + +out: + return val; +} + +static u32 efuse_read(int blk_index) +{ + u32 val; + pr_debug("efuse read %d\n", blk_index); + efuse_lock(); + + __efuse_power_on(); + val = __efuse_read(blk_index); + __efuse_power_off(); + + efuse_unlock(); + return val; +} + +static int __efuse_prog(int blk, u32 val) +{ + u32 cfg0 = __raw_readl((void *)REG_EFUSE_CFG0); + + if (blk < 0 || blk >= EFUSE_BLOCK_MAX) /* debug purpose */ + goto out; + + /* enable pgm mode and setup magic number before programming */ + cfg0 |= BIT_PGM_EN; + __raw_writel(cfg0, (void *)REG_EFUSE_CFG0); + __raw_writel(BITS_MAGIC_NUMBER(efuse_magic), + (void *)REG_EFUSE_MAGIC_NUMBER); + + __raw_writel(val, (void *)REG_EFUSE_DATA_WR); + __raw_writel(BITS_READ_WRITE_INDEX(blk), + (void *)REG_EFUSE_READ_WRITE_INDEX); + pr_debug("cfg0 %x\n", __raw_readl((void *)REG_EFUSE_CFG0)); + __raw_writel(BIT_PG_START, (void *)REG_EFUSE_MODE_CTRL); + if (IS_ERR_VALUE(__efuse_wait_clear(BIT_PGM_BUSY))) + goto out; + +out: + __raw_writel(0, (void *)REG_EFUSE_MAGIC_NUMBER); + cfg0 &= ~BIT_PGM_EN; + __raw_writel(cfg0, (void *)REG_EFUSE_CFG0); + return 0; +} + +static int efuse_prog(int blk_index, u32 val) +{ + int ret; + pr_debug("efuse prog %d %08x\n", blk_index, val); + + efuse_lock(); + + /* enable vddon && vddq */ + __efuse_prog_power_on(); + ret = __efuse_prog(blk_index, val); + __efuse_power_off(); + + efuse_unlock(); + return ret; +} + +static struct sprd_otp_operations efuse_ops = { + .reset = efuse_reset, + .read = efuse_read, + .prog = efuse_prog, +}; + +u32 __ddie_efuse_read(int blk_index) +{ + return efuse_ops.read(blk_index); +} + +EXPORT_SYMBOL_GPL(__ddie_efuse_read); + +static ssize_t efuse_block_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "ddie efuse blocks: %uX%ubits\nprog magic: 0x%x\n", + EFUSE_BLOCK_MAX, EFUSE_BLOCK_WIDTH, efuse_magic); +} + +static ssize_t efuse_magic_store(struct device *pdev, + struct device_attribute *attr, + const char *buff, size_t size) +{ + sscanf(buff, "%x", &efuse_magic); + return size; +} + +static ssize_t efuse_block_dump(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int idx; + char *p = buf; + + p += sprintf(p, "ddie efuse blocks dump:\n"); + for (idx = 0; idx < EFUSE_BLOCK_MAX; idx++) { + p += sprintf(p, "[%02d] %08x\n", idx, efuse_read(idx)); + } + return p - buf; +} +#ifdef CONFIG_ARCH_SCX20 +static ssize_t efuse_uid_info(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u32 block0,block1; + u32 x,y,wafer_id; + u32 LOTID_0,LOTID_1,LOTID_2,LOTID_3,LOTID_4,LOTID_5; + char *p = buf; + block0= efuse_read(0); + block1= efuse_read(1); + y=block1&0x7F; + x=(block1>>7)&0x7F; + wafer_id=(block1>>14)&0x1F; + LOTID_0=(block1>>19)&0x3F; + LOTID_1=(block1>>25)&0x3F; + LOTID_2=block0&0x3F; + LOTID_3=(block0>>6)&0x3F; + LOTID_4=(block0>>12)&0x3F; + LOTID_5=(block0>>18)&0x3F; + p += sprintf(p, "efuse uid dump:\n"); + p += sprintf(p,"%c%c%c%c%c%c_%d_%d_%d\n",LOTID_5+48,LOTID_4+48,LOTID_3+48,LOTID_2+48,LOTID_1+48,LOTID_0+48,wafer_id,x,y); + p += sprintf(p, "\n"); + return p - buf; +} + +static void efuse_uid(void) +{ + u32 block0,block1; + u32 x,y,wafer_id; + u32 LOTID_0,LOTID_1,LOTID_2,LOTID_3,LOTID_4,LOTID_5; + char bufallid[20]; + block0= efuse_read(0); + block1= efuse_read(1); + y=block1&0x7F; + x=(block1>>7)&0x7F; + wafer_id=(block1>>14)&0x1F; + LOTID_0=(block1>>19)&0x3F; + LOTID_1=(block1>>25)&0x3F; + LOTID_2=block0&0x3F; + LOTID_3=(block0>>6)&0x3F; + LOTID_4=(block0>>12)&0x3F; + LOTID_5=(block0>>18)&0x3F; + pr_info("uid is %c%c%c%c%c%c_%d_%d_%d\n",LOTID_5+48,LOTID_4+48,LOTID_3+48,LOTID_2+48,LOTID_1+48,LOTID_0+48,wafer_id,x,y); +} +static DEVICE_ATTR(uid, S_IRUGO, efuse_uid_info, NULL); +#endif +static DEVICE_ATTR(block, S_IRUGO, efuse_block_show, NULL); +static DEVICE_ATTR(magic, S_IWUSR, NULL, efuse_magic_store); +static DEVICE_ATTR(dump, S_IRUGO, efuse_block_dump, NULL); + + + +int __init sprd_efuse_init(void) +{ + int ret; + void *dev; + dev = + sprd_otp_register("sprd_efuse_otp", &efuse_ops, EFUSE_BLOCK_MAX, + EFUSE_BLOCK_WIDTH / 8); + if (IS_ERR_OR_NULL(dev)) + return PTR_ERR(dev); +#ifdef CONFIG_ARCH_SCX20 + efuse_uid(); +#endif + ret = device_create_file(dev, &dev_attr_block); + ret |= device_create_file(dev, &dev_attr_magic); + ret |= device_create_file(dev, &dev_attr_dump); +#ifdef CONFIG_ARCH_SCX20 + ret |= device_create_file(dev, &dev_attr_uid); +#endif + if (ret) + return ret; + + return 0; +} diff --git a/drivers/misc/sprd_otp/laser_fuse.c b/drivers/misc/sprd_otp/laser_fuse.c new file mode 100644 index 00000000..4df8a897 --- /dev/null +++ b/drivers/misc/sprd_otp/laser_fuse.c @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2014 Spreadtrum Communications Inc. + * + * 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. + * + */ + +/* IMPORTANT: + * + * TODO: + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/stat.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <soc/sprd/arch_lock.h> + +#include <soc/sprd/hardware.h> +#include <soc/sprd/adi.h> +#include <soc/sprd/sci.h> +#include <soc/sprd/sci_glb_regs.h> + +#include "sprd_otp.h" + +#define EFUSE_BLOCK_MAX ( 4 ) +#define EFUSE_BLOCK_WIDTH ( 16 ) /* bit counts */ + +static DEFINE_MUTEX(afuse_mtx); +static void afuse_lock(void) +{ + mutex_lock(&afuse_mtx); + arch_hwlock_fast(HWLOCK_EFUSE); +} + +static void afuse_unlock(void) +{ + arch_hwunlock_fast(HWLOCK_EFUSE); + mutex_unlock(&afuse_mtx); +} + +static u32 afuse_read(int blk) +{ + unsigned long timeout; + u32 val = 0; + + pr_debug("adie laser_fuse read %d\n", blk); + afuse_lock(); + + if (blk < 0 || blk >= EFUSE_BLOCK_MAX) /* debug purpose */ + goto out; + + sci_adi_write_fast(ANA_REG_GLB_AFUSE_CTRL, BIT_AFUSE_READ_REQ, 1); + udelay(1); + /* wait for maximum of 300 msec */ + timeout = jiffies + msecs_to_jiffies(300); + while (BIT_AFUSE_READ_REQ & sci_adi_read(ANA_REG_GLB_AFUSE_CTRL)) { + if (time_after(jiffies, timeout)) { + WARN_ON(1); + goto out; + } + cpu_relax(); + } + val = sci_adi_read(ANA_REG_GLB_AFUSE_OUT0 + 4 * blk); + +out: + afuse_unlock(); + return val; +} + +#define BITS_AFUSE_DLY_PROT_KEY (0xa2 << 8) +static void afuse_set_readdly(u32 dly) +{ + u32 val = BITS_AFUSE_DLY_PROT_KEY + BITS_AFUSE_READ_DLY(dly); + afuse_lock(); + + sci_adi_write_fast(ANA_REG_GLB_AFUSE_CTRL, val, 0); + val &= ~BITS_AFUSE_DLY_PROT_KEY; /*release lock */ + sci_adi_write_fast(ANA_REG_GLB_AFUSE_CTRL, val, 1); + + afuse_unlock(); +} + +static struct sprd_otp_operations afuse_ops = { + .read = afuse_read, +}; + +u32 __adie_laserfuse_read(int blk_index) +{ + return afuse_ops.read(blk_index); +} + +EXPORT_SYMBOL_GPL(__adie_laserfuse_read); + +static ssize_t afuse_block_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "adie laser_fuse blocks: %uX%ubits\n", + EFUSE_BLOCK_MAX, EFUSE_BLOCK_WIDTH); +} + +static ssize_t afuse_readdly_store(struct device *pdev, + struct device_attribute *attr, + const char *buff, size_t size) +{ + u32 dly; + sscanf(buff, "%x", &dly); + afuse_set_readdly(dly); + return size; +} + +static ssize_t afuse_block_dump(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int idx; + char *p = buf; + + p += sprintf(p, "adie laser_fuse blocks dump:\n"); + for (idx = 0; idx < EFUSE_BLOCK_MAX; idx++) { + p += sprintf(p, "[%02d] %08x\n", idx, + __adie_laserfuse_read(idx)); + } + return p - buf; +} + +static DEVICE_ATTR(block, S_IRUGO, afuse_block_show, NULL); +static DEVICE_ATTR(readdly, S_IWUSR, NULL, afuse_readdly_store); +static DEVICE_ATTR(dump, S_IRUGO, afuse_block_dump, NULL); + +int __init sprd_adie_laserfuse_init(void) +{ + int ret; + void *dev; + dev = + sprd_otp_register("sprd_afuse_otp", &afuse_ops, + EFUSE_BLOCK_MAX, EFUSE_BLOCK_WIDTH / 8); + if (IS_ERR_OR_NULL(dev)) + return PTR_ERR(dev); + + ret = device_create_file(dev, &dev_attr_block); + ret |= device_create_file(dev, &dev_attr_readdly); + ret |= device_create_file(dev, &dev_attr_dump); + if (ret) + return ret; + + return 0; +} diff --git a/drivers/misc/sprd_otp/otp-bitmap.c b/drivers/misc/sprd_otp/otp-bitmap.c new file mode 100644 index 00000000..d3e64c2d --- /dev/null +++ b/drivers/misc/sprd_otp/otp-bitmap.c @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2014 Spreadtrum Communications Inc. + * + * 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/kernel.h> +#include <linux/string.h> +#include "sprd_otp.h" + +u32 __adie_efuse_read_bits(int bit_index, int length); + +struct otp_bitmap { + const char *name; /* MUST same as sc2723-regulators.dtsi */ + int bits_idx, bits_len;/* field bits offset and length */ +}; + +const struct otp_bitmap otp_bitmap[] = { + /* regulator name, block, offset, length */ + //DCDC (6) + {"vddcore", BITSINDEX(0, 0), 6}, /* DCDC_CORE_NOR */ + {"vddcore_ds", BITSINDEX(1, 0), 6}, /* DCDC_CORE_SLP */ + {"vddarm", BITSINDEX(1, 6), 7}, /* DCDC_ARM_A */ + {"vddarm_b", BITSINDEX(2, 5), 7}, /* DCDC_ARM_B */ + {"vddmem", BITSINDEX(3, 6), 7}, /* DCDC_MEM */ + {"vddgen", BITSINDEX(4, 5), 7}, /* DCDC_GEN */ + {"vddrf", BITSINDEX(5, 4), 7}, + {"vddcon", BITSINDEX(13, 1), 7}, /* DCDC_CON?DCDC_WPA? */ + + //LDO (20) + {"vdd25", BITSINDEX(18, 2), 6}, /* AVDD28? *//* FIXME: be care LDO_VDD2V5_OPT 0 : 1.8v, 1 : 2.8V */ + {"vddkpled", BITSINDEX(19, 0), 6}, + {"vddvibr", BITSINDEX(19, 6), 6}, + {"vddwifipa", BITSINDEX(20, 4), 6}, + {"vddcammot", BITSINDEX(21, 2), 6}, + {"vddemmccore", BITSINDEX(22, 0), 6}, + {"vdddcxo", BITSINDEX(22, 6), 6}, + {"vddsdcore", BITSINDEX(23, 4), 6}, + {"vdd28", BITSINDEX(24, 2), 6}, + {"vddcama", BITSINDEX(25, 0), 6}, + {"vddsim0", BITSINDEX(25, 6), 6}, + {"vddsim1", BITSINDEX(25, 6), 6}, /* share with vddsim0 */ + {"vddsim2", BITSINDEX(25, 6), 6}, /* share with vddsim0 */ + {"vddsdio", BITSINDEX(26, 4), 6}, + {"vddusb", BITSINDEX(27, 2), 6}, + {"vddcamio", BITSINDEX(28, 0), 6}, + {"vdd18", BITSINDEX(28, 0), 6}, /* share with vddcamio */ + {"vddcamd", BITSINDEX(28, 6), 6}, + {"vddrf0", BITSINDEX(29, 4), 6}, + {"vddgen1", BITSINDEX(30, 2), 6}, + {"vddgen0", BITSINDEX(31, 0), 6}, + {0}, +}; + +int sci_otp_get_offset(const char *name) +{ + const struct otp_bitmap *map = otp_bitmap; + int offset = 0; + for (; map->name; map++) { + if (0 == strcmp(map->name, name)) { + offset -= (int)(BIT(map->bits_len) / 2); /* middle */ + offset += + (int)__adie_efuse_read_bits(map->bits_idx, + map->bits_len); + } + } + if ((0 == strcmp(name, "vddcore")) + || (0 == strcmp(name, "vddarm"))) { + int bb_chip = (int)__adie_efuse_read_bits(BITSINDEX(31, 6), 2); + pr_debug("%s bb_chip magic %d\n", name, bb_chip); + offset += -32 + 32 * bb_chip; + } + pr_debug("%s offset %d (steps)\n", name, offset); + return offset; +} diff --git a/drivers/misc/sprd_otp/otp-helper.c b/drivers/misc/sprd_otp/otp-helper.c new file mode 100644 index 00000000..743dc6e0 --- /dev/null +++ b/drivers/misc/sprd_otp/otp-helper.c @@ -0,0 +1,449 @@ +/* + * Copyright (C) 2014 Spreadtrum Communications Inc. + * + * 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/init.h> +#include <linux/kernel.h> +#include <linux/stat.h> +#include <linux/err.h> +#include "sprd_otp.h" + +#define BLK_UID_HIGH ( 0 ) +#define BLK_UID_LOW ( 1 ) +#define BLK_ADC_DETA ( 7 ) +#define BLK_CCCV_DETA ( 9 ) + +#define BLK_FGU_DETA_ABC ( 8 ) +#define BLK_FGU_DETA_D ( 9 ) + +#define BLK_WIDTH_OTP_EMEMORY ( 8 ) /* bit counts */ +#define BLK_ADC_DETA_ABC_OTP ( 7 ) /* start block for ADC otp delta */ + +u32 __weak __ddie_efuse_read(int blk_index) +{ + return 0; +} +/*---------------------------------------------------------------------------* + * Efuse public API functions for other kernel modules * + *---------------------------------------------------------------------------*/ +u64 sci_efuse_get_uid(void) +{ + u64 uid_hi, uid_lo; + + uid_hi = __ddie_efuse_read(BLK_UID_HIGH); + uid_lo = __ddie_efuse_read(BLK_UID_LOW); + + /* fill system serial number */ + return ((uid_hi << 32ull) | uid_lo); +} + +EXPORT_SYMBOL_GPL(sci_efuse_get_uid); + +#if defined(CONFIG_ARCH_SCX20L) || defined(CONFIG_ARCH_SCX35L64)\ +|| defined(CONFIG_ARCH_SCX20) +#define BLK_THM_DETA (13) +#define THM_CAL_BIT (26) +#else +#define BLK_THM_DETA (7) +#define THM_CAL_BIT (24) +#endif +int sci_efuse_thermal_cal_get(int *cal) +{ + u32 data=__ddie_efuse_read(BLK_THM_DETA); + int thm_cal = (data >> THM_CAL_BIT) & 0x001F; + if (thm_cal ==0) { + *cal = 0; + return -1; + } + *cal = thm_cal -16; + return 0; +} +EXPORT_SYMBOL_GPL(sci_efuse_thermal_cal_get); + +#define BASE_ADC_P0 711 //3.6V +#define BASE_ADC_P1 830 //4.2V +#define VOL_P0 3600 +#define VOL_P1 4200 +#define ADC_DATA_OFFSET 128 +int sci_efuse_calibration_get(unsigned int *p_cal_data) +{ + unsigned int deta = 0; + unsigned short adc_temp = 0; + +#if defined(CONFIG_ADIE_SC2723) || defined(CONFIG_ADIE_SC2723S) + /* verify the otp data of ememory written or not */ + adc_temp = (__adie_efuse_read(0) & BIT(7)); + if (adc_temp) + return 0; + + deta = __adie_efuse_read_bits(BLK_ADC_DETA_ABC_OTP * BLK_WIDTH_OTP_EMEMORY, 16); +#elif defined(CONFIG_ARCH_SCX30G) || defined(CONFIG_ARCH_SCX35L) + deta = __ddie_efuse_read(BLK_ADC_DETA); + WARN_ON(!(deta & BIT(31))); /* BIT 31 is protected bit */ +#else + #warning "AuxADC CAL DETA need fixing" +#endif + + pr_info("%s() get efuse block %u, deta: 0x%08x\n", __func__, BLK_ADC_DETA, deta); + + deta &= 0xFFFFFF; /* get BIT0 ~ BIT23) in block 7 */ + + if ((!deta) || (p_cal_data == NULL)) { + return 0; + } + //adc 3.6V + adc_temp = ((deta >> 8) & 0x00FF) + BASE_ADC_P0 - ADC_DATA_OFFSET; + p_cal_data[1] = (VOL_P0) | ((adc_temp << 2) << 16); + + //adc 4.2V + adc_temp = (deta & 0x00FF) + BASE_ADC_P1 - ADC_DATA_OFFSET; + p_cal_data[0] = (VOL_P1) | ((adc_temp << 2) << 16); + + return 1; +} + +EXPORT_SYMBOL_GPL(sci_efuse_calibration_get); + +int sci_efuse_cccv_cal_get(unsigned int *p_cal_data) +{ +#if defined(CONFIG_ADIE_SC2723) || defined(CONFIG_ADIE_SC2723S) + unsigned int data,blk0; + + blk0 = __adie_efuse_read(0); + + if (blk0 & (1 << 7)) { + return 0; + } + + data = __adie_efuse_read_bits(BITSINDEX(14, 0), 6); + pr_info("sci_efuse_cccv_cal_get data:0x%x\n", data); + *p_cal_data = (data) & 0x003F; +#else + unsigned int data; + + data = __ddie_efuse_read(BLK_CCCV_DETA); + data &= ~(1 << 31); + + pr_info("sci_efuse_cccv_cal_get data:0x%x\n", data); + + if ((!data) || (p_cal_data == NULL)) { + return 0; + } + + *p_cal_data = (data >> 24) & 0x003F; /*block 9, bit 29..24 */ +#endif + return 1; +} + +EXPORT_SYMBOL_GPL(sci_efuse_cccv_cal_get); + +int sci_efuse_fgu_cal_get(unsigned int *p_cal_data) +{ +#if defined(CONFIG_ADIE_SC2723) || defined(CONFIG_ADIE_SC2723S) + unsigned int data,blk0; + + blk0 = __adie_efuse_read(0); + + if (blk0 & (1 << 7)) { + return 0; + } + + data = __adie_efuse_read_bits(BITSINDEX(12, 0), 9); + printk("sci_efuse_fgu_cal_get 4.2 data data:0x%x\n", data); + p_cal_data[0] = (data + 6963) - 4096 - 256; //4.0V or 4.2V + + data = __adie_efuse_read_bits(BITSINDEX(14, 2), 6); + printk("sci_efuse_fgu_cal_get 3.6 data data:0x%x\n", data); + p_cal_data[1] = p_cal_data[0] - 410 - data + 16; //3.6V + + data = __adie_efuse_read_bits(BITSINDEX(13, 1), 9); + printk("sci_efuse_fgu_cal_get 0 data data:0x%x\n", data); + p_cal_data[2] = (((data) & 0x1FF) + 8192) - 256; + + return 1; +#else + unsigned int data; + + data = __ddie_efuse_read(BLK_FGU_DETA_ABC); + data &= ~(1 << 31); + + pr_info("sci_efuse_fgu_cal_get ABC data:0x%x\n", data); + + if ((!data) || (p_cal_data == NULL)) { + return 0; + } + + p_cal_data[0] = (((data >> 16) & 0x1FF) + 6963) - 4096 - 256; //4.0V or 4.2V + p_cal_data[1] = p_cal_data[0] - 410 - ((data >> 25) & 0x3F) + 16; //3.6V + p_cal_data[2] = (((data) & 0x1FF) + 8192) - 256; + + return 1; +#endif +} + +EXPORT_SYMBOL_GPL(sci_efuse_fgu_cal_get); + +/* + * sci_efuse_get_apt_cal - read apt data saved in efuse + * @pdata: apt data pointer for dcdcwpa (unit: uV) + * pdata[0] -> 3v + * pdata[1] -> 0.8v + * @num: the length of apt data + * + * retruns 0 if success, else + * returns negative number. + */ +#define APT_CAL_DATA_BLK ( 10 ) +#define OFFSET2VOL(p, uv) ( ((p) - 128) * (uv) / 256) +int sci_efuse_get_apt_cal(unsigned int *pdata, int num) +{ + int i = 0; + u32 efuse_data = 0; + u8 *offset = (u8 *) & efuse_data; + const unsigned int vol_ref[2] = { + 3000000, //3v + 800000, //0.8v + }; + + /* Fixme: dcdcwpa only support two points(3.6v / 0.8v) voltage calibration */ + BUG_ON(num > 2); + + efuse_data = __ddie_efuse_read(APT_CAL_DATA_BLK); + + pr_info("%s efuse data: 0x%08x\n", __func__, efuse_data); + + WARN_ON(!(efuse_data & BIT(31))); /* BIT 31 is protected bit */ + efuse_data &= 0xFFFF; /* get BIT0 ~ BIT15 in block 10 */ + + if (!(efuse_data) || (!pdata)) { + return -1; + } + + printk("%s: ", __func__); + for (i = 0; i < num; i++) { + pdata[i] = (unsigned int)OFFSET2VOL(offset[i], vol_ref[i]); + printk("(%duV : %08d), ", vol_ref[i], pdata[i]); + } + printk("\n"); + + return 0; +} + +EXPORT_SYMBOL_GPL(sci_efuse_get_apt_cal); + +/* + * sci_efuse_get_cal - read 4 point adc calibration data saved in efuse + * @pdata: adc data pointer + * pdata[0] -> 4.2v + * pdata[1] -> 3.6v + * pdata[2] -> 0.4v + * @num: the length of adc data + * + * retruns 0 if success, else + * returns negative number. + */ +#define BLK_ADC_DETA_ABC ( 7 ) +#define BLK_ADC_DETA_D ( 9 ) +#define DELTA2ADC(_delta_, _Ideal_) ( ((_delta_) + (_Ideal_) - 128) << 2 ) +#define ADC_VOL(_delta_, _Ideal_, vol) ( (DELTA2ADC(_delta_, _Ideal_) << 16) | (vol) ) + +int sci_efuse_get_cal(unsigned int *pdata, int num) +{ + int i; + u32 efuse_data = 0; + u8 *delta = (u8 *) & efuse_data; + const u16 ideal[3][2] = { + {4200, 830}, /* FIXME: efuse only support 10bits */ + {3600, 711}, + {400, 79}, + }; + +#if defined(CONFIG_ADIE_SC2723) || defined(CONFIG_ADIE_SC2723S) + /* verify the otp data of ememory written or not */ + efuse_data = (__adie_efuse_read(0) & BIT(7)); + if (efuse_data) + return -2; + + efuse_data = __adie_efuse_read_bits(BLK_ADC_DETA_ABC_OTP * BLK_WIDTH_OTP_EMEMORY, 16); +#elif defined(CONFIG_ARCH_SCX30G) || defined(CONFIG_ARCH_SCX35L) + efuse_data = __ddie_efuse_read(BLK_ADC_DETA_ABC); + WARN_ON(!(efuse_data & BIT(31))); /* BIT 31 is protected bit */ +#else + #warning "AuxADC CAL DETA need fixing" +#endif + + pr_info("%s efuse data: 0x%08x\n", __func__, efuse_data); + + efuse_data &= 0xFFFFFF; /* get BIT0 ~ BIT23 in block 7 */ + + if (!(efuse_data) || (!pdata)) { + return -1; + } + + //WARN_ON(!((delta[0] > delta[1]) && (delta[1] > delta[2]))); + + for (i = 0; i < num; i++) { + pdata[i] = + (unsigned int)ADC_VOL(delta[i], ideal[i][1], ideal[i][0]); + } + + /* dump adc calibration parameters */ + printk("%s adc_cal: ", __func__); + for (i = 0; i < num; i++) { + printk("%d -- %d; \n", (pdata[i] & 0xFFFF), + (pdata[i] >> 16) & 0xFFFF); + } + printk("\n"); + + return 0; +} + +EXPORT_SYMBOL_GPL(sci_efuse_get_cal); + +/* + * sci_efuse_get_cal_v2 - read 4 point adc calibration data saved in efuse + * @pdata: adc data pointer + * pdata[0] -> 4.2v + * pdata[1] -> 3.6v + * pdata[3] -> 0.9v + * pdata[4] -> 0.3v + * @num: the length of adc data + * + * retruns 0 if success, else + * returns negative number. + */ +int sci_efuse_get_cal_v2(unsigned int *pdata, int num) +{ + int i; + u32 efuse_data; + u8 *delta = (u8 *) & efuse_data; + const u16 ideal[4][2] = { + {4200, 830}, /* FIXME: efuse only support 10bits */ + {3600, 711}, + {900, 737}, + {300, 255}, + }; + + efuse_data = __ddie_efuse_read(BLK_ADC_DETA_ABC); + + pr_info("%s efuse block(%d) data: 0x%08x\n", __func__, BLK_ADC_DETA_ABC, efuse_data); + + WARN_ON(!(efuse_data & BIT(31))); /* BIT 31 is protected bit */ + efuse_data &= 0xFFFFFF; /* get BIT0 ~ BIT23 in block 7 */ + + if (!(efuse_data) || (!pdata)) { + return -1; + } + + WARN_ON(!((delta[0] > delta[1]) && (delta[1] > delta[2]))); + + for (i = 0; i < num; i++) { + pdata[i] = + (unsigned int)ADC_VOL(delta[i], ideal[i][1], ideal[i][0]); + if (3 == i) { + efuse_data = __ddie_efuse_read(BLK_ADC_DETA_D); + pr_info("efuse block(%d) data: 0x%08x\n", BLK_ADC_DETA_D, efuse_data); + WARN_ON(!(efuse_data & BIT(31))); + efuse_data &= 0xFF0000; /* get BIT16 ~ BIT23 in block 9 */ + if (!(efuse_data)) { + return -2; + } + pdata[i] = + (unsigned int)ADC_VOL(delta[2], ideal[i][1], + ideal[i][0]); + } + } + + /* dump adc calibration parameters */ + printk("%s adc_cal: ", __func__); + for (i = 0; i < num; i++) { + printk("%d -- %d; \n", (pdata[i] & 0xFFFF), + (pdata[i] >> 16) & 0xFFFF); + } + printk("\n"); + + return 0; +} + +EXPORT_SYMBOL_GPL(sci_efuse_get_cal_v2); + +/* +* sci_efuse_get_cal_v3 - read 2 point adc calibration data saved in efuse +* @pdata: adc data pointer +* pdata[0] -> 0.9v +* pdata[1] -> 0.3v +* @num: the length of adc data +* +* retruns 0 if success, else +* returns negative number. +*/ +int sci_efuse_get_cal_v3(unsigned int *pdata, int num) +{ + int i; + u32 efuse_data; + u8 *delta = (u8 *) & efuse_data; + const u16 ideal[2][2] = { + {900, 737}, + {300, 245}, + }; + if (!pdata || num < 2) { + return -1; + } + efuse_data = __ddie_efuse_read(BLK_ADC_DETA_ABC); + pr_info("%s efuse block(%d) data: 0x%08x\n", __func__, BLK_ADC_DETA_ABC, efuse_data); + WARN_ON(!(efuse_data & BIT(31))); + /* BIT 31 is protected bit */ + efuse_data &= 0xFF0000; + /* get BIT16 ~ BIT23 in block 7 */ + if (!efuse_data) { + return -2; + } + pdata[0] = (unsigned int)ADC_VOL(delta[2], ideal[0][1], ideal[0][0]); + efuse_data = __ddie_efuse_read(BLK_ADC_DETA_D); + pr_info("efuse block(%d) data: 0x%08x\n", BLK_ADC_DETA_D, efuse_data); + WARN_ON(!(efuse_data & BIT(31))); + efuse_data &= 0xFF0000; + /* get BIT16 ~ BIT23 in block 9 */ + if (!(efuse_data)) { + return -3; + } + pdata[1] = (unsigned int)ADC_VOL(delta[2], ideal[1][1],ideal[1][0]); + /* dump adc calibration parameters */ + printk("%s adc_cal: \n", __func__); + for (i = 0; i < num; i++) { + printk("%d -- %d; \n", (pdata[i] & 0xFFFF), + (pdata[i] >> 16) & 0xFFFF); + } + printk("\n"); + return 0; +} +EXPORT_SYMBOL_GPL(sci_efuse_get_cal_v3); + +#if defined(CONFIG_ADIE_SC2723) || defined(CONFIG_ADIE_SC2723S) + +int sci_efuse_ib_trim_get(unsigned int *p_cal_data) +{ + unsigned int data,blk0; + + blk0 = __adie_efuse_read(0); + if (blk0 & (1 << 7)) { + return 0; + } + + data = __adie_efuse_read_bits(BITSINDEX(15, 0), 7); + *p_cal_data = data; + return 1; +} +EXPORT_SYMBOL_GPL(sci_efuse_ib_trim_get); + +#endif diff --git a/drivers/misc/sprd_otp/sprd_otp.h b/drivers/misc/sprd_otp/sprd_otp.h new file mode 100644 index 00000000..bfd057e8 --- /dev/null +++ b/drivers/misc/sprd_otp/sprd_otp.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2014 Spreadtrum Communications Inc. + * + * 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. + * + */ + +#ifndef __SPRD_OTP_H__ +#define __SPRD_OTP_H__ + +#include <linux/miscdevice.h> + +struct sprd_otp_operations { + void (*reset) (void); + u32(*read) (int blk_index); + int (*prog) (int blk_index, u32 data); +}; + +struct sprd_otp_device { + struct miscdevice misc; + int blk_max; /* maximun valid blocks */ + int blk_width; /* bytes counts per block */ + struct sprd_otp_operations *ops; + struct list_head list; +}; +#define BITSINDEX(b, o) ( (b) * 8 + (o) ) + +int sprd_efuse_init(void); +int sprd_adie_efuse_init(void); +u32 __adie_efuse_read(int blk_index); +u32 __adie_efuse_read_bits(int bit_index, int length); + +int sprd_adie_laserfuse_init(void); +void *sprd_otp_register(const char *name, void *ops, int blk_max, + int blk_width); + +/* EXPORT API */ +u32 __ddie_efuse_read(int blk_index); +u32 __adie_laserfuse_read(int blk_index); +u32 __adie_efuse_read(int blk_index); +u32 __adie_efuse_read_bits(int bit_index, int length); + +#endif /* __SPRD_OTP_H__ */ |
