Commit | Line | Data |
---|---|---|
4a2addc2 PM |
1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* | |
3 | * JZ4780 EFUSE Memory Support driver | |
4 | * | |
5 | * Copyright (c) 2017 PrasannaKumar Muralidharan <prasannatsmkumar@gmail.com> | |
6 | * Copyright (c) 2020 H. Nikolaus Schaller <hns@goldelico.com> | |
7 | */ | |
8 | ||
9 | /* | |
10 | * Currently supports JZ4780 efuse which has 8K programmable bit. | |
11 | * Efuse is separated into seven segments as below: | |
12 | * | |
13 | * ----------------------------------------------------------------------- | |
14 | * | 64 bit | 128 bit | 128 bit | 3520 bit | 8 bit | 2296 bit | 2048 bit | | |
15 | * ----------------------------------------------------------------------- | |
16 | * | |
17 | * The rom itself is accessed using a 9 bit address line and an 8 word wide bus | |
18 | * which reads/writes based on strobes. The strobe is configured in the config | |
19 | * register and is based on number of cycles of the bus clock. | |
20 | * | |
21 | * Driver supports read only as the writes are done in the Factory. | |
22 | */ | |
23 | ||
24 | #include <linux/bitops.h> | |
25 | #include <linux/clk.h> | |
26 | #include <linux/module.h> | |
27 | #include <linux/nvmem-provider.h> | |
28 | #include <linux/of.h> | |
29 | #include <linux/platform_device.h> | |
30 | #include <linux/regmap.h> | |
31 | #include <linux/timer.h> | |
32 | ||
33 | #define JZ_EFUCTRL (0x0) /* Control Register */ | |
34 | #define JZ_EFUCFG (0x4) /* Configure Register*/ | |
35 | #define JZ_EFUSTATE (0x8) /* Status Register */ | |
36 | #define JZ_EFUDATA(n) (0xC + (n) * 4) | |
37 | ||
38 | /* We read 32 byte chunks to avoid complexity in the driver. */ | |
39 | #define JZ_EFU_READ_SIZE 32 | |
40 | ||
41 | #define EFUCTRL_ADDR_MASK 0x3FF | |
42 | #define EFUCTRL_ADDR_SHIFT 21 | |
43 | #define EFUCTRL_LEN_MASK 0x1F | |
44 | #define EFUCTRL_LEN_SHIFT 16 | |
45 | #define EFUCTRL_PG_EN BIT(15) | |
46 | #define EFUCTRL_WR_EN BIT(1) | |
47 | #define EFUCTRL_RD_EN BIT(0) | |
48 | ||
49 | #define EFUCFG_INT_EN BIT(31) | |
50 | #define EFUCFG_RD_ADJ_MASK 0xF | |
51 | #define EFUCFG_RD_ADJ_SHIFT 20 | |
52 | #define EFUCFG_RD_STR_MASK 0xF | |
53 | #define EFUCFG_RD_STR_SHIFT 16 | |
54 | #define EFUCFG_WR_ADJ_MASK 0xF | |
55 | #define EFUCFG_WR_ADJ_SHIFT 12 | |
56 | #define EFUCFG_WR_STR_MASK 0xFFF | |
57 | #define EFUCFG_WR_STR_SHIFT 0 | |
58 | ||
59 | #define EFUSTATE_WR_DONE BIT(1) | |
60 | #define EFUSTATE_RD_DONE BIT(0) | |
61 | ||
62 | struct jz4780_efuse { | |
63 | struct device *dev; | |
64 | struct regmap *map; | |
65 | struct clk *clk; | |
66 | }; | |
67 | ||
68 | /* main entry point */ | |
69 | static int jz4780_efuse_read(void *context, unsigned int offset, | |
70 | void *val, size_t bytes) | |
71 | { | |
72 | struct jz4780_efuse *efuse = context; | |
73 | ||
74 | while (bytes > 0) { | |
ba2bb5f7 NS |
75 | size_t start = offset & ~(JZ_EFU_READ_SIZE - 1); |
76 | size_t chunk = min(bytes, (start + JZ_EFU_READ_SIZE) | |
77 | - offset); | |
4a2addc2 PM |
78 | char buf[JZ_EFU_READ_SIZE]; |
79 | unsigned int tmp; | |
80 | u32 ctrl; | |
81 | int ret; | |
82 | ||
83 | ctrl = (start << EFUCTRL_ADDR_SHIFT) | |
84 | | ((JZ_EFU_READ_SIZE - 1) << EFUCTRL_LEN_SHIFT) | |
85 | | EFUCTRL_RD_EN; | |
86 | ||
87 | regmap_update_bits(efuse->map, JZ_EFUCTRL, | |
88 | (EFUCTRL_ADDR_MASK << EFUCTRL_ADDR_SHIFT) | | |
89 | (EFUCTRL_LEN_MASK << EFUCTRL_LEN_SHIFT) | | |
90 | EFUCTRL_PG_EN | EFUCTRL_WR_EN | | |
91 | EFUCTRL_RD_EN, | |
92 | ctrl); | |
93 | ||
94 | ret = regmap_read_poll_timeout(efuse->map, JZ_EFUSTATE, | |
95 | tmp, tmp & EFUSTATE_RD_DONE, | |
96 | 1 * MSEC_PER_SEC, | |
97 | 50 * MSEC_PER_SEC); | |
98 | if (ret < 0) { | |
99 | dev_err(efuse->dev, "Time out while reading efuse data"); | |
100 | return ret; | |
101 | } | |
102 | ||
103 | ret = regmap_bulk_read(efuse->map, JZ_EFUDATA(0), | |
104 | buf, JZ_EFU_READ_SIZE / sizeof(u32)); | |
105 | if (ret < 0) | |
106 | return ret; | |
107 | ||
108 | memcpy(val, &buf[offset - start], chunk); | |
109 | ||
110 | val += chunk; | |
111 | offset += chunk; | |
112 | bytes -= chunk; | |
113 | } | |
114 | ||
115 | return 0; | |
116 | } | |
117 | ||
118 | static struct nvmem_config jz4780_efuse_nvmem_config = { | |
119 | .name = "jz4780-efuse", | |
120 | .size = 1024, | |
121 | .word_size = 1, | |
122 | .stride = 1, | |
123 | .owner = THIS_MODULE, | |
124 | .reg_read = jz4780_efuse_read, | |
125 | }; | |
126 | ||
127 | static const struct regmap_config jz4780_efuse_regmap_config = { | |
128 | .reg_bits = 32, | |
129 | .val_bits = 32, | |
130 | .reg_stride = 4, | |
131 | .max_register = JZ_EFUDATA(7), | |
132 | }; | |
133 | ||
134 | static void clk_disable_unprepare_helper(void *clock) | |
135 | { | |
136 | clk_disable_unprepare(clock); | |
137 | } | |
138 | ||
139 | static int jz4780_efuse_probe(struct platform_device *pdev) | |
140 | { | |
141 | struct nvmem_device *nvmem; | |
142 | struct jz4780_efuse *efuse; | |
143 | struct nvmem_config cfg; | |
144 | unsigned long clk_rate; | |
145 | unsigned long rd_adj; | |
146 | unsigned long rd_strobe; | |
147 | struct device *dev = &pdev->dev; | |
148 | void __iomem *regs; | |
149 | int ret; | |
150 | ||
151 | efuse = devm_kzalloc(dev, sizeof(*efuse), GFP_KERNEL); | |
152 | if (!efuse) | |
153 | return -ENOMEM; | |
154 | ||
155 | regs = devm_platform_ioremap_resource(pdev, 0); | |
156 | if (IS_ERR(regs)) | |
157 | return PTR_ERR(regs); | |
158 | ||
159 | efuse->map = devm_regmap_init_mmio(dev, regs, | |
160 | &jz4780_efuse_regmap_config); | |
161 | if (IS_ERR(efuse->map)) | |
162 | return PTR_ERR(efuse->map); | |
163 | ||
164 | efuse->clk = devm_clk_get(&pdev->dev, NULL); | |
165 | if (IS_ERR(efuse->clk)) | |
166 | return PTR_ERR(efuse->clk); | |
167 | ||
168 | ret = clk_prepare_enable(efuse->clk); | |
169 | if (ret < 0) | |
170 | return ret; | |
171 | ||
172 | ret = devm_add_action_or_reset(&pdev->dev, | |
173 | clk_disable_unprepare_helper, | |
174 | efuse->clk); | |
175 | if (ret < 0) | |
176 | return ret; | |
177 | ||
178 | clk_rate = clk_get_rate(efuse->clk); | |
179 | ||
180 | efuse->dev = dev; | |
181 | ||
182 | /* | |
183 | * rd_adj and rd_strobe are 4 bit values | |
184 | * conditions: | |
185 | * bus clk_period * (rd_adj + 1) > 6.5ns | |
186 | * bus clk_period * (rd_adj + 5 + rd_strobe) > 35ns | |
187 | * i.e. rd_adj >= 6.5ns / clk_period | |
188 | * i.e. rd_strobe >= 35 ns / clk_period - 5 - rd_adj + 1 | |
189 | * constants: | |
190 | * 1 / 6.5ns == 153846154 Hz | |
191 | * 1 / 35ns == 28571429 Hz | |
192 | */ | |
193 | ||
194 | rd_adj = clk_rate / 153846154; | |
195 | rd_strobe = clk_rate / 28571429 - 5 - rd_adj + 1; | |
196 | ||
197 | if (rd_adj > EFUCFG_RD_ADJ_MASK || | |
198 | rd_strobe > EFUCFG_RD_STR_MASK) { | |
199 | dev_err(&pdev->dev, "Cannot set clock configuration\n"); | |
200 | return -EINVAL; | |
201 | } | |
202 | ||
203 | regmap_update_bits(efuse->map, JZ_EFUCFG, | |
204 | (EFUCFG_RD_ADJ_MASK << EFUCFG_RD_ADJ_SHIFT) | | |
205 | (EFUCFG_RD_STR_MASK << EFUCFG_RD_STR_SHIFT), | |
206 | (rd_adj << EFUCFG_RD_ADJ_SHIFT) | | |
207 | (rd_strobe << EFUCFG_RD_STR_SHIFT)); | |
208 | ||
209 | cfg = jz4780_efuse_nvmem_config; | |
210 | cfg.dev = &pdev->dev; | |
211 | cfg.priv = efuse; | |
212 | ||
213 | nvmem = devm_nvmem_register(dev, &cfg); | |
4a2addc2 | 214 | |
e270df39 | 215 | return PTR_ERR_OR_ZERO(nvmem); |
4a2addc2 PM |
216 | } |
217 | ||
218 | static const struct of_device_id jz4780_efuse_match[] = { | |
219 | { .compatible = "ingenic,jz4780-efuse" }, | |
220 | { /* sentinel */ }, | |
221 | }; | |
222 | MODULE_DEVICE_TABLE(of, jz4780_efuse_match); | |
223 | ||
224 | static struct platform_driver jz4780_efuse_driver = { | |
225 | .probe = jz4780_efuse_probe, | |
226 | .driver = { | |
227 | .name = "jz4780-efuse", | |
228 | .of_match_table = jz4780_efuse_match, | |
229 | }, | |
230 | }; | |
231 | module_platform_driver(jz4780_efuse_driver); | |
232 | ||
233 | MODULE_AUTHOR("PrasannaKumar Muralidharan <prasannatsmkumar@gmail.com>"); | |
234 | MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>"); | |
235 | MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); | |
236 | MODULE_DESCRIPTION("Ingenic JZ4780 efuse driver"); | |
237 | MODULE_LICENSE("GPL v2"); |