15b497af4SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
28caef1faSMartin Blumenstingl /*
38caef1faSMartin Blumenstingl  * Amlogic Meson6, Meson8 and Meson8b eFuse Driver
48caef1faSMartin Blumenstingl  *
58caef1faSMartin Blumenstingl  * Copyright (c) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
68caef1faSMartin Blumenstingl  */
78caef1faSMartin Blumenstingl 
88caef1faSMartin Blumenstingl #include <linux/bitfield.h>
98caef1faSMartin Blumenstingl #include <linux/bitops.h>
108caef1faSMartin Blumenstingl #include <linux/clk.h>
118caef1faSMartin Blumenstingl #include <linux/delay.h>
128caef1faSMartin Blumenstingl #include <linux/io.h>
138caef1faSMartin Blumenstingl #include <linux/iopoll.h>
148caef1faSMartin Blumenstingl #include <linux/module.h>
158caef1faSMartin Blumenstingl #include <linux/nvmem-provider.h>
168caef1faSMartin Blumenstingl #include <linux/of.h>
178caef1faSMartin Blumenstingl #include <linux/of_device.h>
188caef1faSMartin Blumenstingl #include <linux/platform_device.h>
198caef1faSMartin Blumenstingl #include <linux/sizes.h>
208caef1faSMartin Blumenstingl #include <linux/slab.h>
218caef1faSMartin Blumenstingl 
228caef1faSMartin Blumenstingl #define MESON_MX_EFUSE_CNTL1					0x04
238caef1faSMartin Blumenstingl #define MESON_MX_EFUSE_CNTL1_PD_ENABLE				BIT(27)
248caef1faSMartin Blumenstingl #define MESON_MX_EFUSE_CNTL1_AUTO_RD_BUSY			BIT(26)
258caef1faSMartin Blumenstingl #define MESON_MX_EFUSE_CNTL1_AUTO_RD_START			BIT(25)
268caef1faSMartin Blumenstingl #define MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE			BIT(24)
278caef1faSMartin Blumenstingl #define MESON_MX_EFUSE_CNTL1_BYTE_WR_DATA			GENMASK(23, 16)
288caef1faSMartin Blumenstingl #define MESON_MX_EFUSE_CNTL1_AUTO_WR_BUSY			BIT(14)
298caef1faSMartin Blumenstingl #define MESON_MX_EFUSE_CNTL1_AUTO_WR_START			BIT(13)
308caef1faSMartin Blumenstingl #define MESON_MX_EFUSE_CNTL1_AUTO_WR_ENABLE			BIT(12)
318caef1faSMartin Blumenstingl #define MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET			BIT(11)
328caef1faSMartin Blumenstingl #define MESON_MX_EFUSE_CNTL1_BYTE_ADDR_MASK			GENMASK(10, 0)
338caef1faSMartin Blumenstingl 
348caef1faSMartin Blumenstingl #define MESON_MX_EFUSE_CNTL2					0x08
358caef1faSMartin Blumenstingl 
368caef1faSMartin Blumenstingl #define MESON_MX_EFUSE_CNTL4					0x10
378caef1faSMartin Blumenstingl #define MESON_MX_EFUSE_CNTL4_ENCRYPT_ENABLE			BIT(10)
388caef1faSMartin Blumenstingl 
398caef1faSMartin Blumenstingl struct meson_mx_efuse_platform_data {
408caef1faSMartin Blumenstingl 	const char *name;
418caef1faSMartin Blumenstingl 	unsigned int word_size;
428caef1faSMartin Blumenstingl };
438caef1faSMartin Blumenstingl 
448caef1faSMartin Blumenstingl struct meson_mx_efuse {
458caef1faSMartin Blumenstingl 	void __iomem *base;
468caef1faSMartin Blumenstingl 	struct clk *core_clk;
478caef1faSMartin Blumenstingl 	struct nvmem_device *nvmem;
488caef1faSMartin Blumenstingl 	struct nvmem_config config;
498caef1faSMartin Blumenstingl };
508caef1faSMartin Blumenstingl 
518caef1faSMartin Blumenstingl static void meson_mx_efuse_mask_bits(struct meson_mx_efuse *efuse, u32 reg,
528caef1faSMartin Blumenstingl 				     u32 mask, u32 set)
538caef1faSMartin Blumenstingl {
548caef1faSMartin Blumenstingl 	u32 data;
558caef1faSMartin Blumenstingl 
568caef1faSMartin Blumenstingl 	data = readl(efuse->base + reg);
578caef1faSMartin Blumenstingl 	data &= ~mask;
588caef1faSMartin Blumenstingl 	data |= (set & mask);
598caef1faSMartin Blumenstingl 
608caef1faSMartin Blumenstingl 	writel(data, efuse->base + reg);
618caef1faSMartin Blumenstingl }
628caef1faSMartin Blumenstingl 
638caef1faSMartin Blumenstingl static int meson_mx_efuse_hw_enable(struct meson_mx_efuse *efuse)
648caef1faSMartin Blumenstingl {
658caef1faSMartin Blumenstingl 	int err;
668caef1faSMartin Blumenstingl 
678caef1faSMartin Blumenstingl 	err = clk_prepare_enable(efuse->core_clk);
688caef1faSMartin Blumenstingl 	if (err)
698caef1faSMartin Blumenstingl 		return err;
708caef1faSMartin Blumenstingl 
718caef1faSMartin Blumenstingl 	/* power up the efuse */
728caef1faSMartin Blumenstingl 	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
738caef1faSMartin Blumenstingl 				 MESON_MX_EFUSE_CNTL1_PD_ENABLE, 0);
748caef1faSMartin Blumenstingl 
758caef1faSMartin Blumenstingl 	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL4,
768caef1faSMartin Blumenstingl 				 MESON_MX_EFUSE_CNTL4_ENCRYPT_ENABLE, 0);
778caef1faSMartin Blumenstingl 
788caef1faSMartin Blumenstingl 	return 0;
798caef1faSMartin Blumenstingl }
808caef1faSMartin Blumenstingl 
818caef1faSMartin Blumenstingl static void meson_mx_efuse_hw_disable(struct meson_mx_efuse *efuse)
828caef1faSMartin Blumenstingl {
838caef1faSMartin Blumenstingl 	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
848caef1faSMartin Blumenstingl 				 MESON_MX_EFUSE_CNTL1_PD_ENABLE,
858caef1faSMartin Blumenstingl 				 MESON_MX_EFUSE_CNTL1_PD_ENABLE);
868caef1faSMartin Blumenstingl 
878caef1faSMartin Blumenstingl 	clk_disable_unprepare(efuse->core_clk);
888caef1faSMartin Blumenstingl }
898caef1faSMartin Blumenstingl 
908caef1faSMartin Blumenstingl static int meson_mx_efuse_read_addr(struct meson_mx_efuse *efuse,
918caef1faSMartin Blumenstingl 				    unsigned int addr, u32 *value)
928caef1faSMartin Blumenstingl {
938caef1faSMartin Blumenstingl 	int err;
948caef1faSMartin Blumenstingl 	u32 regval;
958caef1faSMartin Blumenstingl 
968caef1faSMartin Blumenstingl 	/* write the address to read */
978caef1faSMartin Blumenstingl 	regval = FIELD_PREP(MESON_MX_EFUSE_CNTL1_BYTE_ADDR_MASK, addr);
988caef1faSMartin Blumenstingl 	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
998caef1faSMartin Blumenstingl 				 MESON_MX_EFUSE_CNTL1_BYTE_ADDR_MASK, regval);
1008caef1faSMartin Blumenstingl 
1018caef1faSMartin Blumenstingl 	/* inform the hardware that we changed the address */
1028caef1faSMartin Blumenstingl 	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
1038caef1faSMartin Blumenstingl 				 MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET,
1048caef1faSMartin Blumenstingl 				 MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET);
1058caef1faSMartin Blumenstingl 	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
1068caef1faSMartin Blumenstingl 				 MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET, 0);
1078caef1faSMartin Blumenstingl 
1088caef1faSMartin Blumenstingl 	/* start the read process */
1098caef1faSMartin Blumenstingl 	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
1108caef1faSMartin Blumenstingl 				 MESON_MX_EFUSE_CNTL1_AUTO_RD_START,
1118caef1faSMartin Blumenstingl 				 MESON_MX_EFUSE_CNTL1_AUTO_RD_START);
1128caef1faSMartin Blumenstingl 	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
1138caef1faSMartin Blumenstingl 				 MESON_MX_EFUSE_CNTL1_AUTO_RD_START, 0);
1148caef1faSMartin Blumenstingl 
1158caef1faSMartin Blumenstingl 	/*
1168caef1faSMartin Blumenstingl 	 * perform a dummy read to ensure that the HW has the RD_BUSY bit set
1178caef1faSMartin Blumenstingl 	 * when polling for the status below.
1188caef1faSMartin Blumenstingl 	 */
1198caef1faSMartin Blumenstingl 	readl(efuse->base + MESON_MX_EFUSE_CNTL1);
1208caef1faSMartin Blumenstingl 
1218caef1faSMartin Blumenstingl 	err = readl_poll_timeout_atomic(efuse->base + MESON_MX_EFUSE_CNTL1,
1228caef1faSMartin Blumenstingl 			regval,
1238caef1faSMartin Blumenstingl 			(!(regval & MESON_MX_EFUSE_CNTL1_AUTO_RD_BUSY)),
1248caef1faSMartin Blumenstingl 			1, 1000);
1258caef1faSMartin Blumenstingl 	if (err) {
1268caef1faSMartin Blumenstingl 		dev_err(efuse->config.dev,
1278caef1faSMartin Blumenstingl 			"Timeout while reading efuse address %u\n", addr);
1288caef1faSMartin Blumenstingl 		return err;
1298caef1faSMartin Blumenstingl 	}
1308caef1faSMartin Blumenstingl 
1318caef1faSMartin Blumenstingl 	*value = readl(efuse->base + MESON_MX_EFUSE_CNTL2);
1328caef1faSMartin Blumenstingl 
1338caef1faSMartin Blumenstingl 	return 0;
1348caef1faSMartin Blumenstingl }
1358caef1faSMartin Blumenstingl 
1368caef1faSMartin Blumenstingl static int meson_mx_efuse_read(void *context, unsigned int offset,
1378caef1faSMartin Blumenstingl 			       void *buf, size_t bytes)
1388caef1faSMartin Blumenstingl {
1398caef1faSMartin Blumenstingl 	struct meson_mx_efuse *efuse = context;
1408caef1faSMartin Blumenstingl 	u32 tmp;
1418caef1faSMartin Blumenstingl 	int err, i, addr;
1428caef1faSMartin Blumenstingl 
1438caef1faSMartin Blumenstingl 	err = meson_mx_efuse_hw_enable(efuse);
1448caef1faSMartin Blumenstingl 	if (err)
1458caef1faSMartin Blumenstingl 		return err;
1468caef1faSMartin Blumenstingl 
1478caef1faSMartin Blumenstingl 	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
1488caef1faSMartin Blumenstingl 				 MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE,
1498caef1faSMartin Blumenstingl 				 MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE);
1508caef1faSMartin Blumenstingl 
1518a42d3fcSMartin Blumenstingl 	for (i = 0; i < bytes; i += efuse->config.word_size) {
1528a42d3fcSMartin Blumenstingl 		addr = (offset + i) / efuse->config.word_size;
1538caef1faSMartin Blumenstingl 
1548caef1faSMartin Blumenstingl 		err = meson_mx_efuse_read_addr(efuse, addr, &tmp);
1558caef1faSMartin Blumenstingl 		if (err)
1568caef1faSMartin Blumenstingl 			break;
1578caef1faSMartin Blumenstingl 
158cb6b0a39SMartin Blumenstingl 		memcpy(buf + i, &tmp,
159cb6b0a39SMartin Blumenstingl 		       min_t(size_t, bytes - i, efuse->config.word_size));
1608caef1faSMartin Blumenstingl 	}
1618caef1faSMartin Blumenstingl 
1628caef1faSMartin Blumenstingl 	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
1638caef1faSMartin Blumenstingl 				 MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE, 0);
1648caef1faSMartin Blumenstingl 
1658caef1faSMartin Blumenstingl 	meson_mx_efuse_hw_disable(efuse);
1668caef1faSMartin Blumenstingl 
1678caef1faSMartin Blumenstingl 	return err;
1688caef1faSMartin Blumenstingl }
1698caef1faSMartin Blumenstingl 
1708caef1faSMartin Blumenstingl static const struct meson_mx_efuse_platform_data meson6_efuse_data = {
1718caef1faSMartin Blumenstingl 	.name = "meson6-efuse",
1728caef1faSMartin Blumenstingl 	.word_size = 1,
1738caef1faSMartin Blumenstingl };
1748caef1faSMartin Blumenstingl 
1758caef1faSMartin Blumenstingl static const struct meson_mx_efuse_platform_data meson8_efuse_data = {
1768caef1faSMartin Blumenstingl 	.name = "meson8-efuse",
1778caef1faSMartin Blumenstingl 	.word_size = 4,
1788caef1faSMartin Blumenstingl };
1798caef1faSMartin Blumenstingl 
1808caef1faSMartin Blumenstingl static const struct meson_mx_efuse_platform_data meson8b_efuse_data = {
1818caef1faSMartin Blumenstingl 	.name = "meson8b-efuse",
1828caef1faSMartin Blumenstingl 	.word_size = 4,
1838caef1faSMartin Blumenstingl };
1848caef1faSMartin Blumenstingl 
1858caef1faSMartin Blumenstingl static const struct of_device_id meson_mx_efuse_match[] = {
1868caef1faSMartin Blumenstingl 	{ .compatible = "amlogic,meson6-efuse", .data = &meson6_efuse_data },
1878caef1faSMartin Blumenstingl 	{ .compatible = "amlogic,meson8-efuse", .data = &meson8_efuse_data },
1888caef1faSMartin Blumenstingl 	{ .compatible = "amlogic,meson8b-efuse", .data = &meson8b_efuse_data },
1898caef1faSMartin Blumenstingl 	{ /* sentinel */ },
1908caef1faSMartin Blumenstingl };
1918caef1faSMartin Blumenstingl MODULE_DEVICE_TABLE(of, meson_mx_efuse_match);
1928caef1faSMartin Blumenstingl 
1938caef1faSMartin Blumenstingl static int meson_mx_efuse_probe(struct platform_device *pdev)
1948caef1faSMartin Blumenstingl {
1958caef1faSMartin Blumenstingl 	const struct meson_mx_efuse_platform_data *drvdata;
1968caef1faSMartin Blumenstingl 	struct meson_mx_efuse *efuse;
1978caef1faSMartin Blumenstingl 	struct resource *res;
1988caef1faSMartin Blumenstingl 
1998caef1faSMartin Blumenstingl 	drvdata = of_device_get_match_data(&pdev->dev);
2008caef1faSMartin Blumenstingl 	if (!drvdata)
2018caef1faSMartin Blumenstingl 		return -EINVAL;
2028caef1faSMartin Blumenstingl 
2038caef1faSMartin Blumenstingl 	efuse = devm_kzalloc(&pdev->dev, sizeof(*efuse), GFP_KERNEL);
2048caef1faSMartin Blumenstingl 	if (!efuse)
2058caef1faSMartin Blumenstingl 		return -ENOMEM;
2068caef1faSMartin Blumenstingl 
2078caef1faSMartin Blumenstingl 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
2088caef1faSMartin Blumenstingl 	efuse->base = devm_ioremap_resource(&pdev->dev, res);
2098caef1faSMartin Blumenstingl 	if (IS_ERR(efuse->base))
2108caef1faSMartin Blumenstingl 		return PTR_ERR(efuse->base);
2118caef1faSMartin Blumenstingl 
2128caef1faSMartin Blumenstingl 	efuse->config.name = devm_kstrdup(&pdev->dev, drvdata->name,
2138caef1faSMartin Blumenstingl 					  GFP_KERNEL);
2148caef1faSMartin Blumenstingl 	efuse->config.owner = THIS_MODULE;
2158caef1faSMartin Blumenstingl 	efuse->config.dev = &pdev->dev;
2168caef1faSMartin Blumenstingl 	efuse->config.priv = efuse;
2178caef1faSMartin Blumenstingl 	efuse->config.stride = drvdata->word_size;
2188caef1faSMartin Blumenstingl 	efuse->config.word_size = drvdata->word_size;
2198caef1faSMartin Blumenstingl 	efuse->config.size = SZ_512;
2208caef1faSMartin Blumenstingl 	efuse->config.read_only = true;
2218caef1faSMartin Blumenstingl 	efuse->config.reg_read = meson_mx_efuse_read;
2228caef1faSMartin Blumenstingl 
2238caef1faSMartin Blumenstingl 	efuse->core_clk = devm_clk_get(&pdev->dev, "core");
2248caef1faSMartin Blumenstingl 	if (IS_ERR(efuse->core_clk)) {
2258caef1faSMartin Blumenstingl 		dev_err(&pdev->dev, "Failed to get core clock\n");
2268caef1faSMartin Blumenstingl 		return PTR_ERR(efuse->core_clk);
2278caef1faSMartin Blumenstingl 	}
2288caef1faSMartin Blumenstingl 
2297afbde9eSAndrey Smirnov 	efuse->nvmem = devm_nvmem_register(&pdev->dev, &efuse->config);
2308caef1faSMartin Blumenstingl 
2317afbde9eSAndrey Smirnov 	return PTR_ERR_OR_ZERO(efuse->nvmem);
2328caef1faSMartin Blumenstingl }
2338caef1faSMartin Blumenstingl 
2348caef1faSMartin Blumenstingl static struct platform_driver meson_mx_efuse_driver = {
2358caef1faSMartin Blumenstingl 	.probe = meson_mx_efuse_probe,
2368caef1faSMartin Blumenstingl 	.driver = {
2378caef1faSMartin Blumenstingl 		.name = "meson-mx-efuse",
2388caef1faSMartin Blumenstingl 		.of_match_table = meson_mx_efuse_match,
2398caef1faSMartin Blumenstingl 	},
2408caef1faSMartin Blumenstingl };
2418caef1faSMartin Blumenstingl 
2428caef1faSMartin Blumenstingl module_platform_driver(meson_mx_efuse_driver);
2438caef1faSMartin Blumenstingl 
2448caef1faSMartin Blumenstingl MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
2458caef1faSMartin Blumenstingl MODULE_DESCRIPTION("Amlogic Meson MX eFuse NVMEM driver");
2468caef1faSMartin Blumenstingl MODULE_LICENSE("GPL v2");
247