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