diff options
author | Linus Walleij <linus.walleij@stericsson.com> | 2009-08-18 16:52:26 -0400 |
---|---|---|
committer | Samuel Ortiz <sameo@linux.intel.com> | 2009-09-17 03:47:21 -0400 |
commit | 12992dd89c84839167f97aae540f2ec889daf782 (patch) | |
tree | 9f5316d7fc8add0a1d79f43d111529209d186fa4 | |
parent | 295c08bc69a5dd8cef69ceaeaaf551a17f50c34b (diff) |
mfd: AB3100 OTP readout
This adds the ability to read out OTP (One-Time Programmable)
registers in the AB3100 MFD ASIC. It's a simple sysfs file you
can cat to prompt. The OTP registers of the AB3100 are used to
store various device-unique information such as customer ID,
product flags and the 3GPP standard IMEI (International Mobile
Equipment Indentity) number.
Signed-off-by: Linus Walleij <linus.walleij@stericsson.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
-rw-r--r-- | drivers/mfd/Kconfig | 9 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 1 | ||||
-rw-r--r-- | drivers/mfd/ab3100-otp.c | 268 |
3 files changed, 278 insertions, 0 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index a4f3dff30ba5..4fd4f913c36b 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig | |||
@@ -276,6 +276,15 @@ config AB3100_CORE | |||
276 | LEDs, vibrator, system power and temperature, power management | 276 | LEDs, vibrator, system power and temperature, power management |
277 | and ALSA sound. | 277 | and ALSA sound. |
278 | 278 | ||
279 | config AB3100_OTP | ||
280 | tristate "ST-Ericsson AB3100 OTP functions" | ||
281 | depends on AB3100_CORE | ||
282 | default y if AB3100_CORE | ||
283 | help | ||
284 | Select this to enable the AB3100 Mixed Signal IC OTP (one-time | ||
285 | programmable memory) support. This exposes a sysfs file to read | ||
286 | out OTP values. | ||
287 | |||
279 | config EZX_PCAP | 288 | config EZX_PCAP |
280 | bool "PCAP Support" | 289 | bool "PCAP Support" |
281 | depends on GENERIC_HARDIRQS && SPI_MASTER | 290 | depends on GENERIC_HARDIRQS && SPI_MASTER |
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 7fec04fb5f47..6a5d8cb545fc 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile | |||
@@ -48,3 +48,4 @@ obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o | |||
48 | obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o | 48 | obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o |
49 | obj-$(CONFIG_PCF50633_GPIO) += pcf50633-gpio.o | 49 | obj-$(CONFIG_PCF50633_GPIO) += pcf50633-gpio.o |
50 | obj-$(CONFIG_AB3100_CORE) += ab3100-core.o | 50 | obj-$(CONFIG_AB3100_CORE) += ab3100-core.o |
51 | obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o | ||
diff --git a/drivers/mfd/ab3100-otp.c b/drivers/mfd/ab3100-otp.c new file mode 100644 index 000000000000..0499b2031a2c --- /dev/null +++ b/drivers/mfd/ab3100-otp.c | |||
@@ -0,0 +1,268 @@ | |||
1 | /* | ||
2 | * drivers/mfd/ab3100_otp.c | ||
3 | * | ||
4 | * Copyright (C) 2007-2009 ST-Ericsson AB | ||
5 | * License terms: GNU General Public License (GPL) version 2 | ||
6 | * Driver to read out OTP from the AB3100 Mixed-signal circuit | ||
7 | * Author: Linus Walleij <linus.walleij@stericsson.com> | ||
8 | */ | ||
9 | |||
10 | #include <linux/module.h> | ||
11 | #include <linux/kernel.h> | ||
12 | #include <linux/init.h> | ||
13 | #include <linux/platform_device.h> | ||
14 | #include <linux/mfd/ab3100.h> | ||
15 | #include <linux/debugfs.h> | ||
16 | |||
17 | /* The OTP registers */ | ||
18 | #define AB3100_OTP0 0xb0 | ||
19 | #define AB3100_OTP1 0xb1 | ||
20 | #define AB3100_OTP2 0xb2 | ||
21 | #define AB3100_OTP3 0xb3 | ||
22 | #define AB3100_OTP4 0xb4 | ||
23 | #define AB3100_OTP5 0xb5 | ||
24 | #define AB3100_OTP6 0xb6 | ||
25 | #define AB3100_OTP7 0xb7 | ||
26 | #define AB3100_OTPP 0xbf | ||
27 | |||
28 | /** | ||
29 | * struct ab3100_otp | ||
30 | * @dev containing device | ||
31 | * @ab3100 a pointer to the parent ab3100 device struct | ||
32 | * @locked whether the OTP is locked, after locking, no more bits | ||
33 | * can be changed but before locking it is still possible | ||
34 | * to change bits from 1->0. | ||
35 | * @freq clocking frequency for the OTP, this frequency is either | ||
36 | * 32768Hz or 1MHz/30 | ||
37 | * @paf product activation flag, indicates whether this is a real | ||
38 | * product (paf true) or a lab board etc (paf false) | ||
39 | * @imeich if this is set it is possible to override the | ||
40 | * IMEI number found in the tac, fac and svn fields with | ||
41 | * (secured) software | ||
42 | * @cid customer ID | ||
43 | * @tac type allocation code of the IMEI | ||
44 | * @fac final assembly code of the IMEI | ||
45 | * @svn software version number of the IMEI | ||
46 | * @debugfs a debugfs file used when dumping to file | ||
47 | */ | ||
48 | struct ab3100_otp { | ||
49 | struct device *dev; | ||
50 | struct ab3100 *ab3100; | ||
51 | bool locked; | ||
52 | u32 freq; | ||
53 | bool paf; | ||
54 | bool imeich; | ||
55 | u16 cid:14; | ||
56 | u32 tac:20; | ||
57 | u8 fac; | ||
58 | u32 svn:20; | ||
59 | struct dentry *debugfs; | ||
60 | }; | ||
61 | |||
62 | static int __init ab3100_otp_read(struct ab3100_otp *otp) | ||
63 | { | ||
64 | struct ab3100 *ab = otp->ab3100; | ||
65 | u8 otpval[8]; | ||
66 | u8 otpp; | ||
67 | int err; | ||
68 | |||
69 | err = ab3100_get_register_interruptible(ab, AB3100_OTPP, &otpp); | ||
70 | if (err) { | ||
71 | dev_err(otp->dev, "unable to read OTPP register\n"); | ||
72 | return err; | ||
73 | } | ||
74 | |||
75 | err = ab3100_get_register_page_interruptible(ab, AB3100_OTP0, | ||
76 | otpval, 8); | ||
77 | if (err) { | ||
78 | dev_err(otp->dev, "unable to read OTP register page\n"); | ||
79 | return err; | ||
80 | } | ||
81 | |||
82 | /* Cache OTP properties, they never change by nature */ | ||
83 | otp->locked = (otpp & 0x80); | ||
84 | otp->freq = (otpp & 0x40) ? 32768 : 34100; | ||
85 | otp->paf = (otpval[1] & 0x80); | ||
86 | otp->imeich = (otpval[1] & 0x40); | ||
87 | otp->cid = ((otpval[1] << 8) | otpval[0]) & 0x3fff; | ||
88 | otp->tac = ((otpval[4] & 0x0f) << 16) | (otpval[3] << 8) | otpval[2]; | ||
89 | otp->fac = ((otpval[5] & 0x0f) << 4) | (otpval[4] >> 4); | ||
90 | otp->svn = (otpval[7] << 12) | (otpval[6] << 4) | (otpval[5] >> 4); | ||
91 | return 0; | ||
92 | } | ||
93 | |||
94 | /* | ||
95 | * This is a simple debugfs human-readable file that dumps out | ||
96 | * the contents of the OTP. | ||
97 | */ | ||
98 | #ifdef CONFIG_DEBUGFS | ||
99 | static int show_otp(struct seq_file *s, void *v) | ||
100 | { | ||
101 | struct ab3100_otp *otp = s->private; | ||
102 | int err; | ||
103 | |||
104 | seq_printf(s, "OTP is %s\n", otp->locked ? "LOCKED" : "UNLOCKED"); | ||
105 | seq_printf(s, "OTP clock switch startup is %uHz\n", otp->freq); | ||
106 | seq_printf(s, "PAF is %s\n", otp->paf ? "SET" : "NOT SET"); | ||
107 | seq_printf(s, "IMEI is %s\n", otp->imeich ? | ||
108 | "CHANGEABLE" : "NOT CHANGEABLE"); | ||
109 | seq_printf(s, "CID: 0x%04x (decimal: %d)\n", otp->cid, otp->cid); | ||
110 | seq_printf(s, "IMEI: %u-%u-%u\n", otp->tac, otp->fac, otp->svn); | ||
111 | return 0; | ||
112 | } | ||
113 | |||
114 | static int ab3100_otp_open(struct inode *inode, struct file *file) | ||
115 | { | ||
116 | return single_open(file, ab3100_otp_show, inode->i_private); | ||
117 | } | ||
118 | |||
119 | static const struct file_operations ab3100_otp_operations = { | ||
120 | .open = ab3100_otp_open, | ||
121 | .read = seq_read, | ||
122 | .llseek = seq_lseek, | ||
123 | .release = single_release, | ||
124 | }; | ||
125 | |||
126 | static int __init ab3100_otp_init_debugfs(struct device *dev, | ||
127 | struct ab3100_otp *otp) | ||
128 | { | ||
129 | otp->debugfs = debugfs_create_file("ab3100_otp", S_IFREG | S_IRUGO, | ||
130 | NULL, otp, | ||
131 | &ab3100_otp_operations); | ||
132 | if (!otp->debugfs) { | ||
133 | dev_err(dev, "AB3100 debugfs OTP file registration failed!\n"); | ||
134 | return err; | ||
135 | } | ||
136 | } | ||
137 | |||
138 | static void __exit ab3100_otp_exit_debugfs(struct ab3100_otp *otp) | ||
139 | { | ||
140 | debugfs_remove_file(otp->debugfs); | ||
141 | } | ||
142 | #else | ||
143 | /* Compile this out if debugfs not selected */ | ||
144 | static inline int __init ab3100_otp_init_debugfs(struct device *dev, | ||
145 | struct ab3100_otp *otp) | ||
146 | { | ||
147 | return 0; | ||
148 | } | ||
149 | |||
150 | static inline void __exit ab3100_otp_exit_debugfs(struct ab3100_otp *otp) | ||
151 | { | ||
152 | } | ||
153 | #endif | ||
154 | |||
155 | #define SHOW_AB3100_ATTR(name) \ | ||
156 | static ssize_t ab3100_otp_##name##_show(struct device *dev, \ | ||
157 | struct device_attribute *attr, \ | ||
158 | char *buf) \ | ||
159 | {\ | ||
160 | struct ab3100_otp *otp = dev_get_drvdata(dev); \ | ||
161 | return sprintf(buf, "%u\n", otp->name); \ | ||
162 | } | ||
163 | |||
164 | SHOW_AB3100_ATTR(locked) | ||
165 | SHOW_AB3100_ATTR(freq) | ||
166 | SHOW_AB3100_ATTR(paf) | ||
167 | SHOW_AB3100_ATTR(imeich) | ||
168 | SHOW_AB3100_ATTR(cid) | ||
169 | SHOW_AB3100_ATTR(fac) | ||
170 | SHOW_AB3100_ATTR(tac) | ||
171 | SHOW_AB3100_ATTR(svn) | ||
172 | |||
173 | static struct device_attribute ab3100_otp_attrs[] = { | ||
174 | __ATTR(locked, S_IRUGO, ab3100_otp_locked_show, NULL), | ||
175 | __ATTR(freq, S_IRUGO, ab3100_otp_freq_show, NULL), | ||
176 | __ATTR(paf, S_IRUGO, ab3100_otp_paf_show, NULL), | ||
177 | __ATTR(imeich, S_IRUGO, ab3100_otp_imeich_show, NULL), | ||
178 | __ATTR(cid, S_IRUGO, ab3100_otp_cid_show, NULL), | ||
179 | __ATTR(fac, S_IRUGO, ab3100_otp_fac_show, NULL), | ||
180 | __ATTR(tac, S_IRUGO, ab3100_otp_tac_show, NULL), | ||
181 | __ATTR(svn, S_IRUGO, ab3100_otp_svn_show, NULL), | ||
182 | }; | ||
183 | |||
184 | static int __init ab3100_otp_probe(struct platform_device *pdev) | ||
185 | { | ||
186 | struct ab3100_otp *otp; | ||
187 | int err = 0; | ||
188 | int i; | ||
189 | |||
190 | otp = kzalloc(sizeof(struct ab3100_otp), GFP_KERNEL); | ||
191 | if (!otp) { | ||
192 | dev_err(&pdev->dev, "could not allocate AB3100 OTP device\n"); | ||
193 | return -ENOMEM; | ||
194 | } | ||
195 | otp->dev = &pdev->dev; | ||
196 | |||
197 | /* Replace platform data coming in with a local struct */ | ||
198 | otp->ab3100 = platform_get_drvdata(pdev); | ||
199 | platform_set_drvdata(pdev, otp); | ||
200 | |||
201 | err = ab3100_otp_read(otp); | ||
202 | if (err) | ||
203 | return err; | ||
204 | |||
205 | dev_info(&pdev->dev, "AB3100 OTP readout registered\n"); | ||
206 | |||
207 | /* sysfs entries */ | ||
208 | for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++) { | ||
209 | err = device_create_file(&pdev->dev, | ||
210 | &ab3100_otp_attrs[i]); | ||
211 | if (err) | ||
212 | goto out_no_sysfs; | ||
213 | } | ||
214 | |||
215 | /* debugfs entries */ | ||
216 | err = ab3100_otp_init_debugfs(&pdev->dev, otp); | ||
217 | if (err) | ||
218 | goto out_no_debugfs; | ||
219 | |||
220 | return 0; | ||
221 | |||
222 | out_no_sysfs: | ||
223 | for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++) | ||
224 | device_remove_file(&pdev->dev, | ||
225 | &ab3100_otp_attrs[i]); | ||
226 | out_no_debugfs: | ||
227 | kfree(otp); | ||
228 | return err; | ||
229 | } | ||
230 | |||
231 | static int __exit ab3100_otp_remove(struct platform_device *pdev) | ||
232 | { | ||
233 | struct ab3100_otp *otp = platform_get_drvdata(pdev); | ||
234 | int i; | ||
235 | |||
236 | for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++) | ||
237 | device_remove_file(&pdev->dev, | ||
238 | &ab3100_otp_attrs[i]); | ||
239 | ab3100_otp_exit_debugfs(otp); | ||
240 | kfree(otp); | ||
241 | return 0; | ||
242 | } | ||
243 | |||
244 | static struct platform_driver ab3100_otp_driver = { | ||
245 | .driver = { | ||
246 | .name = "ab3100-otp", | ||
247 | .owner = THIS_MODULE, | ||
248 | }, | ||
249 | .remove = __exit_p(ab3100_otp_remove), | ||
250 | }; | ||
251 | |||
252 | static int __init ab3100_otp_init(void) | ||
253 | { | ||
254 | return platform_driver_probe(&ab3100_otp_driver, | ||
255 | ab3100_otp_probe); | ||
256 | } | ||
257 | |||
258 | static void __exit ab3100_otp_exit(void) | ||
259 | { | ||
260 | platform_driver_unregister(&ab3100_otp_driver); | ||
261 | } | ||
262 | |||
263 | module_init(ab3100_otp_init); | ||
264 | module_exit(ab3100_otp_exit); | ||
265 | |||
266 | MODULE_AUTHOR("Linus Walleij <linus.walleij@stericsson.com>"); | ||
267 | MODULE_DESCRIPTION("AB3100 OTP Readout Driver"); | ||
268 | MODULE_LICENSE("GPL"); | ||