1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2f02f8aeeSAriel D'Alessandro /*
3f02f8aeeSAriel D'Alessandro  * NXP LPC18xx/LPC43xx EEPROM memory NVMEM driver
4f02f8aeeSAriel D'Alessandro  *
5f02f8aeeSAriel D'Alessandro  * Copyright (c) 2015 Ariel D'Alessandro <ariel@vanguardiasur.com>
6f02f8aeeSAriel D'Alessandro  */
7f02f8aeeSAriel D'Alessandro 
8f02f8aeeSAriel D'Alessandro #include <linux/clk.h>
9f02f8aeeSAriel D'Alessandro #include <linux/device.h>
10f02f8aeeSAriel D'Alessandro #include <linux/delay.h>
11f02f8aeeSAriel D'Alessandro #include <linux/err.h>
12f02f8aeeSAriel D'Alessandro #include <linux/io.h>
13f02f8aeeSAriel D'Alessandro #include <linux/module.h>
14ac316725SRandy Dunlap #include <linux/mod_devicetable.h>
15f02f8aeeSAriel D'Alessandro #include <linux/nvmem-provider.h>
16f02f8aeeSAriel D'Alessandro #include <linux/platform_device.h>
17f02f8aeeSAriel D'Alessandro #include <linux/reset.h>
18f02f8aeeSAriel D'Alessandro 
19f02f8aeeSAriel D'Alessandro /* Registers */
20f02f8aeeSAriel D'Alessandro #define LPC18XX_EEPROM_AUTOPROG			0x00c
21f02f8aeeSAriel D'Alessandro #define LPC18XX_EEPROM_AUTOPROG_WORD		0x1
22f02f8aeeSAriel D'Alessandro 
23f02f8aeeSAriel D'Alessandro #define LPC18XX_EEPROM_CLKDIV			0x014
24f02f8aeeSAriel D'Alessandro 
25f02f8aeeSAriel D'Alessandro #define LPC18XX_EEPROM_PWRDWN			0x018
26f02f8aeeSAriel D'Alessandro #define LPC18XX_EEPROM_PWRDWN_NO		0x0
27f02f8aeeSAriel D'Alessandro #define LPC18XX_EEPROM_PWRDWN_YES		0x1
28f02f8aeeSAriel D'Alessandro 
29f02f8aeeSAriel D'Alessandro #define LPC18XX_EEPROM_INTSTAT			0xfe0
30f02f8aeeSAriel D'Alessandro #define LPC18XX_EEPROM_INTSTAT_END_OF_PROG	BIT(2)
31f02f8aeeSAriel D'Alessandro 
32f02f8aeeSAriel D'Alessandro #define LPC18XX_EEPROM_INTSTATCLR		0xfe8
33f02f8aeeSAriel D'Alessandro #define LPC18XX_EEPROM_INTSTATCLR_PROG_CLR_ST	BIT(2)
34f02f8aeeSAriel D'Alessandro 
35f02f8aeeSAriel D'Alessandro /* Fixed page size (bytes) */
36f02f8aeeSAriel D'Alessandro #define LPC18XX_EEPROM_PAGE_SIZE		0x80
37f02f8aeeSAriel D'Alessandro 
38f02f8aeeSAriel D'Alessandro /* EEPROM device requires a ~1500 kHz clock (min 800 kHz, max 1600 kHz) */
39f02f8aeeSAriel D'Alessandro #define LPC18XX_EEPROM_CLOCK_HZ			1500000
40f02f8aeeSAriel D'Alessandro 
41f02f8aeeSAriel D'Alessandro /* EEPROM requires 3 ms of erase/program time between each writing */
42f02f8aeeSAriel D'Alessandro #define LPC18XX_EEPROM_PROGRAM_TIME		3
43f02f8aeeSAriel D'Alessandro 
44f02f8aeeSAriel D'Alessandro struct lpc18xx_eeprom_dev {
45f02f8aeeSAriel D'Alessandro 	struct clk *clk;
46f02f8aeeSAriel D'Alessandro 	void __iomem *reg_base;
47f02f8aeeSAriel D'Alessandro 	void __iomem *mem_base;
48f02f8aeeSAriel D'Alessandro 	struct nvmem_device *nvmem;
49f02f8aeeSAriel D'Alessandro 	unsigned reg_bytes;
50f02f8aeeSAriel D'Alessandro 	unsigned val_bytes;
512e8d0733SSrinivas Kandagatla 	int size;
52f02f8aeeSAriel D'Alessandro };
53f02f8aeeSAriel D'Alessandro 
lpc18xx_eeprom_writel(struct lpc18xx_eeprom_dev * eeprom,u32 reg,u32 val)54f02f8aeeSAriel D'Alessandro static inline void lpc18xx_eeprom_writel(struct lpc18xx_eeprom_dev *eeprom,
55f02f8aeeSAriel D'Alessandro 					 u32 reg, u32 val)
56f02f8aeeSAriel D'Alessandro {
57f02f8aeeSAriel D'Alessandro 	writel(val, eeprom->reg_base + reg);
58f02f8aeeSAriel D'Alessandro }
59f02f8aeeSAriel D'Alessandro 
lpc18xx_eeprom_readl(struct lpc18xx_eeprom_dev * eeprom,u32 reg)60f02f8aeeSAriel D'Alessandro static inline u32 lpc18xx_eeprom_readl(struct lpc18xx_eeprom_dev *eeprom,
61f02f8aeeSAriel D'Alessandro 				       u32 reg)
62f02f8aeeSAriel D'Alessandro {
63f02f8aeeSAriel D'Alessandro 	return readl(eeprom->reg_base + reg);
64f02f8aeeSAriel D'Alessandro }
65f02f8aeeSAriel D'Alessandro 
lpc18xx_eeprom_busywait_until_prog(struct lpc18xx_eeprom_dev * eeprom)66f02f8aeeSAriel D'Alessandro static int lpc18xx_eeprom_busywait_until_prog(struct lpc18xx_eeprom_dev *eeprom)
67f02f8aeeSAriel D'Alessandro {
68f02f8aeeSAriel D'Alessandro 	unsigned long end;
69f02f8aeeSAriel D'Alessandro 	u32 val;
70f02f8aeeSAriel D'Alessandro 
71f02f8aeeSAriel D'Alessandro 	/* Wait until EEPROM program operation has finished */
72f02f8aeeSAriel D'Alessandro 	end = jiffies + msecs_to_jiffies(LPC18XX_EEPROM_PROGRAM_TIME * 10);
73f02f8aeeSAriel D'Alessandro 
74f02f8aeeSAriel D'Alessandro 	while (time_is_after_jiffies(end)) {
75f02f8aeeSAriel D'Alessandro 		val = lpc18xx_eeprom_readl(eeprom, LPC18XX_EEPROM_INTSTAT);
76f02f8aeeSAriel D'Alessandro 
77f02f8aeeSAriel D'Alessandro 		if (val & LPC18XX_EEPROM_INTSTAT_END_OF_PROG) {
78f02f8aeeSAriel D'Alessandro 			lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_INTSTATCLR,
79f02f8aeeSAriel D'Alessandro 					LPC18XX_EEPROM_INTSTATCLR_PROG_CLR_ST);
80f02f8aeeSAriel D'Alessandro 			return 0;
81f02f8aeeSAriel D'Alessandro 		}
82f02f8aeeSAriel D'Alessandro 
83f02f8aeeSAriel D'Alessandro 		usleep_range(LPC18XX_EEPROM_PROGRAM_TIME * USEC_PER_MSEC,
84f02f8aeeSAriel D'Alessandro 			     (LPC18XX_EEPROM_PROGRAM_TIME + 1) * USEC_PER_MSEC);
85f02f8aeeSAriel D'Alessandro 	}
86f02f8aeeSAriel D'Alessandro 
87f02f8aeeSAriel D'Alessandro 	return -ETIMEDOUT;
88f02f8aeeSAriel D'Alessandro }
89f02f8aeeSAriel D'Alessandro 
lpc18xx_eeprom_gather_write(void * context,unsigned int reg,void * val,size_t bytes)902e8d0733SSrinivas Kandagatla static int lpc18xx_eeprom_gather_write(void *context, unsigned int reg,
912e8d0733SSrinivas Kandagatla 				       void *val, size_t bytes)
92f02f8aeeSAriel D'Alessandro {
93f02f8aeeSAriel D'Alessandro 	struct lpc18xx_eeprom_dev *eeprom = context;
942e8d0733SSrinivas Kandagatla 	unsigned int offset = reg;
95f02f8aeeSAriel D'Alessandro 	int ret;
96f02f8aeeSAriel D'Alessandro 
972e8d0733SSrinivas Kandagatla 	/*
982e8d0733SSrinivas Kandagatla 	 * The last page contains the EEPROM initialization data and is not
992e8d0733SSrinivas Kandagatla 	 * writable.
1002e8d0733SSrinivas Kandagatla 	 */
1012e8d0733SSrinivas Kandagatla 	if ((reg > eeprom->size - LPC18XX_EEPROM_PAGE_SIZE) ||
1022e8d0733SSrinivas Kandagatla 			(reg + bytes > eeprom->size - LPC18XX_EEPROM_PAGE_SIZE))
103f02f8aeeSAriel D'Alessandro 		return -EINVAL;
104f02f8aeeSAriel D'Alessandro 
1052e8d0733SSrinivas Kandagatla 
106f02f8aeeSAriel D'Alessandro 	lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_PWRDWN,
107f02f8aeeSAriel D'Alessandro 			      LPC18XX_EEPROM_PWRDWN_NO);
108f02f8aeeSAriel D'Alessandro 
109f02f8aeeSAriel D'Alessandro 	/* Wait 100 us while the EEPROM wakes up */
110f02f8aeeSAriel D'Alessandro 	usleep_range(100, 200);
111f02f8aeeSAriel D'Alessandro 
1122e8d0733SSrinivas Kandagatla 	while (bytes) {
113f02f8aeeSAriel D'Alessandro 		writel(*(u32 *)val, eeprom->mem_base + offset);
114f02f8aeeSAriel D'Alessandro 		ret = lpc18xx_eeprom_busywait_until_prog(eeprom);
115f02f8aeeSAriel D'Alessandro 		if (ret < 0)
116f02f8aeeSAriel D'Alessandro 			return ret;
117f02f8aeeSAriel D'Alessandro 
1182e8d0733SSrinivas Kandagatla 		bytes -= eeprom->val_bytes;
119f02f8aeeSAriel D'Alessandro 		val += eeprom->val_bytes;
120f02f8aeeSAriel D'Alessandro 		offset += eeprom->val_bytes;
121f02f8aeeSAriel D'Alessandro 	}
122f02f8aeeSAriel D'Alessandro 
123f02f8aeeSAriel D'Alessandro 	lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_PWRDWN,
124f02f8aeeSAriel D'Alessandro 			      LPC18XX_EEPROM_PWRDWN_YES);
125f02f8aeeSAriel D'Alessandro 
126f02f8aeeSAriel D'Alessandro 	return 0;
127f02f8aeeSAriel D'Alessandro }
128f02f8aeeSAriel D'Alessandro 
lpc18xx_eeprom_read(void * context,unsigned int offset,void * val,size_t bytes)1292e8d0733SSrinivas Kandagatla static int lpc18xx_eeprom_read(void *context, unsigned int offset,
1302e8d0733SSrinivas Kandagatla 			       void *val, size_t bytes)
131f02f8aeeSAriel D'Alessandro {
132f02f8aeeSAriel D'Alessandro 	struct lpc18xx_eeprom_dev *eeprom = context;
133f02f8aeeSAriel D'Alessandro 
134f02f8aeeSAriel D'Alessandro 	lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_PWRDWN,
135f02f8aeeSAriel D'Alessandro 			      LPC18XX_EEPROM_PWRDWN_NO);
136f02f8aeeSAriel D'Alessandro 
137f02f8aeeSAriel D'Alessandro 	/* Wait 100 us while the EEPROM wakes up */
138f02f8aeeSAriel D'Alessandro 	usleep_range(100, 200);
139f02f8aeeSAriel D'Alessandro 
1402e8d0733SSrinivas Kandagatla 	while (bytes) {
141f02f8aeeSAriel D'Alessandro 		*(u32 *)val = readl(eeprom->mem_base + offset);
1422e8d0733SSrinivas Kandagatla 		bytes -= eeprom->val_bytes;
143f02f8aeeSAriel D'Alessandro 		val += eeprom->val_bytes;
144f02f8aeeSAriel D'Alessandro 		offset += eeprom->val_bytes;
145f02f8aeeSAriel D'Alessandro 	}
146f02f8aeeSAriel D'Alessandro 
147f02f8aeeSAriel D'Alessandro 	lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_PWRDWN,
148f02f8aeeSAriel D'Alessandro 			      LPC18XX_EEPROM_PWRDWN_YES);
149f02f8aeeSAriel D'Alessandro 
150f02f8aeeSAriel D'Alessandro 	return 0;
151f02f8aeeSAriel D'Alessandro }
152f02f8aeeSAriel D'Alessandro 
153f02f8aeeSAriel D'Alessandro 
154f02f8aeeSAriel D'Alessandro static struct nvmem_config lpc18xx_nvmem_config = {
155f02f8aeeSAriel D'Alessandro 	.name = "lpc18xx-eeprom",
1562e8d0733SSrinivas Kandagatla 	.stride = 4,
1572e8d0733SSrinivas Kandagatla 	.word_size = 4,
1582e8d0733SSrinivas Kandagatla 	.reg_read = lpc18xx_eeprom_read,
1592e8d0733SSrinivas Kandagatla 	.reg_write = lpc18xx_eeprom_gather_write,
160f02f8aeeSAriel D'Alessandro };
161f02f8aeeSAriel D'Alessandro 
lpc18xx_eeprom_probe(struct platform_device * pdev)162f02f8aeeSAriel D'Alessandro static int lpc18xx_eeprom_probe(struct platform_device *pdev)
163f02f8aeeSAriel D'Alessandro {
164f02f8aeeSAriel D'Alessandro 	struct lpc18xx_eeprom_dev *eeprom;
165f02f8aeeSAriel D'Alessandro 	struct device *dev = &pdev->dev;
166f02f8aeeSAriel D'Alessandro 	struct reset_control *rst;
167f02f8aeeSAriel D'Alessandro 	unsigned long clk_rate;
168f02f8aeeSAriel D'Alessandro 	struct resource *res;
169f02f8aeeSAriel D'Alessandro 	int ret;
170f02f8aeeSAriel D'Alessandro 
171f02f8aeeSAriel D'Alessandro 	eeprom = devm_kzalloc(dev, sizeof(*eeprom), GFP_KERNEL);
172f02f8aeeSAriel D'Alessandro 	if (!eeprom)
173f02f8aeeSAriel D'Alessandro 		return -ENOMEM;
174f02f8aeeSAriel D'Alessandro 
175f02f8aeeSAriel D'Alessandro 	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "reg");
176f02f8aeeSAriel D'Alessandro 	eeprom->reg_base = devm_ioremap_resource(dev, res);
177f02f8aeeSAriel D'Alessandro 	if (IS_ERR(eeprom->reg_base))
178f02f8aeeSAriel D'Alessandro 		return PTR_ERR(eeprom->reg_base);
179f02f8aeeSAriel D'Alessandro 
180f02f8aeeSAriel D'Alessandro 	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mem");
181f02f8aeeSAriel D'Alessandro 	eeprom->mem_base = devm_ioremap_resource(dev, res);
182f02f8aeeSAriel D'Alessandro 	if (IS_ERR(eeprom->mem_base))
183f02f8aeeSAriel D'Alessandro 		return PTR_ERR(eeprom->mem_base);
184f02f8aeeSAriel D'Alessandro 
185f02f8aeeSAriel D'Alessandro 	eeprom->clk = devm_clk_get(&pdev->dev, "eeprom");
186f02f8aeeSAriel D'Alessandro 	if (IS_ERR(eeprom->clk)) {
187f02f8aeeSAriel D'Alessandro 		dev_err(&pdev->dev, "failed to get eeprom clock\n");
188f02f8aeeSAriel D'Alessandro 		return PTR_ERR(eeprom->clk);
189f02f8aeeSAriel D'Alessandro 	}
190f02f8aeeSAriel D'Alessandro 
191f02f8aeeSAriel D'Alessandro 	ret = clk_prepare_enable(eeprom->clk);
192f02f8aeeSAriel D'Alessandro 	if (ret < 0) {
193f02f8aeeSAriel D'Alessandro 		dev_err(dev, "failed to prepare/enable eeprom clk: %d\n", ret);
194f02f8aeeSAriel D'Alessandro 		return ret;
195f02f8aeeSAriel D'Alessandro 	}
196f02f8aeeSAriel D'Alessandro 
197aed0c46eSPhilipp Zabel 	rst = devm_reset_control_get_exclusive(dev, NULL);
198f02f8aeeSAriel D'Alessandro 	if (IS_ERR(rst)) {
199f02f8aeeSAriel D'Alessandro 		dev_err(dev, "failed to get reset: %ld\n", PTR_ERR(rst));
200f02f8aeeSAriel D'Alessandro 		ret = PTR_ERR(rst);
201f02f8aeeSAriel D'Alessandro 		goto err_clk;
202f02f8aeeSAriel D'Alessandro 	}
203f02f8aeeSAriel D'Alessandro 
204f02f8aeeSAriel D'Alessandro 	ret = reset_control_assert(rst);
205f02f8aeeSAriel D'Alessandro 	if (ret < 0) {
206f02f8aeeSAriel D'Alessandro 		dev_err(dev, "failed to assert reset: %d\n", ret);
207f02f8aeeSAriel D'Alessandro 		goto err_clk;
208f02f8aeeSAriel D'Alessandro 	}
209f02f8aeeSAriel D'Alessandro 
2102e8d0733SSrinivas Kandagatla 	eeprom->val_bytes = 4;
2112e8d0733SSrinivas Kandagatla 	eeprom->reg_bytes = 4;
212f02f8aeeSAriel D'Alessandro 
213f02f8aeeSAriel D'Alessandro 	/*
214f02f8aeeSAriel D'Alessandro 	 * Clock rate is generated by dividing the system bus clock by the
215f02f8aeeSAriel D'Alessandro 	 * division factor, contained in the divider register (minus 1 encoded).
216f02f8aeeSAriel D'Alessandro 	 */
217f02f8aeeSAriel D'Alessandro 	clk_rate = clk_get_rate(eeprom->clk);
218f02f8aeeSAriel D'Alessandro 	clk_rate = DIV_ROUND_UP(clk_rate, LPC18XX_EEPROM_CLOCK_HZ) - 1;
219f02f8aeeSAriel D'Alessandro 	lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_CLKDIV, clk_rate);
220f02f8aeeSAriel D'Alessandro 
221f02f8aeeSAriel D'Alessandro 	/*
222f02f8aeeSAriel D'Alessandro 	 * Writing a single word to the page will start the erase/program cycle
223f02f8aeeSAriel D'Alessandro 	 * automatically
224f02f8aeeSAriel D'Alessandro 	 */
225f02f8aeeSAriel D'Alessandro 	lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_AUTOPROG,
226f02f8aeeSAriel D'Alessandro 			      LPC18XX_EEPROM_AUTOPROG_WORD);
227f02f8aeeSAriel D'Alessandro 
228f02f8aeeSAriel D'Alessandro 	lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_PWRDWN,
229f02f8aeeSAriel D'Alessandro 			      LPC18XX_EEPROM_PWRDWN_YES);
230f02f8aeeSAriel D'Alessandro 
2312e8d0733SSrinivas Kandagatla 	eeprom->size = resource_size(res);
2322e8d0733SSrinivas Kandagatla 	lpc18xx_nvmem_config.size = resource_size(res);
233f02f8aeeSAriel D'Alessandro 	lpc18xx_nvmem_config.dev = dev;
2342e8d0733SSrinivas Kandagatla 	lpc18xx_nvmem_config.priv = eeprom;
235f02f8aeeSAriel D'Alessandro 
236226014d1SBartosz Golaszewski 	eeprom->nvmem = devm_nvmem_register(dev, &lpc18xx_nvmem_config);
237f02f8aeeSAriel D'Alessandro 	if (IS_ERR(eeprom->nvmem)) {
238f02f8aeeSAriel D'Alessandro 		ret = PTR_ERR(eeprom->nvmem);
239f02f8aeeSAriel D'Alessandro 		goto err_clk;
240f02f8aeeSAriel D'Alessandro 	}
241f02f8aeeSAriel D'Alessandro 
242f02f8aeeSAriel D'Alessandro 	platform_set_drvdata(pdev, eeprom);
243f02f8aeeSAriel D'Alessandro 
244f02f8aeeSAriel D'Alessandro 	return 0;
245f02f8aeeSAriel D'Alessandro 
246f02f8aeeSAriel D'Alessandro err_clk:
247f02f8aeeSAriel D'Alessandro 	clk_disable_unprepare(eeprom->clk);
248f02f8aeeSAriel D'Alessandro 
249f02f8aeeSAriel D'Alessandro 	return ret;
250f02f8aeeSAriel D'Alessandro }
251f02f8aeeSAriel D'Alessandro 
lpc18xx_eeprom_remove(struct platform_device * pdev)252f02f8aeeSAriel D'Alessandro static int lpc18xx_eeprom_remove(struct platform_device *pdev)
253f02f8aeeSAriel D'Alessandro {
254f02f8aeeSAriel D'Alessandro 	struct lpc18xx_eeprom_dev *eeprom = platform_get_drvdata(pdev);
255f02f8aeeSAriel D'Alessandro 
256f02f8aeeSAriel D'Alessandro 	clk_disable_unprepare(eeprom->clk);
257f02f8aeeSAriel D'Alessandro 
258f02f8aeeSAriel D'Alessandro 	return 0;
259f02f8aeeSAriel D'Alessandro }
260f02f8aeeSAriel D'Alessandro 
261f02f8aeeSAriel D'Alessandro static const struct of_device_id lpc18xx_eeprom_of_match[] = {
262f02f8aeeSAriel D'Alessandro 	{ .compatible = "nxp,lpc1857-eeprom" },
263f02f8aeeSAriel D'Alessandro 	{ },
264f02f8aeeSAriel D'Alessandro };
265f02f8aeeSAriel D'Alessandro MODULE_DEVICE_TABLE(of, lpc18xx_eeprom_of_match);
266f02f8aeeSAriel D'Alessandro 
267f02f8aeeSAriel D'Alessandro static struct platform_driver lpc18xx_eeprom_driver = {
268f02f8aeeSAriel D'Alessandro 	.probe = lpc18xx_eeprom_probe,
269f02f8aeeSAriel D'Alessandro 	.remove = lpc18xx_eeprom_remove,
270f02f8aeeSAriel D'Alessandro 	.driver = {
271f02f8aeeSAriel D'Alessandro 		.name = "lpc18xx-eeprom",
272f02f8aeeSAriel D'Alessandro 		.of_match_table = lpc18xx_eeprom_of_match,
273f02f8aeeSAriel D'Alessandro 	},
274f02f8aeeSAriel D'Alessandro };
275f02f8aeeSAriel D'Alessandro 
276f02f8aeeSAriel D'Alessandro module_platform_driver(lpc18xx_eeprom_driver);
277f02f8aeeSAriel D'Alessandro 
278f02f8aeeSAriel D'Alessandro MODULE_AUTHOR("Ariel D'Alessandro <ariel@vanguardiasur.com.ar>");
279f02f8aeeSAriel D'Alessandro MODULE_DESCRIPTION("NXP LPC18xx EEPROM memory Driver");
280f02f8aeeSAriel D'Alessandro MODULE_LICENSE("GPL v2");
281