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