18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * drivers/mfd/ab3100_otp.c 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2007-2009 ST-Ericsson AB 68c2ecf20Sopenharmony_ci * Driver to read out OTP from the AB3100 Mixed-signal circuit 78c2ecf20Sopenharmony_ci * Author: Linus Walleij <linus.walleij@stericsson.com> 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/module.h> 118c2ecf20Sopenharmony_ci#include <linux/kernel.h> 128c2ecf20Sopenharmony_ci#include <linux/slab.h> 138c2ecf20Sopenharmony_ci#include <linux/init.h> 148c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 158c2ecf20Sopenharmony_ci#include <linux/mfd/abx500.h> 168c2ecf20Sopenharmony_ci#include <linux/debugfs.h> 178c2ecf20Sopenharmony_ci#include <linux/seq_file.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci/* The OTP registers */ 208c2ecf20Sopenharmony_ci#define AB3100_OTP0 0xb0 218c2ecf20Sopenharmony_ci#define AB3100_OTP1 0xb1 228c2ecf20Sopenharmony_ci#define AB3100_OTP2 0xb2 238c2ecf20Sopenharmony_ci#define AB3100_OTP3 0xb3 248c2ecf20Sopenharmony_ci#define AB3100_OTP4 0xb4 258c2ecf20Sopenharmony_ci#define AB3100_OTP5 0xb5 268c2ecf20Sopenharmony_ci#define AB3100_OTP6 0xb6 278c2ecf20Sopenharmony_ci#define AB3100_OTP7 0xb7 288c2ecf20Sopenharmony_ci#define AB3100_OTPP 0xbf 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci/** 318c2ecf20Sopenharmony_ci * struct ab3100_otp 328c2ecf20Sopenharmony_ci * @dev: containing device 338c2ecf20Sopenharmony_ci * @locked: whether the OTP is locked, after locking, no more bits 348c2ecf20Sopenharmony_ci * can be changed but before locking it is still possible 358c2ecf20Sopenharmony_ci * to change bits from 1->0. 368c2ecf20Sopenharmony_ci * @freq: clocking frequency for the OTP, this frequency is either 378c2ecf20Sopenharmony_ci * 32768Hz or 1MHz/30 388c2ecf20Sopenharmony_ci * @paf: product activation flag, indicates whether this is a real 398c2ecf20Sopenharmony_ci * product (paf true) or a lab board etc (paf false) 408c2ecf20Sopenharmony_ci * @imeich: if this is set it is possible to override the 418c2ecf20Sopenharmony_ci * IMEI number found in the tac, fac and svn fields with 428c2ecf20Sopenharmony_ci * (secured) software 438c2ecf20Sopenharmony_ci * @cid: customer ID 448c2ecf20Sopenharmony_ci * @tac: type allocation code of the IMEI 458c2ecf20Sopenharmony_ci * @fac: final assembly code of the IMEI 468c2ecf20Sopenharmony_ci * @svn: software version number of the IMEI 478c2ecf20Sopenharmony_ci * @debugfs: a debugfs file used when dumping to file 488c2ecf20Sopenharmony_ci */ 498c2ecf20Sopenharmony_cistruct ab3100_otp { 508c2ecf20Sopenharmony_ci struct device *dev; 518c2ecf20Sopenharmony_ci bool locked; 528c2ecf20Sopenharmony_ci u32 freq; 538c2ecf20Sopenharmony_ci bool paf; 548c2ecf20Sopenharmony_ci bool imeich; 558c2ecf20Sopenharmony_ci u16 cid:14; 568c2ecf20Sopenharmony_ci u32 tac:20; 578c2ecf20Sopenharmony_ci u8 fac; 588c2ecf20Sopenharmony_ci u32 svn:20; 598c2ecf20Sopenharmony_ci struct dentry *debugfs; 608c2ecf20Sopenharmony_ci}; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistatic int __init ab3100_otp_read(struct ab3100_otp *otp) 638c2ecf20Sopenharmony_ci{ 648c2ecf20Sopenharmony_ci u8 otpval[8]; 658c2ecf20Sopenharmony_ci u8 otpp; 668c2ecf20Sopenharmony_ci int err; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci err = abx500_get_register_interruptible(otp->dev, 0, 698c2ecf20Sopenharmony_ci AB3100_OTPP, &otpp); 708c2ecf20Sopenharmony_ci if (err) { 718c2ecf20Sopenharmony_ci dev_err(otp->dev, "unable to read OTPP register\n"); 728c2ecf20Sopenharmony_ci return err; 738c2ecf20Sopenharmony_ci } 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci err = abx500_get_register_page_interruptible(otp->dev, 0, 768c2ecf20Sopenharmony_ci AB3100_OTP0, otpval, 8); 778c2ecf20Sopenharmony_ci if (err) { 788c2ecf20Sopenharmony_ci dev_err(otp->dev, "unable to read OTP register page\n"); 798c2ecf20Sopenharmony_ci return err; 808c2ecf20Sopenharmony_ci } 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci /* Cache OTP properties, they never change by nature */ 838c2ecf20Sopenharmony_ci otp->locked = (otpp & 0x80); 848c2ecf20Sopenharmony_ci otp->freq = (otpp & 0x40) ? 32768 : 34100; 858c2ecf20Sopenharmony_ci otp->paf = (otpval[1] & 0x80); 868c2ecf20Sopenharmony_ci otp->imeich = (otpval[1] & 0x40); 878c2ecf20Sopenharmony_ci otp->cid = ((otpval[1] << 8) | otpval[0]) & 0x3fff; 888c2ecf20Sopenharmony_ci otp->tac = ((otpval[4] & 0x0f) << 16) | (otpval[3] << 8) | otpval[2]; 898c2ecf20Sopenharmony_ci otp->fac = ((otpval[5] & 0x0f) << 4) | (otpval[4] >> 4); 908c2ecf20Sopenharmony_ci otp->svn = (otpval[7] << 12) | (otpval[6] << 4) | (otpval[5] >> 4); 918c2ecf20Sopenharmony_ci return 0; 928c2ecf20Sopenharmony_ci} 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci/* 958c2ecf20Sopenharmony_ci * This is a simple debugfs human-readable file that dumps out 968c2ecf20Sopenharmony_ci * the contents of the OTP. 978c2ecf20Sopenharmony_ci */ 988c2ecf20Sopenharmony_ci#ifdef CONFIG_DEBUG_FS 998c2ecf20Sopenharmony_cistatic int ab3100_show_otp(struct seq_file *s, void *v) 1008c2ecf20Sopenharmony_ci{ 1018c2ecf20Sopenharmony_ci struct ab3100_otp *otp = s->private; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci seq_printf(s, "OTP is %s\n", otp->locked ? "LOCKED" : "UNLOCKED"); 1048c2ecf20Sopenharmony_ci seq_printf(s, "OTP clock switch startup is %uHz\n", otp->freq); 1058c2ecf20Sopenharmony_ci seq_printf(s, "PAF is %s\n", otp->paf ? "SET" : "NOT SET"); 1068c2ecf20Sopenharmony_ci seq_printf(s, "IMEI is %s\n", otp->imeich ? 1078c2ecf20Sopenharmony_ci "CHANGEABLE" : "NOT CHANGEABLE"); 1088c2ecf20Sopenharmony_ci seq_printf(s, "CID: 0x%04x (decimal: %d)\n", otp->cid, otp->cid); 1098c2ecf20Sopenharmony_ci seq_printf(s, "IMEI: %u-%u-%u\n", otp->tac, otp->fac, otp->svn); 1108c2ecf20Sopenharmony_ci return 0; 1118c2ecf20Sopenharmony_ci} 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_cistatic int ab3100_otp_open(struct inode *inode, struct file *file) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci return single_open(file, ab3100_show_otp, inode->i_private); 1168c2ecf20Sopenharmony_ci} 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_cistatic const struct file_operations ab3100_otp_operations = { 1198c2ecf20Sopenharmony_ci .open = ab3100_otp_open, 1208c2ecf20Sopenharmony_ci .read = seq_read, 1218c2ecf20Sopenharmony_ci .llseek = seq_lseek, 1228c2ecf20Sopenharmony_ci .release = single_release, 1238c2ecf20Sopenharmony_ci}; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_cistatic void __init ab3100_otp_init_debugfs(struct device *dev, 1268c2ecf20Sopenharmony_ci struct ab3100_otp *otp) 1278c2ecf20Sopenharmony_ci{ 1288c2ecf20Sopenharmony_ci otp->debugfs = debugfs_create_file("ab3100_otp", S_IFREG | S_IRUGO, 1298c2ecf20Sopenharmony_ci NULL, otp, &ab3100_otp_operations); 1308c2ecf20Sopenharmony_ci} 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_cistatic void __exit ab3100_otp_exit_debugfs(struct ab3100_otp *otp) 1338c2ecf20Sopenharmony_ci{ 1348c2ecf20Sopenharmony_ci debugfs_remove(otp->debugfs); 1358c2ecf20Sopenharmony_ci} 1368c2ecf20Sopenharmony_ci#else 1378c2ecf20Sopenharmony_ci/* Compile this out if debugfs not selected */ 1388c2ecf20Sopenharmony_cistatic inline void __init ab3100_otp_init_debugfs(struct device *dev, 1398c2ecf20Sopenharmony_ci struct ab3100_otp *otp) 1408c2ecf20Sopenharmony_ci{ 1418c2ecf20Sopenharmony_ci} 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_cistatic inline void __exit ab3100_otp_exit_debugfs(struct ab3100_otp *otp) 1448c2ecf20Sopenharmony_ci{ 1458c2ecf20Sopenharmony_ci} 1468c2ecf20Sopenharmony_ci#endif 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci#define SHOW_AB3100_ATTR(name) \ 1498c2ecf20Sopenharmony_cistatic ssize_t ab3100_otp_##name##_show(struct device *dev, \ 1508c2ecf20Sopenharmony_ci struct device_attribute *attr, \ 1518c2ecf20Sopenharmony_ci char *buf) \ 1528c2ecf20Sopenharmony_ci{\ 1538c2ecf20Sopenharmony_ci struct ab3100_otp *otp = dev_get_drvdata(dev); \ 1548c2ecf20Sopenharmony_ci return sprintf(buf, "%u\n", otp->name); \ 1558c2ecf20Sopenharmony_ci} 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ciSHOW_AB3100_ATTR(locked) 1588c2ecf20Sopenharmony_ciSHOW_AB3100_ATTR(freq) 1598c2ecf20Sopenharmony_ciSHOW_AB3100_ATTR(paf) 1608c2ecf20Sopenharmony_ciSHOW_AB3100_ATTR(imeich) 1618c2ecf20Sopenharmony_ciSHOW_AB3100_ATTR(cid) 1628c2ecf20Sopenharmony_ciSHOW_AB3100_ATTR(fac) 1638c2ecf20Sopenharmony_ciSHOW_AB3100_ATTR(tac) 1648c2ecf20Sopenharmony_ciSHOW_AB3100_ATTR(svn) 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_cistatic struct device_attribute ab3100_otp_attrs[] = { 1678c2ecf20Sopenharmony_ci __ATTR(locked, S_IRUGO, ab3100_otp_locked_show, NULL), 1688c2ecf20Sopenharmony_ci __ATTR(freq, S_IRUGO, ab3100_otp_freq_show, NULL), 1698c2ecf20Sopenharmony_ci __ATTR(paf, S_IRUGO, ab3100_otp_paf_show, NULL), 1708c2ecf20Sopenharmony_ci __ATTR(imeich, S_IRUGO, ab3100_otp_imeich_show, NULL), 1718c2ecf20Sopenharmony_ci __ATTR(cid, S_IRUGO, ab3100_otp_cid_show, NULL), 1728c2ecf20Sopenharmony_ci __ATTR(fac, S_IRUGO, ab3100_otp_fac_show, NULL), 1738c2ecf20Sopenharmony_ci __ATTR(tac, S_IRUGO, ab3100_otp_tac_show, NULL), 1748c2ecf20Sopenharmony_ci __ATTR(svn, S_IRUGO, ab3100_otp_svn_show, NULL), 1758c2ecf20Sopenharmony_ci}; 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_cistatic int __init ab3100_otp_probe(struct platform_device *pdev) 1788c2ecf20Sopenharmony_ci{ 1798c2ecf20Sopenharmony_ci struct ab3100_otp *otp; 1808c2ecf20Sopenharmony_ci int err = 0; 1818c2ecf20Sopenharmony_ci int i; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci otp = devm_kzalloc(&pdev->dev, sizeof(struct ab3100_otp), GFP_KERNEL); 1848c2ecf20Sopenharmony_ci if (!otp) 1858c2ecf20Sopenharmony_ci return -ENOMEM; 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci otp->dev = &pdev->dev; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci /* Replace platform data coming in with a local struct */ 1908c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, otp); 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci err = ab3100_otp_read(otp); 1938c2ecf20Sopenharmony_ci if (err) 1948c2ecf20Sopenharmony_ci return err; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci dev_info(&pdev->dev, "AB3100 OTP readout registered\n"); 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci /* sysfs entries */ 1998c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++) { 2008c2ecf20Sopenharmony_ci err = device_create_file(&pdev->dev, 2018c2ecf20Sopenharmony_ci &ab3100_otp_attrs[i]); 2028c2ecf20Sopenharmony_ci if (err) 2038c2ecf20Sopenharmony_ci goto err; 2048c2ecf20Sopenharmony_ci } 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci /* debugfs entries */ 2078c2ecf20Sopenharmony_ci ab3100_otp_init_debugfs(&pdev->dev, otp); 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci return 0; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_cierr: 2128c2ecf20Sopenharmony_ci while (--i >= 0) 2138c2ecf20Sopenharmony_ci device_remove_file(&pdev->dev, &ab3100_otp_attrs[i]); 2148c2ecf20Sopenharmony_ci return err; 2158c2ecf20Sopenharmony_ci} 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_cistatic int __exit ab3100_otp_remove(struct platform_device *pdev) 2188c2ecf20Sopenharmony_ci{ 2198c2ecf20Sopenharmony_ci struct ab3100_otp *otp = platform_get_drvdata(pdev); 2208c2ecf20Sopenharmony_ci int i; 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++) 2238c2ecf20Sopenharmony_ci device_remove_file(&pdev->dev, 2248c2ecf20Sopenharmony_ci &ab3100_otp_attrs[i]); 2258c2ecf20Sopenharmony_ci ab3100_otp_exit_debugfs(otp); 2268c2ecf20Sopenharmony_ci return 0; 2278c2ecf20Sopenharmony_ci} 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_cistatic struct platform_driver ab3100_otp_driver = { 2308c2ecf20Sopenharmony_ci .driver = { 2318c2ecf20Sopenharmony_ci .name = "ab3100-otp", 2328c2ecf20Sopenharmony_ci }, 2338c2ecf20Sopenharmony_ci .remove = __exit_p(ab3100_otp_remove), 2348c2ecf20Sopenharmony_ci}; 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_cimodule_platform_driver_probe(ab3100_otp_driver, ab3100_otp_probe); 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ciMODULE_AUTHOR("Linus Walleij <linus.walleij@stericsson.com>"); 2398c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("AB3100 OTP Readout Driver"); 2408c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 241