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