162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * NXP LPC18xx/LPC43xx EEPROM memory NVMEM driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2015 Ariel D'Alessandro <ariel@vanguardiasur.com>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/clk.h>
962306a36Sopenharmony_ci#include <linux/device.h>
1062306a36Sopenharmony_ci#include <linux/delay.h>
1162306a36Sopenharmony_ci#include <linux/err.h>
1262306a36Sopenharmony_ci#include <linux/io.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/mod_devicetable.h>
1562306a36Sopenharmony_ci#include <linux/nvmem-provider.h>
1662306a36Sopenharmony_ci#include <linux/platform_device.h>
1762306a36Sopenharmony_ci#include <linux/reset.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci/* Registers */
2062306a36Sopenharmony_ci#define LPC18XX_EEPROM_AUTOPROG			0x00c
2162306a36Sopenharmony_ci#define LPC18XX_EEPROM_AUTOPROG_WORD		0x1
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#define LPC18XX_EEPROM_CLKDIV			0x014
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define LPC18XX_EEPROM_PWRDWN			0x018
2662306a36Sopenharmony_ci#define LPC18XX_EEPROM_PWRDWN_NO		0x0
2762306a36Sopenharmony_ci#define LPC18XX_EEPROM_PWRDWN_YES		0x1
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#define LPC18XX_EEPROM_INTSTAT			0xfe0
3062306a36Sopenharmony_ci#define LPC18XX_EEPROM_INTSTAT_END_OF_PROG	BIT(2)
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci#define LPC18XX_EEPROM_INTSTATCLR		0xfe8
3362306a36Sopenharmony_ci#define LPC18XX_EEPROM_INTSTATCLR_PROG_CLR_ST	BIT(2)
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci/* Fixed page size (bytes) */
3662306a36Sopenharmony_ci#define LPC18XX_EEPROM_PAGE_SIZE		0x80
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci/* EEPROM device requires a ~1500 kHz clock (min 800 kHz, max 1600 kHz) */
3962306a36Sopenharmony_ci#define LPC18XX_EEPROM_CLOCK_HZ			1500000
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci/* EEPROM requires 3 ms of erase/program time between each writing */
4262306a36Sopenharmony_ci#define LPC18XX_EEPROM_PROGRAM_TIME		3
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistruct lpc18xx_eeprom_dev {
4562306a36Sopenharmony_ci	struct clk *clk;
4662306a36Sopenharmony_ci	void __iomem *reg_base;
4762306a36Sopenharmony_ci	void __iomem *mem_base;
4862306a36Sopenharmony_ci	struct nvmem_device *nvmem;
4962306a36Sopenharmony_ci	unsigned reg_bytes;
5062306a36Sopenharmony_ci	unsigned val_bytes;
5162306a36Sopenharmony_ci	int size;
5262306a36Sopenharmony_ci};
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistatic inline void lpc18xx_eeprom_writel(struct lpc18xx_eeprom_dev *eeprom,
5562306a36Sopenharmony_ci					 u32 reg, u32 val)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	writel(val, eeprom->reg_base + reg);
5862306a36Sopenharmony_ci}
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic inline u32 lpc18xx_eeprom_readl(struct lpc18xx_eeprom_dev *eeprom,
6162306a36Sopenharmony_ci				       u32 reg)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	return readl(eeprom->reg_base + reg);
6462306a36Sopenharmony_ci}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistatic int lpc18xx_eeprom_busywait_until_prog(struct lpc18xx_eeprom_dev *eeprom)
6762306a36Sopenharmony_ci{
6862306a36Sopenharmony_ci	unsigned long end;
6962306a36Sopenharmony_ci	u32 val;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	/* Wait until EEPROM program operation has finished */
7262306a36Sopenharmony_ci	end = jiffies + msecs_to_jiffies(LPC18XX_EEPROM_PROGRAM_TIME * 10);
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	while (time_is_after_jiffies(end)) {
7562306a36Sopenharmony_ci		val = lpc18xx_eeprom_readl(eeprom, LPC18XX_EEPROM_INTSTAT);
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci		if (val & LPC18XX_EEPROM_INTSTAT_END_OF_PROG) {
7862306a36Sopenharmony_ci			lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_INTSTATCLR,
7962306a36Sopenharmony_ci					LPC18XX_EEPROM_INTSTATCLR_PROG_CLR_ST);
8062306a36Sopenharmony_ci			return 0;
8162306a36Sopenharmony_ci		}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci		usleep_range(LPC18XX_EEPROM_PROGRAM_TIME * USEC_PER_MSEC,
8462306a36Sopenharmony_ci			     (LPC18XX_EEPROM_PROGRAM_TIME + 1) * USEC_PER_MSEC);
8562306a36Sopenharmony_ci	}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	return -ETIMEDOUT;
8862306a36Sopenharmony_ci}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cistatic int lpc18xx_eeprom_gather_write(void *context, unsigned int reg,
9162306a36Sopenharmony_ci				       void *val, size_t bytes)
9262306a36Sopenharmony_ci{
9362306a36Sopenharmony_ci	struct lpc18xx_eeprom_dev *eeprom = context;
9462306a36Sopenharmony_ci	unsigned int offset = reg;
9562306a36Sopenharmony_ci	int ret;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	/*
9862306a36Sopenharmony_ci	 * The last page contains the EEPROM initialization data and is not
9962306a36Sopenharmony_ci	 * writable.
10062306a36Sopenharmony_ci	 */
10162306a36Sopenharmony_ci	if ((reg > eeprom->size - LPC18XX_EEPROM_PAGE_SIZE) ||
10262306a36Sopenharmony_ci			(reg + bytes > eeprom->size - LPC18XX_EEPROM_PAGE_SIZE))
10362306a36Sopenharmony_ci		return -EINVAL;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_PWRDWN,
10762306a36Sopenharmony_ci			      LPC18XX_EEPROM_PWRDWN_NO);
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	/* Wait 100 us while the EEPROM wakes up */
11062306a36Sopenharmony_ci	usleep_range(100, 200);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	while (bytes) {
11362306a36Sopenharmony_ci		writel(*(u32 *)val, eeprom->mem_base + offset);
11462306a36Sopenharmony_ci		ret = lpc18xx_eeprom_busywait_until_prog(eeprom);
11562306a36Sopenharmony_ci		if (ret < 0)
11662306a36Sopenharmony_ci			return ret;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci		bytes -= eeprom->val_bytes;
11962306a36Sopenharmony_ci		val += eeprom->val_bytes;
12062306a36Sopenharmony_ci		offset += eeprom->val_bytes;
12162306a36Sopenharmony_ci	}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_PWRDWN,
12462306a36Sopenharmony_ci			      LPC18XX_EEPROM_PWRDWN_YES);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	return 0;
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic int lpc18xx_eeprom_read(void *context, unsigned int offset,
13062306a36Sopenharmony_ci			       void *val, size_t bytes)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	struct lpc18xx_eeprom_dev *eeprom = context;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_PWRDWN,
13562306a36Sopenharmony_ci			      LPC18XX_EEPROM_PWRDWN_NO);
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	/* Wait 100 us while the EEPROM wakes up */
13862306a36Sopenharmony_ci	usleep_range(100, 200);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	while (bytes) {
14162306a36Sopenharmony_ci		*(u32 *)val = readl(eeprom->mem_base + offset);
14262306a36Sopenharmony_ci		bytes -= eeprom->val_bytes;
14362306a36Sopenharmony_ci		val += eeprom->val_bytes;
14462306a36Sopenharmony_ci		offset += eeprom->val_bytes;
14562306a36Sopenharmony_ci	}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_PWRDWN,
14862306a36Sopenharmony_ci			      LPC18XX_EEPROM_PWRDWN_YES);
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	return 0;
15162306a36Sopenharmony_ci}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_cistatic struct nvmem_config lpc18xx_nvmem_config = {
15562306a36Sopenharmony_ci	.name = "lpc18xx-eeprom",
15662306a36Sopenharmony_ci	.stride = 4,
15762306a36Sopenharmony_ci	.word_size = 4,
15862306a36Sopenharmony_ci	.reg_read = lpc18xx_eeprom_read,
15962306a36Sopenharmony_ci	.reg_write = lpc18xx_eeprom_gather_write,
16062306a36Sopenharmony_ci};
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_cistatic int lpc18xx_eeprom_probe(struct platform_device *pdev)
16362306a36Sopenharmony_ci{
16462306a36Sopenharmony_ci	struct lpc18xx_eeprom_dev *eeprom;
16562306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
16662306a36Sopenharmony_ci	struct reset_control *rst;
16762306a36Sopenharmony_ci	unsigned long clk_rate;
16862306a36Sopenharmony_ci	struct resource *res;
16962306a36Sopenharmony_ci	int ret;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	eeprom = devm_kzalloc(dev, sizeof(*eeprom), GFP_KERNEL);
17262306a36Sopenharmony_ci	if (!eeprom)
17362306a36Sopenharmony_ci		return -ENOMEM;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "reg");
17662306a36Sopenharmony_ci	eeprom->reg_base = devm_ioremap_resource(dev, res);
17762306a36Sopenharmony_ci	if (IS_ERR(eeprom->reg_base))
17862306a36Sopenharmony_ci		return PTR_ERR(eeprom->reg_base);
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mem");
18162306a36Sopenharmony_ci	eeprom->mem_base = devm_ioremap_resource(dev, res);
18262306a36Sopenharmony_ci	if (IS_ERR(eeprom->mem_base))
18362306a36Sopenharmony_ci		return PTR_ERR(eeprom->mem_base);
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	eeprom->clk = devm_clk_get(&pdev->dev, "eeprom");
18662306a36Sopenharmony_ci	if (IS_ERR(eeprom->clk)) {
18762306a36Sopenharmony_ci		dev_err(&pdev->dev, "failed to get eeprom clock\n");
18862306a36Sopenharmony_ci		return PTR_ERR(eeprom->clk);
18962306a36Sopenharmony_ci	}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	ret = clk_prepare_enable(eeprom->clk);
19262306a36Sopenharmony_ci	if (ret < 0) {
19362306a36Sopenharmony_ci		dev_err(dev, "failed to prepare/enable eeprom clk: %d\n", ret);
19462306a36Sopenharmony_ci		return ret;
19562306a36Sopenharmony_ci	}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	rst = devm_reset_control_get_exclusive(dev, NULL);
19862306a36Sopenharmony_ci	if (IS_ERR(rst)) {
19962306a36Sopenharmony_ci		dev_err(dev, "failed to get reset: %ld\n", PTR_ERR(rst));
20062306a36Sopenharmony_ci		ret = PTR_ERR(rst);
20162306a36Sopenharmony_ci		goto err_clk;
20262306a36Sopenharmony_ci	}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	ret = reset_control_assert(rst);
20562306a36Sopenharmony_ci	if (ret < 0) {
20662306a36Sopenharmony_ci		dev_err(dev, "failed to assert reset: %d\n", ret);
20762306a36Sopenharmony_ci		goto err_clk;
20862306a36Sopenharmony_ci	}
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	eeprom->val_bytes = 4;
21162306a36Sopenharmony_ci	eeprom->reg_bytes = 4;
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	/*
21462306a36Sopenharmony_ci	 * Clock rate is generated by dividing the system bus clock by the
21562306a36Sopenharmony_ci	 * division factor, contained in the divider register (minus 1 encoded).
21662306a36Sopenharmony_ci	 */
21762306a36Sopenharmony_ci	clk_rate = clk_get_rate(eeprom->clk);
21862306a36Sopenharmony_ci	clk_rate = DIV_ROUND_UP(clk_rate, LPC18XX_EEPROM_CLOCK_HZ) - 1;
21962306a36Sopenharmony_ci	lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_CLKDIV, clk_rate);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	/*
22262306a36Sopenharmony_ci	 * Writing a single word to the page will start the erase/program cycle
22362306a36Sopenharmony_ci	 * automatically
22462306a36Sopenharmony_ci	 */
22562306a36Sopenharmony_ci	lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_AUTOPROG,
22662306a36Sopenharmony_ci			      LPC18XX_EEPROM_AUTOPROG_WORD);
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_PWRDWN,
22962306a36Sopenharmony_ci			      LPC18XX_EEPROM_PWRDWN_YES);
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	eeprom->size = resource_size(res);
23262306a36Sopenharmony_ci	lpc18xx_nvmem_config.size = resource_size(res);
23362306a36Sopenharmony_ci	lpc18xx_nvmem_config.dev = dev;
23462306a36Sopenharmony_ci	lpc18xx_nvmem_config.priv = eeprom;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	eeprom->nvmem = devm_nvmem_register(dev, &lpc18xx_nvmem_config);
23762306a36Sopenharmony_ci	if (IS_ERR(eeprom->nvmem)) {
23862306a36Sopenharmony_ci		ret = PTR_ERR(eeprom->nvmem);
23962306a36Sopenharmony_ci		goto err_clk;
24062306a36Sopenharmony_ci	}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	platform_set_drvdata(pdev, eeprom);
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	return 0;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_cierr_clk:
24762306a36Sopenharmony_ci	clk_disable_unprepare(eeprom->clk);
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	return ret;
25062306a36Sopenharmony_ci}
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_cistatic int lpc18xx_eeprom_remove(struct platform_device *pdev)
25362306a36Sopenharmony_ci{
25462306a36Sopenharmony_ci	struct lpc18xx_eeprom_dev *eeprom = platform_get_drvdata(pdev);
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	clk_disable_unprepare(eeprom->clk);
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	return 0;
25962306a36Sopenharmony_ci}
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_cistatic const struct of_device_id lpc18xx_eeprom_of_match[] = {
26262306a36Sopenharmony_ci	{ .compatible = "nxp,lpc1857-eeprom" },
26362306a36Sopenharmony_ci	{ },
26462306a36Sopenharmony_ci};
26562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, lpc18xx_eeprom_of_match);
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_cistatic struct platform_driver lpc18xx_eeprom_driver = {
26862306a36Sopenharmony_ci	.probe = lpc18xx_eeprom_probe,
26962306a36Sopenharmony_ci	.remove = lpc18xx_eeprom_remove,
27062306a36Sopenharmony_ci	.driver = {
27162306a36Sopenharmony_ci		.name = "lpc18xx-eeprom",
27262306a36Sopenharmony_ci		.of_match_table = lpc18xx_eeprom_of_match,
27362306a36Sopenharmony_ci	},
27462306a36Sopenharmony_ci};
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_cimodule_platform_driver(lpc18xx_eeprom_driver);
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ciMODULE_AUTHOR("Ariel D'Alessandro <ariel@vanguardiasur.com.ar>");
27962306a36Sopenharmony_ciMODULE_DESCRIPTION("NXP LPC18xx EEPROM memory Driver");
28062306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
281