diff options
author | Stefan Wahren <stefan.wahren@i2se.com> | 2015-09-30 08:56:27 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2015-10-04 07:06:13 -0400 |
commit | c01e9a11ab6f3096a54574c3224d8732a374f135 (patch) | |
tree | aac8f37859f835c6d341796e516a09d79b7ad238 /drivers/nvmem | |
parent | fb86de91c2a48e320bfa3767802d9a1fb204a230 (diff) |
nvmem: add driver for ocotp in i.MX23 and i.MX28
This patch brings read-only support for the On-Chip OTP cells
in the i.MX23 and i.MX28 processor. The driver implements the
new NVMEM provider API.
Signed-off-by: Stefan Wahren <stefan.wahren@i2se.com>
Reviewed-by: Marek Vasut <marex@denx.de>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/nvmem')
-rw-r--r-- | drivers/nvmem/Kconfig | 11 | ||||
-rw-r--r-- | drivers/nvmem/Makefile | 2 | ||||
-rw-r--r-- | drivers/nvmem/mxs-ocotp.c | 257 |
3 files changed, 270 insertions, 0 deletions
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index 208a1cb6e4bf..a6d80536fd58 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig | |||
@@ -25,6 +25,17 @@ config NVMEM_IMX_OCOTP | |||
25 | This driver can also be built as a module. If so, the module | 25 | This driver can also be built as a module. If so, the module |
26 | will be called nvmem-imx-ocotp. | 26 | will be called nvmem-imx-ocotp. |
27 | 27 | ||
28 | config NVMEM_MXS_OCOTP | ||
29 | tristate "Freescale MXS On-Chip OTP Memory Support" | ||
30 | depends on ARCH_MXS || COMPILE_TEST | ||
31 | help | ||
32 | If you say Y here, you will get readonly access to the | ||
33 | One Time Programmable memory pages that are stored | ||
34 | on the Freescale i.MX23/i.MX28 processor. | ||
35 | |||
36 | This driver can also be built as a module. If so, the module | ||
37 | will be called nvmem-mxs-ocotp. | ||
38 | |||
28 | config QCOM_QFPROM | 39 | config QCOM_QFPROM |
29 | tristate "QCOM QFPROM Support" | 40 | tristate "QCOM QFPROM Support" |
30 | depends on ARCH_QCOM || COMPILE_TEST | 41 | depends on ARCH_QCOM || COMPILE_TEST |
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index 2ddf0e88af3c..9322116d21ec 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile | |||
@@ -8,6 +8,8 @@ nvmem_core-y := core.o | |||
8 | # Devices | 8 | # Devices |
9 | obj-$(CONFIG_NVMEM_IMX_OCOTP) += nvmem-imx-ocotp.o | 9 | obj-$(CONFIG_NVMEM_IMX_OCOTP) += nvmem-imx-ocotp.o |
10 | nvmem-imx-ocotp-y := imx-ocotp.o | 10 | nvmem-imx-ocotp-y := imx-ocotp.o |
11 | obj-$(CONFIG_NVMEM_MXS_OCOTP) += nvmem-mxs-ocotp.o | ||
12 | nvmem-mxs-ocotp-y := mxs-ocotp.o | ||
11 | obj-$(CONFIG_QCOM_QFPROM) += nvmem_qfprom.o | 13 | obj-$(CONFIG_QCOM_QFPROM) += nvmem_qfprom.o |
12 | nvmem_qfprom-y := qfprom.o | 14 | nvmem_qfprom-y := qfprom.o |
13 | obj-$(CONFIG_NVMEM_SUNXI_SID) += nvmem_sunxi_sid.o | 15 | obj-$(CONFIG_NVMEM_SUNXI_SID) += nvmem_sunxi_sid.o |
diff --git a/drivers/nvmem/mxs-ocotp.c b/drivers/nvmem/mxs-ocotp.c new file mode 100644 index 000000000000..8ba19bba3156 --- /dev/null +++ b/drivers/nvmem/mxs-ocotp.c | |||
@@ -0,0 +1,257 @@ | |||
1 | /* | ||
2 | * Freescale MXS On-Chip OTP driver | ||
3 | * | ||
4 | * Copyright (C) 2015 Stefan Wahren <stefan.wahren@i2se.com> | ||
5 | * | ||
6 | * Based on the driver from Huang Shijie and Christoph G. Baumann | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | ||
17 | * | ||
18 | */ | ||
19 | #include <linux/clk.h> | ||
20 | #include <linux/delay.h> | ||
21 | #include <linux/device.h> | ||
22 | #include <linux/err.h> | ||
23 | #include <linux/io.h> | ||
24 | #include <linux/module.h> | ||
25 | #include <linux/nvmem-provider.h> | ||
26 | #include <linux/of_device.h> | ||
27 | #include <linux/platform_device.h> | ||
28 | #include <linux/regmap.h> | ||
29 | #include <linux/slab.h> | ||
30 | #include <linux/stmp_device.h> | ||
31 | |||
32 | /* OCOTP registers and bits */ | ||
33 | |||
34 | #define BM_OCOTP_CTRL_RD_BANK_OPEN BIT(12) | ||
35 | #define BM_OCOTP_CTRL_ERROR BIT(9) | ||
36 | #define BM_OCOTP_CTRL_BUSY BIT(8) | ||
37 | |||
38 | #define OCOTP_TIMEOUT 10000 | ||
39 | #define OCOTP_DATA_OFFSET 0x20 | ||
40 | |||
41 | struct mxs_ocotp { | ||
42 | struct clk *clk; | ||
43 | void __iomem *base; | ||
44 | struct nvmem_device *nvmem; | ||
45 | }; | ||
46 | |||
47 | static int mxs_ocotp_wait(struct mxs_ocotp *otp) | ||
48 | { | ||
49 | int timeout = OCOTP_TIMEOUT; | ||
50 | unsigned int status = 0; | ||
51 | |||
52 | while (timeout--) { | ||
53 | status = readl(otp->base); | ||
54 | |||
55 | if (!(status & (BM_OCOTP_CTRL_BUSY | BM_OCOTP_CTRL_ERROR))) | ||
56 | break; | ||
57 | |||
58 | cpu_relax(); | ||
59 | } | ||
60 | |||
61 | if (status & BM_OCOTP_CTRL_BUSY) | ||
62 | return -EBUSY; | ||
63 | else if (status & BM_OCOTP_CTRL_ERROR) | ||
64 | return -EIO; | ||
65 | |||
66 | return 0; | ||
67 | } | ||
68 | |||
69 | static int mxs_ocotp_read(void *context, const void *reg, size_t reg_size, | ||
70 | void *val, size_t val_size) | ||
71 | { | ||
72 | struct mxs_ocotp *otp = context; | ||
73 | unsigned int offset = *(u32 *)reg; | ||
74 | u32 *buf = val; | ||
75 | int ret; | ||
76 | |||
77 | ret = clk_enable(otp->clk); | ||
78 | if (ret) | ||
79 | return ret; | ||
80 | |||
81 | writel(BM_OCOTP_CTRL_ERROR, otp->base + STMP_OFFSET_REG_CLR); | ||
82 | |||
83 | ret = mxs_ocotp_wait(otp); | ||
84 | if (ret) | ||
85 | goto disable_clk; | ||
86 | |||
87 | /* open OCOTP banks for read */ | ||
88 | writel(BM_OCOTP_CTRL_RD_BANK_OPEN, otp->base + STMP_OFFSET_REG_SET); | ||
89 | |||
90 | /* approximately wait 33 hclk cycles */ | ||
91 | udelay(1); | ||
92 | |||
93 | ret = mxs_ocotp_wait(otp); | ||
94 | if (ret) | ||
95 | goto close_banks; | ||
96 | |||
97 | while (val_size) { | ||
98 | if ((offset < OCOTP_DATA_OFFSET) || (offset % 16)) { | ||
99 | /* fill up non-data register */ | ||
100 | *buf = 0; | ||
101 | } else { | ||
102 | *buf = readl(otp->base + offset); | ||
103 | } | ||
104 | |||
105 | buf++; | ||
106 | val_size--; | ||
107 | offset += reg_size; | ||
108 | } | ||
109 | |||
110 | close_banks: | ||
111 | /* close banks for power saving */ | ||
112 | writel(BM_OCOTP_CTRL_RD_BANK_OPEN, otp->base + STMP_OFFSET_REG_CLR); | ||
113 | |||
114 | disable_clk: | ||
115 | clk_disable(otp->clk); | ||
116 | |||
117 | return ret; | ||
118 | } | ||
119 | |||
120 | static int mxs_ocotp_write(void *context, const void *data, size_t count) | ||
121 | { | ||
122 | /* We don't want to support writing */ | ||
123 | return 0; | ||
124 | } | ||
125 | |||
126 | static bool mxs_ocotp_writeable_reg(struct device *dev, unsigned int reg) | ||
127 | { | ||
128 | return false; | ||
129 | } | ||
130 | |||
131 | static struct nvmem_config ocotp_config = { | ||
132 | .name = "mxs-ocotp", | ||
133 | .owner = THIS_MODULE, | ||
134 | }; | ||
135 | |||
136 | static const struct regmap_range imx23_ranges[] = { | ||
137 | regmap_reg_range(OCOTP_DATA_OFFSET, 0x210), | ||
138 | }; | ||
139 | |||
140 | static const struct regmap_access_table imx23_access = { | ||
141 | .yes_ranges = imx23_ranges, | ||
142 | .n_yes_ranges = ARRAY_SIZE(imx23_ranges), | ||
143 | }; | ||
144 | |||
145 | static const struct regmap_range imx28_ranges[] = { | ||
146 | regmap_reg_range(OCOTP_DATA_OFFSET, 0x290), | ||
147 | }; | ||
148 | |||
149 | static const struct regmap_access_table imx28_access = { | ||
150 | .yes_ranges = imx28_ranges, | ||
151 | .n_yes_ranges = ARRAY_SIZE(imx28_ranges), | ||
152 | }; | ||
153 | |||
154 | static struct regmap_bus mxs_ocotp_bus = { | ||
155 | .read = mxs_ocotp_read, | ||
156 | .write = mxs_ocotp_write, /* make regmap_init() happy */ | ||
157 | .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, | ||
158 | .val_format_endian_default = REGMAP_ENDIAN_NATIVE, | ||
159 | }; | ||
160 | |||
161 | static struct regmap_config mxs_ocotp_config = { | ||
162 | .reg_bits = 32, | ||
163 | .val_bits = 32, | ||
164 | .reg_stride = 16, | ||
165 | .writeable_reg = mxs_ocotp_writeable_reg, | ||
166 | }; | ||
167 | |||
168 | static const struct of_device_id mxs_ocotp_match[] = { | ||
169 | { .compatible = "fsl,imx23-ocotp", .data = &imx23_access }, | ||
170 | { .compatible = "fsl,imx28-ocotp", .data = &imx28_access }, | ||
171 | { /* sentinel */}, | ||
172 | }; | ||
173 | MODULE_DEVICE_TABLE(of, mxs_ocotp_match); | ||
174 | |||
175 | static int mxs_ocotp_probe(struct platform_device *pdev) | ||
176 | { | ||
177 | struct device *dev = &pdev->dev; | ||
178 | struct mxs_ocotp *otp; | ||
179 | struct resource *res; | ||
180 | const struct of_device_id *match; | ||
181 | struct regmap *regmap; | ||
182 | const struct regmap_access_table *access; | ||
183 | int ret; | ||
184 | |||
185 | match = of_match_device(dev->driver->of_match_table, dev); | ||
186 | if (!match || !match->data) | ||
187 | return -EINVAL; | ||
188 | |||
189 | otp = devm_kzalloc(dev, sizeof(*otp), GFP_KERNEL); | ||
190 | if (!otp) | ||
191 | return -ENOMEM; | ||
192 | |||
193 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
194 | otp->base = devm_ioremap_resource(dev, res); | ||
195 | if (IS_ERR(otp->base)) | ||
196 | return PTR_ERR(otp->base); | ||
197 | |||
198 | otp->clk = devm_clk_get(&pdev->dev, NULL); | ||
199 | if (IS_ERR(otp->clk)) | ||
200 | return PTR_ERR(otp->clk); | ||
201 | |||
202 | ret = clk_prepare(otp->clk); | ||
203 | if (ret < 0) { | ||
204 | dev_err(dev, "failed to prepare clk: %d\n", ret); | ||
205 | return ret; | ||
206 | } | ||
207 | |||
208 | access = match->data; | ||
209 | mxs_ocotp_config.rd_table = access; | ||
210 | mxs_ocotp_config.max_register = access->yes_ranges[0].range_max; | ||
211 | |||
212 | regmap = devm_regmap_init(dev, &mxs_ocotp_bus, otp, &mxs_ocotp_config); | ||
213 | if (IS_ERR(regmap)) { | ||
214 | dev_err(dev, "regmap init failed\n"); | ||
215 | ret = PTR_ERR(regmap); | ||
216 | goto err_clk; | ||
217 | } | ||
218 | |||
219 | ocotp_config.dev = dev; | ||
220 | otp->nvmem = nvmem_register(&ocotp_config); | ||
221 | if (IS_ERR(otp->nvmem)) { | ||
222 | ret = PTR_ERR(otp->nvmem); | ||
223 | goto err_clk; | ||
224 | } | ||
225 | |||
226 | platform_set_drvdata(pdev, otp); | ||
227 | |||
228 | return 0; | ||
229 | |||
230 | err_clk: | ||
231 | clk_unprepare(otp->clk); | ||
232 | |||
233 | return ret; | ||
234 | } | ||
235 | |||
236 | static int mxs_ocotp_remove(struct platform_device *pdev) | ||
237 | { | ||
238 | struct mxs_ocotp *otp = platform_get_drvdata(pdev); | ||
239 | |||
240 | clk_unprepare(otp->clk); | ||
241 | |||
242 | return nvmem_unregister(otp->nvmem); | ||
243 | } | ||
244 | |||
245 | static struct platform_driver mxs_ocotp_driver = { | ||
246 | .probe = mxs_ocotp_probe, | ||
247 | .remove = mxs_ocotp_remove, | ||
248 | .driver = { | ||
249 | .name = "mxs-ocotp", | ||
250 | .of_match_table = mxs_ocotp_match, | ||
251 | }, | ||
252 | }; | ||
253 | |||
254 | module_platform_driver(mxs_ocotp_driver); | ||
255 | MODULE_AUTHOR("Stefan Wahren <stefan.wahren@i2se.com>"); | ||
256 | MODULE_DESCRIPTION("driver for OCOTP in i.MX23/i.MX28"); | ||
257 | MODULE_LICENSE("GPL v2"); | ||