162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci// Copyright (C) 2016 Broadcom
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <linux/acpi.h>
562306a36Sopenharmony_ci#include <linux/delay.h>
662306a36Sopenharmony_ci#include <linux/device.h>
762306a36Sopenharmony_ci#include <linux/io.h>
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/nvmem-provider.h>
1062306a36Sopenharmony_ci#include <linux/of.h>
1162306a36Sopenharmony_ci#include <linux/platform_device.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci/*
1462306a36Sopenharmony_ci * # of tries for OTP Status. The time to execute a command varies. The slowest
1562306a36Sopenharmony_ci * commands are writes which also vary based on the # of bits turned on. Writing
1662306a36Sopenharmony_ci * 0xffffffff takes ~3800 us.
1762306a36Sopenharmony_ci */
1862306a36Sopenharmony_ci#define OTPC_RETRIES                 5000
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/* Sequence to enable OTP program */
2162306a36Sopenharmony_ci#define OTPC_PROG_EN_SEQ             { 0xf, 0x4, 0x8, 0xd }
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci/* OTPC Commands */
2462306a36Sopenharmony_ci#define OTPC_CMD_READ                0x0
2562306a36Sopenharmony_ci#define OTPC_CMD_OTP_PROG_ENABLE     0x2
2662306a36Sopenharmony_ci#define OTPC_CMD_OTP_PROG_DISABLE    0x3
2762306a36Sopenharmony_ci#define OTPC_CMD_PROGRAM             0x8
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci/* OTPC Status Bits */
3062306a36Sopenharmony_ci#define OTPC_STAT_CMD_DONE           BIT(1)
3162306a36Sopenharmony_ci#define OTPC_STAT_PROG_OK            BIT(2)
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci/* OTPC register definition */
3462306a36Sopenharmony_ci#define OTPC_MODE_REG_OFFSET         0x0
3562306a36Sopenharmony_ci#define OTPC_MODE_REG_OTPC_MODE      0
3662306a36Sopenharmony_ci#define OTPC_COMMAND_OFFSET          0x4
3762306a36Sopenharmony_ci#define OTPC_COMMAND_COMMAND_WIDTH   6
3862306a36Sopenharmony_ci#define OTPC_CMD_START_OFFSET        0x8
3962306a36Sopenharmony_ci#define OTPC_CMD_START_START         0
4062306a36Sopenharmony_ci#define OTPC_CPU_STATUS_OFFSET       0xc
4162306a36Sopenharmony_ci#define OTPC_CPUADDR_REG_OFFSET      0x28
4262306a36Sopenharmony_ci#define OTPC_CPUADDR_REG_OTPC_CPU_ADDRESS_WIDTH 16
4362306a36Sopenharmony_ci#define OTPC_CPU_WRITE_REG_OFFSET    0x2c
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci#define OTPC_CMD_MASK  (BIT(OTPC_COMMAND_COMMAND_WIDTH) - 1)
4662306a36Sopenharmony_ci#define OTPC_ADDR_MASK (BIT(OTPC_CPUADDR_REG_OTPC_CPU_ADDRESS_WIDTH) - 1)
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_cistruct otpc_map {
5062306a36Sopenharmony_ci	/* in words. */
5162306a36Sopenharmony_ci	u32 otpc_row_size;
5262306a36Sopenharmony_ci	/* 128 bit row / 4 words support. */
5362306a36Sopenharmony_ci	u16 data_r_offset[4];
5462306a36Sopenharmony_ci	/* 128 bit row / 4 words support. */
5562306a36Sopenharmony_ci	u16 data_w_offset[4];
5662306a36Sopenharmony_ci};
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistatic struct otpc_map otp_map = {
5962306a36Sopenharmony_ci	.otpc_row_size = 1,
6062306a36Sopenharmony_ci	.data_r_offset = {0x10},
6162306a36Sopenharmony_ci	.data_w_offset = {0x2c},
6262306a36Sopenharmony_ci};
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_cistatic struct otpc_map otp_map_v2 = {
6562306a36Sopenharmony_ci	.otpc_row_size = 2,
6662306a36Sopenharmony_ci	.data_r_offset = {0x10, 0x5c},
6762306a36Sopenharmony_ci	.data_w_offset = {0x2c, 0x64},
6862306a36Sopenharmony_ci};
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistruct otpc_priv {
7162306a36Sopenharmony_ci	struct device *dev;
7262306a36Sopenharmony_ci	void __iomem *base;
7362306a36Sopenharmony_ci	const struct otpc_map *map;
7462306a36Sopenharmony_ci	struct nvmem_config *config;
7562306a36Sopenharmony_ci};
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_cistatic inline void set_command(void __iomem *base, u32 command)
7862306a36Sopenharmony_ci{
7962306a36Sopenharmony_ci	writel(command & OTPC_CMD_MASK, base + OTPC_COMMAND_OFFSET);
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic inline void set_cpu_address(void __iomem *base, u32 addr)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	writel(addr & OTPC_ADDR_MASK, base + OTPC_CPUADDR_REG_OFFSET);
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_cistatic inline void set_start_bit(void __iomem *base)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	writel(1 << OTPC_CMD_START_START, base + OTPC_CMD_START_OFFSET);
9062306a36Sopenharmony_ci}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic inline void reset_start_bit(void __iomem *base)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	writel(0, base + OTPC_CMD_START_OFFSET);
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic inline void write_cpu_data(void __iomem *base, u32 value)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	writel(value, base + OTPC_CPU_WRITE_REG_OFFSET);
10062306a36Sopenharmony_ci}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_cistatic int poll_cpu_status(void __iomem *base, u32 value)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	u32 status;
10562306a36Sopenharmony_ci	u32 retries;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	for (retries = 0; retries < OTPC_RETRIES; retries++) {
10862306a36Sopenharmony_ci		status = readl(base + OTPC_CPU_STATUS_OFFSET);
10962306a36Sopenharmony_ci		if (status & value)
11062306a36Sopenharmony_ci			break;
11162306a36Sopenharmony_ci		udelay(1);
11262306a36Sopenharmony_ci	}
11362306a36Sopenharmony_ci	if (retries == OTPC_RETRIES)
11462306a36Sopenharmony_ci		return -EAGAIN;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	return 0;
11762306a36Sopenharmony_ci}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cistatic int enable_ocotp_program(void __iomem *base)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	static const u32 vals[] = OTPC_PROG_EN_SEQ;
12262306a36Sopenharmony_ci	int i;
12362306a36Sopenharmony_ci	int ret;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	/* Write the magic sequence to enable programming */
12662306a36Sopenharmony_ci	set_command(base, OTPC_CMD_OTP_PROG_ENABLE);
12762306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(vals); i++) {
12862306a36Sopenharmony_ci		write_cpu_data(base, vals[i]);
12962306a36Sopenharmony_ci		set_start_bit(base);
13062306a36Sopenharmony_ci		ret = poll_cpu_status(base, OTPC_STAT_CMD_DONE);
13162306a36Sopenharmony_ci		reset_start_bit(base);
13262306a36Sopenharmony_ci		if (ret)
13362306a36Sopenharmony_ci			return ret;
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	return poll_cpu_status(base, OTPC_STAT_PROG_OK);
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cistatic int disable_ocotp_program(void __iomem *base)
14062306a36Sopenharmony_ci{
14162306a36Sopenharmony_ci	int ret;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	set_command(base, OTPC_CMD_OTP_PROG_DISABLE);
14462306a36Sopenharmony_ci	set_start_bit(base);
14562306a36Sopenharmony_ci	ret = poll_cpu_status(base, OTPC_STAT_PROG_OK);
14662306a36Sopenharmony_ci	reset_start_bit(base);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	return ret;
14962306a36Sopenharmony_ci}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_cistatic int bcm_otpc_read(void *context, unsigned int offset, void *val,
15262306a36Sopenharmony_ci	size_t bytes)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	struct otpc_priv *priv = context;
15562306a36Sopenharmony_ci	u32 *buf = val;
15662306a36Sopenharmony_ci	u32 bytes_read;
15762306a36Sopenharmony_ci	u32 address = offset / priv->config->word_size;
15862306a36Sopenharmony_ci	int i, ret;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	for (bytes_read = 0; bytes_read < bytes;) {
16162306a36Sopenharmony_ci		set_command(priv->base, OTPC_CMD_READ);
16262306a36Sopenharmony_ci		set_cpu_address(priv->base, address++);
16362306a36Sopenharmony_ci		set_start_bit(priv->base);
16462306a36Sopenharmony_ci		ret = poll_cpu_status(priv->base, OTPC_STAT_CMD_DONE);
16562306a36Sopenharmony_ci		if (ret) {
16662306a36Sopenharmony_ci			dev_err(priv->dev, "otp read error: 0x%x", ret);
16762306a36Sopenharmony_ci			return -EIO;
16862306a36Sopenharmony_ci		}
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci		for (i = 0; i < priv->map->otpc_row_size; i++) {
17162306a36Sopenharmony_ci			*buf++ = readl(priv->base +
17262306a36Sopenharmony_ci					priv->map->data_r_offset[i]);
17362306a36Sopenharmony_ci			bytes_read += sizeof(*buf);
17462306a36Sopenharmony_ci		}
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci		reset_start_bit(priv->base);
17762306a36Sopenharmony_ci	}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	return 0;
18062306a36Sopenharmony_ci}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_cistatic int bcm_otpc_write(void *context, unsigned int offset, void *val,
18362306a36Sopenharmony_ci	size_t bytes)
18462306a36Sopenharmony_ci{
18562306a36Sopenharmony_ci	struct otpc_priv *priv = context;
18662306a36Sopenharmony_ci	u32 *buf = val;
18762306a36Sopenharmony_ci	u32 bytes_written;
18862306a36Sopenharmony_ci	u32 address = offset / priv->config->word_size;
18962306a36Sopenharmony_ci	int i, ret;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	if (offset % priv->config->word_size)
19262306a36Sopenharmony_ci		return -EINVAL;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	ret = enable_ocotp_program(priv->base);
19562306a36Sopenharmony_ci	if (ret)
19662306a36Sopenharmony_ci		return -EIO;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	for (bytes_written = 0; bytes_written < bytes;) {
19962306a36Sopenharmony_ci		set_command(priv->base, OTPC_CMD_PROGRAM);
20062306a36Sopenharmony_ci		set_cpu_address(priv->base, address++);
20162306a36Sopenharmony_ci		for (i = 0; i < priv->map->otpc_row_size; i++) {
20262306a36Sopenharmony_ci			writel(*buf, priv->base + priv->map->data_w_offset[i]);
20362306a36Sopenharmony_ci			buf++;
20462306a36Sopenharmony_ci			bytes_written += sizeof(*buf);
20562306a36Sopenharmony_ci		}
20662306a36Sopenharmony_ci		set_start_bit(priv->base);
20762306a36Sopenharmony_ci		ret = poll_cpu_status(priv->base, OTPC_STAT_CMD_DONE);
20862306a36Sopenharmony_ci		reset_start_bit(priv->base);
20962306a36Sopenharmony_ci		if (ret) {
21062306a36Sopenharmony_ci			dev_err(priv->dev, "otp write error: 0x%x", ret);
21162306a36Sopenharmony_ci			return -EIO;
21262306a36Sopenharmony_ci		}
21362306a36Sopenharmony_ci	}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	disable_ocotp_program(priv->base);
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	return 0;
21862306a36Sopenharmony_ci}
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_cistatic struct nvmem_config bcm_otpc_nvmem_config = {
22162306a36Sopenharmony_ci	.name = "bcm-ocotp",
22262306a36Sopenharmony_ci	.read_only = false,
22362306a36Sopenharmony_ci	.word_size = 4,
22462306a36Sopenharmony_ci	.stride = 4,
22562306a36Sopenharmony_ci	.reg_read = bcm_otpc_read,
22662306a36Sopenharmony_ci	.reg_write = bcm_otpc_write,
22762306a36Sopenharmony_ci};
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_cistatic const struct of_device_id bcm_otpc_dt_ids[] = {
23062306a36Sopenharmony_ci	{ .compatible = "brcm,ocotp", .data = &otp_map },
23162306a36Sopenharmony_ci	{ .compatible = "brcm,ocotp-v2", .data = &otp_map_v2 },
23262306a36Sopenharmony_ci	{ },
23362306a36Sopenharmony_ci};
23462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, bcm_otpc_dt_ids);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic const struct acpi_device_id bcm_otpc_acpi_ids[] __maybe_unused = {
23762306a36Sopenharmony_ci	{ .id = "BRCM0700", .driver_data = (kernel_ulong_t)&otp_map },
23862306a36Sopenharmony_ci	{ .id = "BRCM0701", .driver_data = (kernel_ulong_t)&otp_map_v2 },
23962306a36Sopenharmony_ci	{ /* sentinel */ }
24062306a36Sopenharmony_ci};
24162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, bcm_otpc_acpi_ids);
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_cistatic int bcm_otpc_probe(struct platform_device *pdev)
24462306a36Sopenharmony_ci{
24562306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
24662306a36Sopenharmony_ci	struct otpc_priv *priv;
24762306a36Sopenharmony_ci	struct nvmem_device *nvmem;
24862306a36Sopenharmony_ci	int err;
24962306a36Sopenharmony_ci	u32 num_words;
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
25262306a36Sopenharmony_ci	if (!priv)
25362306a36Sopenharmony_ci		return -ENOMEM;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	priv->map = device_get_match_data(dev);
25662306a36Sopenharmony_ci	if (!priv->map)
25762306a36Sopenharmony_ci		return -ENODEV;
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	/* Get OTP base address register. */
26062306a36Sopenharmony_ci	priv->base = devm_platform_ioremap_resource(pdev, 0);
26162306a36Sopenharmony_ci	if (IS_ERR(priv->base)) {
26262306a36Sopenharmony_ci		dev_err(dev, "unable to map I/O memory\n");
26362306a36Sopenharmony_ci		return PTR_ERR(priv->base);
26462306a36Sopenharmony_ci	}
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	/* Enable CPU access to OTPC. */
26762306a36Sopenharmony_ci	writel(readl(priv->base + OTPC_MODE_REG_OFFSET) |
26862306a36Sopenharmony_ci		BIT(OTPC_MODE_REG_OTPC_MODE),
26962306a36Sopenharmony_ci		priv->base + OTPC_MODE_REG_OFFSET);
27062306a36Sopenharmony_ci	reset_start_bit(priv->base);
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	/* Read size of memory in words. */
27362306a36Sopenharmony_ci	err = device_property_read_u32(dev, "brcm,ocotp-size", &num_words);
27462306a36Sopenharmony_ci	if (err) {
27562306a36Sopenharmony_ci		dev_err(dev, "size parameter not specified\n");
27662306a36Sopenharmony_ci		return -EINVAL;
27762306a36Sopenharmony_ci	} else if (num_words == 0) {
27862306a36Sopenharmony_ci		dev_err(dev, "size must be > 0\n");
27962306a36Sopenharmony_ci		return -EINVAL;
28062306a36Sopenharmony_ci	}
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	bcm_otpc_nvmem_config.size = 4 * num_words;
28362306a36Sopenharmony_ci	bcm_otpc_nvmem_config.dev = dev;
28462306a36Sopenharmony_ci	bcm_otpc_nvmem_config.priv = priv;
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	if (priv->map == &otp_map_v2) {
28762306a36Sopenharmony_ci		bcm_otpc_nvmem_config.word_size = 8;
28862306a36Sopenharmony_ci		bcm_otpc_nvmem_config.stride = 8;
28962306a36Sopenharmony_ci	}
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	priv->config = &bcm_otpc_nvmem_config;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	nvmem = devm_nvmem_register(dev, &bcm_otpc_nvmem_config);
29462306a36Sopenharmony_ci	if (IS_ERR(nvmem)) {
29562306a36Sopenharmony_ci		dev_err(dev, "error registering nvmem config\n");
29662306a36Sopenharmony_ci		return PTR_ERR(nvmem);
29762306a36Sopenharmony_ci	}
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	return 0;
30062306a36Sopenharmony_ci}
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_cistatic struct platform_driver bcm_otpc_driver = {
30362306a36Sopenharmony_ci	.probe	= bcm_otpc_probe,
30462306a36Sopenharmony_ci	.driver = {
30562306a36Sopenharmony_ci		.name	= "brcm-otpc",
30662306a36Sopenharmony_ci		.of_match_table = bcm_otpc_dt_ids,
30762306a36Sopenharmony_ci		.acpi_match_table = ACPI_PTR(bcm_otpc_acpi_ids),
30862306a36Sopenharmony_ci	},
30962306a36Sopenharmony_ci};
31062306a36Sopenharmony_cimodule_platform_driver(bcm_otpc_driver);
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ciMODULE_DESCRIPTION("Broadcom OTPC driver");
31362306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
314