]>
Commit | Line | Data |
---|---|---|
3edba6b4 PZ |
1 | /* |
2 | * i.MX6 OCOTP fusebox driver | |
3 | * | |
4 | * Copyright (c) 2015 Pengutronix, Philipp Zabel <p.zabel@pengutronix.de> | |
5 | * | |
6 | * Based on the barebox ocotp driver, | |
7 | * Copyright (c) 2010 Baruch Siach <baruch@tkos.co.il>, | |
8 | * Orex Computed Radiography | |
9 | * | |
10 | * This program is free software; you can redistribute it and/or modify | |
11 | * it under the terms of the GNU General Public License version 2 | |
12 | * as published by the Free Software Foundation. | |
13 | * | |
14 | * http://www.opensource.org/licenses/gpl-license.html | |
15 | * http://www.gnu.org/copyleft/gpl.html | |
16 | */ | |
17 | ||
deb31970 | 18 | #include <linux/clk.h> |
3edba6b4 PZ |
19 | #include <linux/device.h> |
20 | #include <linux/io.h> | |
21 | #include <linux/module.h> | |
22 | #include <linux/nvmem-provider.h> | |
23 | #include <linux/of.h> | |
24 | #include <linux/of_device.h> | |
25 | #include <linux/platform_device.h> | |
3edba6b4 PZ |
26 | #include <linux/slab.h> |
27 | ||
9b66587e RL |
28 | #define IMX_OCOTP_OFFSET_B0W0 0x400 /* Offset from base address of the |
29 | * OTP Bank0 Word0 | |
30 | */ | |
31 | #define IMX_OCOTP_OFFSET_PER_WORD 0x10 /* Offset between the start addr | |
32 | * of two consecutive OTP words. | |
33 | */ | |
34 | #define IMX_OCOTP_ADDR_CTRL 0x0000 | |
35 | #define IMX_OCOTP_ADDR_CTRL_CLR 0x0008 | |
36 | ||
37 | #define IMX_OCOTP_BM_CTRL_ERROR 0x00000200 | |
38 | ||
39 | #define IMX_OCOTP_READ_LOCKED_VAL 0xBADABADA | |
40 | ||
3edba6b4 PZ |
41 | struct ocotp_priv { |
42 | struct device *dev; | |
deb31970 | 43 | struct clk *clk; |
3edba6b4 PZ |
44 | void __iomem *base; |
45 | unsigned int nregs; | |
46 | }; | |
47 | ||
9b66587e RL |
48 | static void imx_ocotp_clr_err_if_set(void __iomem *base) |
49 | { | |
50 | u32 c; | |
51 | ||
52 | c = readl(base + IMX_OCOTP_ADDR_CTRL); | |
53 | if (!(c & IMX_OCOTP_BM_CTRL_ERROR)) | |
54 | return; | |
55 | ||
56 | writel(IMX_OCOTP_BM_CTRL_ERROR, base + IMX_OCOTP_ADDR_CTRL_CLR); | |
57 | } | |
58 | ||
33e5e29c SK |
59 | static int imx_ocotp_read(void *context, unsigned int offset, |
60 | void *val, size_t bytes) | |
3edba6b4 PZ |
61 | { |
62 | struct ocotp_priv *priv = context; | |
3edba6b4 | 63 | unsigned int count; |
33e5e29c | 64 | u32 *buf = val; |
deb31970 | 65 | int i, ret; |
3edba6b4 PZ |
66 | u32 index; |
67 | ||
68 | index = offset >> 2; | |
33e5e29c | 69 | count = bytes >> 2; |
3edba6b4 PZ |
70 | |
71 | if (count > (priv->nregs - index)) | |
72 | count = priv->nregs - index; | |
73 | ||
deb31970 PF |
74 | ret = clk_prepare_enable(priv->clk); |
75 | if (ret < 0) { | |
76 | dev_err(priv->dev, "failed to prepare/enable ocotp clk\n"); | |
77 | return ret; | |
78 | } | |
3edba6b4 | 79 | |
9b66587e RL |
80 | for (i = index; i < (index + count); i++) { |
81 | *buf++ = readl(priv->base + IMX_OCOTP_OFFSET_B0W0 + | |
82 | i * IMX_OCOTP_OFFSET_PER_WORD); | |
deb31970 | 83 | |
9b66587e RL |
84 | /* 47.3.1.2 |
85 | * For "read locked" registers 0xBADABADA will be returned and | |
86 | * HW_OCOTP_CTRL[ERROR] will be set. It must be cleared by | |
87 | * software before any new write, read or reload access can be | |
88 | * issued | |
89 | */ | |
90 | if (*(buf - 1) == IMX_OCOTP_READ_LOCKED_VAL) | |
91 | imx_ocotp_clr_err_if_set(priv->base); | |
92 | } | |
93 | ||
94 | clk_disable_unprepare(priv->clk); | |
c7e3c5f8 | 95 | return 0; |
3edba6b4 PZ |
96 | } |
97 | ||
3edba6b4 PZ |
98 | static struct nvmem_config imx_ocotp_nvmem_config = { |
99 | .name = "imx-ocotp", | |
100 | .read_only = true, | |
33e5e29c SK |
101 | .word_size = 4, |
102 | .stride = 4, | |
3edba6b4 | 103 | .owner = THIS_MODULE, |
33e5e29c | 104 | .reg_read = imx_ocotp_read, |
3edba6b4 PZ |
105 | }; |
106 | ||
107 | static const struct of_device_id imx_ocotp_dt_ids[] = { | |
108 | { .compatible = "fsl,imx6q-ocotp", (void *)128 }, | |
14ba9728 | 109 | { .compatible = "fsl,imx6sl-ocotp", (void *)64 }, |
3edba6b4 | 110 | { .compatible = "fsl,imx6sx-ocotp", (void *)128 }, |
4aa2b480 | 111 | { .compatible = "fsl,imx6ul-ocotp", (void *)128 }, |
711d4547 | 112 | { .compatible = "fsl,imx7d-ocotp", (void *)64 }, |
3edba6b4 PZ |
113 | { }, |
114 | }; | |
115 | MODULE_DEVICE_TABLE(of, imx_ocotp_dt_ids); | |
116 | ||
117 | static int imx_ocotp_probe(struct platform_device *pdev) | |
118 | { | |
119 | const struct of_device_id *of_id; | |
120 | struct device *dev = &pdev->dev; | |
121 | struct resource *res; | |
3edba6b4 PZ |
122 | struct ocotp_priv *priv; |
123 | struct nvmem_device *nvmem; | |
124 | ||
125 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
126 | if (!priv) | |
127 | return -ENOMEM; | |
128 | ||
4cefb74a RL |
129 | priv->dev = dev; |
130 | ||
3edba6b4 PZ |
131 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
132 | priv->base = devm_ioremap_resource(dev, res); | |
133 | if (IS_ERR(priv->base)) | |
134 | return PTR_ERR(priv->base); | |
135 | ||
4cefb74a | 136 | priv->clk = devm_clk_get(dev, NULL); |
deb31970 PF |
137 | if (IS_ERR(priv->clk)) |
138 | return PTR_ERR(priv->clk); | |
139 | ||
3edba6b4 | 140 | of_id = of_match_device(imx_ocotp_dt_ids, dev); |
e2402b1d | 141 | priv->nregs = (unsigned long)of_id->data; |
33e5e29c | 142 | imx_ocotp_nvmem_config.size = 4 * priv->nregs; |
3edba6b4 | 143 | imx_ocotp_nvmem_config.dev = dev; |
33e5e29c | 144 | imx_ocotp_nvmem_config.priv = priv; |
3edba6b4 PZ |
145 | nvmem = nvmem_register(&imx_ocotp_nvmem_config); |
146 | if (IS_ERR(nvmem)) | |
147 | return PTR_ERR(nvmem); | |
148 | ||
149 | platform_set_drvdata(pdev, nvmem); | |
150 | ||
151 | return 0; | |
152 | } | |
153 | ||
154 | static int imx_ocotp_remove(struct platform_device *pdev) | |
155 | { | |
156 | struct nvmem_device *nvmem = platform_get_drvdata(pdev); | |
157 | ||
158 | return nvmem_unregister(nvmem); | |
159 | } | |
160 | ||
161 | static struct platform_driver imx_ocotp_driver = { | |
162 | .probe = imx_ocotp_probe, | |
163 | .remove = imx_ocotp_remove, | |
164 | .driver = { | |
165 | .name = "imx_ocotp", | |
166 | .of_match_table = imx_ocotp_dt_ids, | |
167 | }, | |
168 | }; | |
169 | module_platform_driver(imx_ocotp_driver); | |
170 | ||
171 | MODULE_AUTHOR("Philipp Zabel <p.zabel@pengutronix.de>"); | |
172 | MODULE_DESCRIPTION("i.MX6 OCOTP fuse box driver"); | |
173 | MODULE_LICENSE("GPL v2"); |