162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2015 Toradex AG. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Author: Sanchayan Maity <sanchayan.maity@toradex.com> 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Based on the barebox ocotp driver, 862306a36Sopenharmony_ci * Copyright (c) 2010 Baruch Siach <baruch@tkos.co.il> 962306a36Sopenharmony_ci * Orex Computed Radiography 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/clk.h> 1362306a36Sopenharmony_ci#include <linux/delay.h> 1462306a36Sopenharmony_ci#include <linux/device.h> 1562306a36Sopenharmony_ci#include <linux/io.h> 1662306a36Sopenharmony_ci#include <linux/module.h> 1762306a36Sopenharmony_ci#include <linux/nvmem-provider.h> 1862306a36Sopenharmony_ci#include <linux/of.h> 1962306a36Sopenharmony_ci#include <linux/platform_device.h> 2062306a36Sopenharmony_ci#include <linux/slab.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci/* OCOTP Register Offsets */ 2362306a36Sopenharmony_ci#define OCOTP_CTRL_REG 0x00 2462306a36Sopenharmony_ci#define OCOTP_CTRL_SET 0x04 2562306a36Sopenharmony_ci#define OCOTP_CTRL_CLR 0x08 2662306a36Sopenharmony_ci#define OCOTP_TIMING 0x10 2762306a36Sopenharmony_ci#define OCOTP_DATA 0x20 2862306a36Sopenharmony_ci#define OCOTP_READ_CTRL_REG 0x30 2962306a36Sopenharmony_ci#define OCOTP_READ_FUSE_DATA 0x40 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci/* OCOTP Register bits and masks */ 3262306a36Sopenharmony_ci#define OCOTP_CTRL_WR_UNLOCK 16 3362306a36Sopenharmony_ci#define OCOTP_CTRL_WR_UNLOCK_KEY 0x3E77 3462306a36Sopenharmony_ci#define OCOTP_CTRL_WR_UNLOCK_MASK GENMASK(31, 16) 3562306a36Sopenharmony_ci#define OCOTP_CTRL_ADDR 0 3662306a36Sopenharmony_ci#define OCOTP_CTRL_ADDR_MASK GENMASK(6, 0) 3762306a36Sopenharmony_ci#define OCOTP_CTRL_RELOAD_SHADOWS BIT(10) 3862306a36Sopenharmony_ci#define OCOTP_CTRL_ERR BIT(9) 3962306a36Sopenharmony_ci#define OCOTP_CTRL_BUSY BIT(8) 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci#define OCOTP_TIMING_STROBE_READ 16 4262306a36Sopenharmony_ci#define OCOTP_TIMING_STROBE_READ_MASK GENMASK(21, 16) 4362306a36Sopenharmony_ci#define OCOTP_TIMING_RELAX 12 4462306a36Sopenharmony_ci#define OCOTP_TIMING_RELAX_MASK GENMASK(15, 12) 4562306a36Sopenharmony_ci#define OCOTP_TIMING_STROBE_PROG 0 4662306a36Sopenharmony_ci#define OCOTP_TIMING_STROBE_PROG_MASK GENMASK(11, 0) 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci#define OCOTP_READ_CTRL_READ_FUSE 0x1 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci#define VF610_OCOTP_TIMEOUT 100000 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci#define BF(value, field) (((value) << field) & field##_MASK) 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci#define DEF_RELAX 20 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic const int base_to_fuse_addr_mappings[][2] = { 5762306a36Sopenharmony_ci {0x400, 0x00}, 5862306a36Sopenharmony_ci {0x410, 0x01}, 5962306a36Sopenharmony_ci {0x420, 0x02}, 6062306a36Sopenharmony_ci {0x450, 0x05}, 6162306a36Sopenharmony_ci {0x4F0, 0x0F}, 6262306a36Sopenharmony_ci {0x600, 0x20}, 6362306a36Sopenharmony_ci {0x610, 0x21}, 6462306a36Sopenharmony_ci {0x620, 0x22}, 6562306a36Sopenharmony_ci {0x630, 0x23}, 6662306a36Sopenharmony_ci {0x640, 0x24}, 6762306a36Sopenharmony_ci {0x650, 0x25}, 6862306a36Sopenharmony_ci {0x660, 0x26}, 6962306a36Sopenharmony_ci {0x670, 0x27}, 7062306a36Sopenharmony_ci {0x6F0, 0x2F}, 7162306a36Sopenharmony_ci {0x880, 0x38}, 7262306a36Sopenharmony_ci {0x890, 0x39}, 7362306a36Sopenharmony_ci {0x8A0, 0x3A}, 7462306a36Sopenharmony_ci {0x8B0, 0x3B}, 7562306a36Sopenharmony_ci {0x8C0, 0x3C}, 7662306a36Sopenharmony_ci {0x8D0, 0x3D}, 7762306a36Sopenharmony_ci {0x8E0, 0x3E}, 7862306a36Sopenharmony_ci {0x8F0, 0x3F}, 7962306a36Sopenharmony_ci {0xC80, 0x78}, 8062306a36Sopenharmony_ci {0xC90, 0x79}, 8162306a36Sopenharmony_ci {0xCA0, 0x7A}, 8262306a36Sopenharmony_ci {0xCB0, 0x7B}, 8362306a36Sopenharmony_ci {0xCC0, 0x7C}, 8462306a36Sopenharmony_ci {0xCD0, 0x7D}, 8562306a36Sopenharmony_ci {0xCE0, 0x7E}, 8662306a36Sopenharmony_ci {0xCF0, 0x7F}, 8762306a36Sopenharmony_ci}; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistruct vf610_ocotp { 9062306a36Sopenharmony_ci void __iomem *base; 9162306a36Sopenharmony_ci struct clk *clk; 9262306a36Sopenharmony_ci struct device *dev; 9362306a36Sopenharmony_ci struct nvmem_device *nvmem; 9462306a36Sopenharmony_ci int timing; 9562306a36Sopenharmony_ci}; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic int vf610_ocotp_wait_busy(void __iomem *base) 9862306a36Sopenharmony_ci{ 9962306a36Sopenharmony_ci int timeout = VF610_OCOTP_TIMEOUT; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci while ((readl(base) & OCOTP_CTRL_BUSY) && --timeout) 10262306a36Sopenharmony_ci udelay(10); 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci if (!timeout) { 10562306a36Sopenharmony_ci writel(OCOTP_CTRL_ERR, base + OCOTP_CTRL_CLR); 10662306a36Sopenharmony_ci return -ETIMEDOUT; 10762306a36Sopenharmony_ci } 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci udelay(10); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci return 0; 11262306a36Sopenharmony_ci} 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cistatic int vf610_ocotp_calculate_timing(struct vf610_ocotp *ocotp_dev) 11562306a36Sopenharmony_ci{ 11662306a36Sopenharmony_ci u32 clk_rate; 11762306a36Sopenharmony_ci u32 relax, strobe_read, strobe_prog; 11862306a36Sopenharmony_ci u32 timing; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci clk_rate = clk_get_rate(ocotp_dev->clk); 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci /* Refer section OTP read/write timing parameters in TRM */ 12362306a36Sopenharmony_ci relax = clk_rate / (1000000000 / DEF_RELAX) - 1; 12462306a36Sopenharmony_ci strobe_prog = clk_rate / (1000000000 / 10000) + 2 * (DEF_RELAX + 1) - 1; 12562306a36Sopenharmony_ci strobe_read = clk_rate / (1000000000 / 40) + 2 * (DEF_RELAX + 1) - 1; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci timing = BF(relax, OCOTP_TIMING_RELAX); 12862306a36Sopenharmony_ci timing |= BF(strobe_read, OCOTP_TIMING_STROBE_READ); 12962306a36Sopenharmony_ci timing |= BF(strobe_prog, OCOTP_TIMING_STROBE_PROG); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci return timing; 13262306a36Sopenharmony_ci} 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistatic int vf610_get_fuse_address(int base_addr_offset) 13562306a36Sopenharmony_ci{ 13662306a36Sopenharmony_ci int i; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(base_to_fuse_addr_mappings); i++) { 13962306a36Sopenharmony_ci if (base_to_fuse_addr_mappings[i][0] == base_addr_offset) 14062306a36Sopenharmony_ci return base_to_fuse_addr_mappings[i][1]; 14162306a36Sopenharmony_ci } 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci return -EINVAL; 14462306a36Sopenharmony_ci} 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_cistatic int vf610_ocotp_read(void *context, unsigned int offset, 14762306a36Sopenharmony_ci void *val, size_t bytes) 14862306a36Sopenharmony_ci{ 14962306a36Sopenharmony_ci struct vf610_ocotp *ocotp = context; 15062306a36Sopenharmony_ci void __iomem *base = ocotp->base; 15162306a36Sopenharmony_ci u32 reg, *buf = val; 15262306a36Sopenharmony_ci int fuse_addr; 15362306a36Sopenharmony_ci int ret; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci while (bytes > 0) { 15662306a36Sopenharmony_ci fuse_addr = vf610_get_fuse_address(offset); 15762306a36Sopenharmony_ci if (fuse_addr > 0) { 15862306a36Sopenharmony_ci writel(ocotp->timing, base + OCOTP_TIMING); 15962306a36Sopenharmony_ci ret = vf610_ocotp_wait_busy(base + OCOTP_CTRL_REG); 16062306a36Sopenharmony_ci if (ret) 16162306a36Sopenharmony_ci return ret; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci reg = readl(base + OCOTP_CTRL_REG); 16462306a36Sopenharmony_ci reg &= ~OCOTP_CTRL_ADDR_MASK; 16562306a36Sopenharmony_ci reg &= ~OCOTP_CTRL_WR_UNLOCK_MASK; 16662306a36Sopenharmony_ci reg |= BF(fuse_addr, OCOTP_CTRL_ADDR); 16762306a36Sopenharmony_ci writel(reg, base + OCOTP_CTRL_REG); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci writel(OCOTP_READ_CTRL_READ_FUSE, 17062306a36Sopenharmony_ci base + OCOTP_READ_CTRL_REG); 17162306a36Sopenharmony_ci ret = vf610_ocotp_wait_busy(base + OCOTP_CTRL_REG); 17262306a36Sopenharmony_ci if (ret) 17362306a36Sopenharmony_ci return ret; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci if (readl(base) & OCOTP_CTRL_ERR) { 17662306a36Sopenharmony_ci dev_dbg(ocotp->dev, "Error reading from fuse address %x\n", 17762306a36Sopenharmony_ci fuse_addr); 17862306a36Sopenharmony_ci writel(OCOTP_CTRL_ERR, base + OCOTP_CTRL_CLR); 17962306a36Sopenharmony_ci } 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci /* 18262306a36Sopenharmony_ci * In case of error, we do not abort and expect to read 18362306a36Sopenharmony_ci * 0xBADABADA as mentioned by the TRM. We just read this 18462306a36Sopenharmony_ci * value and return. 18562306a36Sopenharmony_ci */ 18662306a36Sopenharmony_ci *buf = readl(base + OCOTP_READ_FUSE_DATA); 18762306a36Sopenharmony_ci } else { 18862306a36Sopenharmony_ci *buf = 0; 18962306a36Sopenharmony_ci } 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci buf++; 19262306a36Sopenharmony_ci bytes -= 4; 19362306a36Sopenharmony_ci offset += 4; 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci return 0; 19762306a36Sopenharmony_ci} 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_cistatic struct nvmem_config ocotp_config = { 20062306a36Sopenharmony_ci .name = "ocotp", 20162306a36Sopenharmony_ci .stride = 4, 20262306a36Sopenharmony_ci .word_size = 4, 20362306a36Sopenharmony_ci .reg_read = vf610_ocotp_read, 20462306a36Sopenharmony_ci}; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_cistatic const struct of_device_id ocotp_of_match[] = { 20762306a36Sopenharmony_ci { .compatible = "fsl,vf610-ocotp", }, 20862306a36Sopenharmony_ci {/* sentinel */}, 20962306a36Sopenharmony_ci}; 21062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, ocotp_of_match); 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cistatic int vf610_ocotp_probe(struct platform_device *pdev) 21362306a36Sopenharmony_ci{ 21462306a36Sopenharmony_ci struct device *dev = &pdev->dev; 21562306a36Sopenharmony_ci struct resource *res; 21662306a36Sopenharmony_ci struct vf610_ocotp *ocotp_dev; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci ocotp_dev = devm_kzalloc(dev, sizeof(struct vf610_ocotp), GFP_KERNEL); 21962306a36Sopenharmony_ci if (!ocotp_dev) 22062306a36Sopenharmony_ci return -ENOMEM; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci ocotp_dev->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); 22362306a36Sopenharmony_ci if (IS_ERR(ocotp_dev->base)) 22462306a36Sopenharmony_ci return PTR_ERR(ocotp_dev->base); 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci ocotp_dev->clk = devm_clk_get(dev, NULL); 22762306a36Sopenharmony_ci if (IS_ERR(ocotp_dev->clk)) { 22862306a36Sopenharmony_ci dev_err(dev, "failed getting clock, err = %ld\n", 22962306a36Sopenharmony_ci PTR_ERR(ocotp_dev->clk)); 23062306a36Sopenharmony_ci return PTR_ERR(ocotp_dev->clk); 23162306a36Sopenharmony_ci } 23262306a36Sopenharmony_ci ocotp_dev->dev = dev; 23362306a36Sopenharmony_ci ocotp_dev->timing = vf610_ocotp_calculate_timing(ocotp_dev); 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci ocotp_config.size = resource_size(res); 23662306a36Sopenharmony_ci ocotp_config.priv = ocotp_dev; 23762306a36Sopenharmony_ci ocotp_config.dev = dev; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci ocotp_dev->nvmem = devm_nvmem_register(dev, &ocotp_config); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci return PTR_ERR_OR_ZERO(ocotp_dev->nvmem); 24262306a36Sopenharmony_ci} 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_cistatic struct platform_driver vf610_ocotp_driver = { 24562306a36Sopenharmony_ci .probe = vf610_ocotp_probe, 24662306a36Sopenharmony_ci .driver = { 24762306a36Sopenharmony_ci .name = "vf610-ocotp", 24862306a36Sopenharmony_ci .of_match_table = ocotp_of_match, 24962306a36Sopenharmony_ci }, 25062306a36Sopenharmony_ci}; 25162306a36Sopenharmony_cimodule_platform_driver(vf610_ocotp_driver); 25262306a36Sopenharmony_ciMODULE_AUTHOR("Sanchayan Maity <sanchayan.maity@toradex.com>"); 25362306a36Sopenharmony_ciMODULE_DESCRIPTION("Vybrid OCOTP driver"); 25462306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 255