1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Amlogic Meson6, Meson8 and Meson8b eFuse Driver |
4 | * |
5 | * Copyright (c) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com> |
6 | */ |
7 | |
8 | #include <linux/bitfield.h> |
9 | #include <linux/bitops.h> |
10 | #include <linux/clk.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/io.h> |
13 | #include <linux/iopoll.h> |
14 | #include <linux/module.h> |
15 | #include <linux/nvmem-provider.h> |
16 | #include <linux/of.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/sizes.h> |
19 | #include <linux/slab.h> |
20 | |
21 | #define MESON_MX_EFUSE_CNTL1 0x04 |
22 | #define MESON_MX_EFUSE_CNTL1_PD_ENABLE BIT(27) |
23 | #define MESON_MX_EFUSE_CNTL1_AUTO_RD_BUSY BIT(26) |
24 | #define MESON_MX_EFUSE_CNTL1_AUTO_RD_START BIT(25) |
25 | #define MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE BIT(24) |
26 | #define MESON_MX_EFUSE_CNTL1_BYTE_WR_DATA GENMASK(23, 16) |
27 | #define MESON_MX_EFUSE_CNTL1_AUTO_WR_BUSY BIT(14) |
28 | #define MESON_MX_EFUSE_CNTL1_AUTO_WR_START BIT(13) |
29 | #define MESON_MX_EFUSE_CNTL1_AUTO_WR_ENABLE BIT(12) |
30 | #define MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET BIT(11) |
31 | #define MESON_MX_EFUSE_CNTL1_BYTE_ADDR_MASK GENMASK(10, 0) |
32 | |
33 | #define MESON_MX_EFUSE_CNTL2 0x08 |
34 | |
35 | #define MESON_MX_EFUSE_CNTL4 0x10 |
36 | #define MESON_MX_EFUSE_CNTL4_ENCRYPT_ENABLE BIT(10) |
37 | |
38 | struct meson_mx_efuse_platform_data { |
39 | const char *name; |
40 | unsigned int word_size; |
41 | }; |
42 | |
43 | struct meson_mx_efuse { |
44 | void __iomem *base; |
45 | struct clk *core_clk; |
46 | struct nvmem_device *nvmem; |
47 | struct nvmem_config config; |
48 | }; |
49 | |
50 | static void meson_mx_efuse_mask_bits(struct meson_mx_efuse *efuse, u32 reg, |
51 | u32 mask, u32 set) |
52 | { |
53 | u32 data; |
54 | |
55 | data = readl(addr: efuse->base + reg); |
56 | data &= ~mask; |
57 | data |= (set & mask); |
58 | |
59 | writel(val: data, addr: efuse->base + reg); |
60 | } |
61 | |
62 | static int meson_mx_efuse_hw_enable(struct meson_mx_efuse *efuse) |
63 | { |
64 | int err; |
65 | |
66 | err = clk_prepare_enable(clk: efuse->core_clk); |
67 | if (err) |
68 | return err; |
69 | |
70 | /* power up the efuse */ |
71 | meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1, |
72 | MESON_MX_EFUSE_CNTL1_PD_ENABLE, set: 0); |
73 | |
74 | meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL4, |
75 | MESON_MX_EFUSE_CNTL4_ENCRYPT_ENABLE, set: 0); |
76 | |
77 | return 0; |
78 | } |
79 | |
80 | static void meson_mx_efuse_hw_disable(struct meson_mx_efuse *efuse) |
81 | { |
82 | meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1, |
83 | MESON_MX_EFUSE_CNTL1_PD_ENABLE, |
84 | MESON_MX_EFUSE_CNTL1_PD_ENABLE); |
85 | |
86 | clk_disable_unprepare(clk: efuse->core_clk); |
87 | } |
88 | |
89 | static int meson_mx_efuse_read_addr(struct meson_mx_efuse *efuse, |
90 | unsigned int addr, u32 *value) |
91 | { |
92 | int err; |
93 | u32 regval; |
94 | |
95 | /* write the address to read */ |
96 | regval = FIELD_PREP(MESON_MX_EFUSE_CNTL1_BYTE_ADDR_MASK, addr); |
97 | meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1, |
98 | MESON_MX_EFUSE_CNTL1_BYTE_ADDR_MASK, set: regval); |
99 | |
100 | /* inform the hardware that we changed the address */ |
101 | meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1, |
102 | MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET, |
103 | MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET); |
104 | meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1, |
105 | MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET, set: 0); |
106 | |
107 | /* start the read process */ |
108 | meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1, |
109 | MESON_MX_EFUSE_CNTL1_AUTO_RD_START, |
110 | MESON_MX_EFUSE_CNTL1_AUTO_RD_START); |
111 | meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1, |
112 | MESON_MX_EFUSE_CNTL1_AUTO_RD_START, set: 0); |
113 | |
114 | /* |
115 | * perform a dummy read to ensure that the HW has the RD_BUSY bit set |
116 | * when polling for the status below. |
117 | */ |
118 | readl(addr: efuse->base + MESON_MX_EFUSE_CNTL1); |
119 | |
120 | err = readl_poll_timeout_atomic(efuse->base + MESON_MX_EFUSE_CNTL1, |
121 | regval, |
122 | (!(regval & MESON_MX_EFUSE_CNTL1_AUTO_RD_BUSY)), |
123 | 1, 1000); |
124 | if (err) { |
125 | dev_err(efuse->config.dev, |
126 | "Timeout while reading efuse address %u\n" , addr); |
127 | return err; |
128 | } |
129 | |
130 | *value = readl(addr: efuse->base + MESON_MX_EFUSE_CNTL2); |
131 | |
132 | return 0; |
133 | } |
134 | |
135 | static int meson_mx_efuse_read(void *context, unsigned int offset, |
136 | void *buf, size_t bytes) |
137 | { |
138 | struct meson_mx_efuse *efuse = context; |
139 | u32 tmp; |
140 | int err, i, addr; |
141 | |
142 | err = meson_mx_efuse_hw_enable(efuse); |
143 | if (err) |
144 | return err; |
145 | |
146 | meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1, |
147 | MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE, |
148 | MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE); |
149 | |
150 | for (i = 0; i < bytes; i += efuse->config.word_size) { |
151 | addr = (offset + i) / efuse->config.word_size; |
152 | |
153 | err = meson_mx_efuse_read_addr(efuse, addr, value: &tmp); |
154 | if (err) |
155 | break; |
156 | |
157 | memcpy(buf + i, &tmp, |
158 | min_t(size_t, bytes - i, efuse->config.word_size)); |
159 | } |
160 | |
161 | meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1, |
162 | MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE, set: 0); |
163 | |
164 | meson_mx_efuse_hw_disable(efuse); |
165 | |
166 | return err; |
167 | } |
168 | |
169 | static const struct meson_mx_efuse_platform_data meson6_efuse_data = { |
170 | .name = "meson6-efuse" , |
171 | .word_size = 1, |
172 | }; |
173 | |
174 | static const struct meson_mx_efuse_platform_data meson8_efuse_data = { |
175 | .name = "meson8-efuse" , |
176 | .word_size = 4, |
177 | }; |
178 | |
179 | static const struct meson_mx_efuse_platform_data meson8b_efuse_data = { |
180 | .name = "meson8b-efuse" , |
181 | .word_size = 4, |
182 | }; |
183 | |
184 | static const struct of_device_id meson_mx_efuse_match[] = { |
185 | { .compatible = "amlogic,meson6-efuse" , .data = &meson6_efuse_data }, |
186 | { .compatible = "amlogic,meson8-efuse" , .data = &meson8_efuse_data }, |
187 | { .compatible = "amlogic,meson8b-efuse" , .data = &meson8b_efuse_data }, |
188 | { /* sentinel */ }, |
189 | }; |
190 | MODULE_DEVICE_TABLE(of, meson_mx_efuse_match); |
191 | |
192 | static int meson_mx_efuse_probe(struct platform_device *pdev) |
193 | { |
194 | const struct meson_mx_efuse_platform_data *drvdata; |
195 | struct meson_mx_efuse *efuse; |
196 | |
197 | drvdata = of_device_get_match_data(dev: &pdev->dev); |
198 | if (!drvdata) |
199 | return -EINVAL; |
200 | |
201 | efuse = devm_kzalloc(dev: &pdev->dev, size: sizeof(*efuse), GFP_KERNEL); |
202 | if (!efuse) |
203 | return -ENOMEM; |
204 | |
205 | efuse->base = devm_platform_ioremap_resource(pdev, index: 0); |
206 | if (IS_ERR(ptr: efuse->base)) |
207 | return PTR_ERR(ptr: efuse->base); |
208 | |
209 | efuse->config.name = drvdata->name; |
210 | efuse->config.owner = THIS_MODULE; |
211 | efuse->config.dev = &pdev->dev; |
212 | efuse->config.priv = efuse; |
213 | efuse->config.add_legacy_fixed_of_cells = true; |
214 | efuse->config.stride = drvdata->word_size; |
215 | efuse->config.word_size = drvdata->word_size; |
216 | efuse->config.size = SZ_512; |
217 | efuse->config.read_only = true; |
218 | efuse->config.reg_read = meson_mx_efuse_read; |
219 | |
220 | efuse->core_clk = devm_clk_get(dev: &pdev->dev, id: "core" ); |
221 | if (IS_ERR(ptr: efuse->core_clk)) { |
222 | dev_err(&pdev->dev, "Failed to get core clock\n" ); |
223 | return PTR_ERR(ptr: efuse->core_clk); |
224 | } |
225 | |
226 | efuse->nvmem = devm_nvmem_register(dev: &pdev->dev, cfg: &efuse->config); |
227 | |
228 | return PTR_ERR_OR_ZERO(ptr: efuse->nvmem); |
229 | } |
230 | |
231 | static struct platform_driver meson_mx_efuse_driver = { |
232 | .probe = meson_mx_efuse_probe, |
233 | .driver = { |
234 | .name = "meson-mx-efuse" , |
235 | .of_match_table = meson_mx_efuse_match, |
236 | }, |
237 | }; |
238 | |
239 | module_platform_driver(meson_mx_efuse_driver); |
240 | |
241 | MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>" ); |
242 | MODULE_DESCRIPTION("Amlogic Meson MX eFuse NVMEM driver" ); |
243 | MODULE_LICENSE("GPL v2" ); |
244 | |