162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * OTP Memory controller 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Author: Claudiu Beznea <claudiu.beznea@microchip.com> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/bitfield.h> 1162306a36Sopenharmony_ci#include <linux/iopoll.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/nvmem-provider.h> 1462306a36Sopenharmony_ci#include <linux/of.h> 1562306a36Sopenharmony_ci#include <linux/platform_device.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define MCHP_OTPC_CR (0x0) 1862306a36Sopenharmony_ci#define MCHP_OTPC_CR_READ BIT(6) 1962306a36Sopenharmony_ci#define MCHP_OTPC_MR (0x4) 2062306a36Sopenharmony_ci#define MCHP_OTPC_MR_ADDR GENMASK(31, 16) 2162306a36Sopenharmony_ci#define MCHP_OTPC_AR (0x8) 2262306a36Sopenharmony_ci#define MCHP_OTPC_SR (0xc) 2362306a36Sopenharmony_ci#define MCHP_OTPC_SR_READ BIT(6) 2462306a36Sopenharmony_ci#define MCHP_OTPC_HR (0x20) 2562306a36Sopenharmony_ci#define MCHP_OTPC_HR_SIZE GENMASK(15, 8) 2662306a36Sopenharmony_ci#define MCHP_OTPC_DR (0x24) 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#define MCHP_OTPC_NAME "mchp-otpc" 2962306a36Sopenharmony_ci#define MCHP_OTPC_SIZE (11 * 1024) 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci/** 3262306a36Sopenharmony_ci * struct mchp_otpc - OTPC private data structure 3362306a36Sopenharmony_ci * @base: base address 3462306a36Sopenharmony_ci * @dev: struct device pointer 3562306a36Sopenharmony_ci * @packets: list of packets in OTP memory 3662306a36Sopenharmony_ci * @npackets: number of packets in OTP memory 3762306a36Sopenharmony_ci */ 3862306a36Sopenharmony_cistruct mchp_otpc { 3962306a36Sopenharmony_ci void __iomem *base; 4062306a36Sopenharmony_ci struct device *dev; 4162306a36Sopenharmony_ci struct list_head packets; 4262306a36Sopenharmony_ci u32 npackets; 4362306a36Sopenharmony_ci}; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci/** 4662306a36Sopenharmony_ci * struct mchp_otpc_packet - OTPC packet data structure 4762306a36Sopenharmony_ci * @list: list head 4862306a36Sopenharmony_ci * @id: packet ID 4962306a36Sopenharmony_ci * @offset: packet offset (in words) in OTP memory 5062306a36Sopenharmony_ci */ 5162306a36Sopenharmony_cistruct mchp_otpc_packet { 5262306a36Sopenharmony_ci struct list_head list; 5362306a36Sopenharmony_ci u32 id; 5462306a36Sopenharmony_ci u32 offset; 5562306a36Sopenharmony_ci}; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_cistatic struct mchp_otpc_packet *mchp_otpc_id_to_packet(struct mchp_otpc *otpc, 5862306a36Sopenharmony_ci u32 id) 5962306a36Sopenharmony_ci{ 6062306a36Sopenharmony_ci struct mchp_otpc_packet *packet; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci if (id >= otpc->npackets) 6362306a36Sopenharmony_ci return NULL; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci list_for_each_entry(packet, &otpc->packets, list) { 6662306a36Sopenharmony_ci if (packet->id == id) 6762306a36Sopenharmony_ci return packet; 6862306a36Sopenharmony_ci } 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci return NULL; 7162306a36Sopenharmony_ci} 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic int mchp_otpc_prepare_read(struct mchp_otpc *otpc, 7462306a36Sopenharmony_ci unsigned int offset) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci u32 tmp; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci /* Set address. */ 7962306a36Sopenharmony_ci tmp = readl_relaxed(otpc->base + MCHP_OTPC_MR); 8062306a36Sopenharmony_ci tmp &= ~MCHP_OTPC_MR_ADDR; 8162306a36Sopenharmony_ci tmp |= FIELD_PREP(MCHP_OTPC_MR_ADDR, offset); 8262306a36Sopenharmony_ci writel_relaxed(tmp, otpc->base + MCHP_OTPC_MR); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci /* Set read. */ 8562306a36Sopenharmony_ci tmp = readl_relaxed(otpc->base + MCHP_OTPC_CR); 8662306a36Sopenharmony_ci tmp |= MCHP_OTPC_CR_READ; 8762306a36Sopenharmony_ci writel_relaxed(tmp, otpc->base + MCHP_OTPC_CR); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci /* Wait for packet to be transferred into temporary buffers. */ 9062306a36Sopenharmony_ci return read_poll_timeout(readl_relaxed, tmp, !(tmp & MCHP_OTPC_SR_READ), 9162306a36Sopenharmony_ci 10000, 2000, false, otpc->base + MCHP_OTPC_SR); 9262306a36Sopenharmony_ci} 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci/* 9562306a36Sopenharmony_ci * OTPC memory is organized into packets. Each packets contains a header and 9662306a36Sopenharmony_ci * a payload. Header is 4 bytes long and contains the size of the payload. 9762306a36Sopenharmony_ci * Payload size varies. The memory footprint is something as follows: 9862306a36Sopenharmony_ci * 9962306a36Sopenharmony_ci * Memory offset Memory footprint Packet ID 10062306a36Sopenharmony_ci * ------------- ---------------- --------- 10162306a36Sopenharmony_ci * 10262306a36Sopenharmony_ci * 0x0 +------------+ <-- packet 0 10362306a36Sopenharmony_ci * | header 0 | 10462306a36Sopenharmony_ci * 0x4 +------------+ 10562306a36Sopenharmony_ci * | payload 0 | 10662306a36Sopenharmony_ci * . . 10762306a36Sopenharmony_ci * . ... . 10862306a36Sopenharmony_ci * . . 10962306a36Sopenharmony_ci * offset1 +------------+ <-- packet 1 11062306a36Sopenharmony_ci * | header 1 | 11162306a36Sopenharmony_ci * offset1 + 0x4 +------------+ 11262306a36Sopenharmony_ci * | payload 1 | 11362306a36Sopenharmony_ci * . . 11462306a36Sopenharmony_ci * . ... . 11562306a36Sopenharmony_ci * . . 11662306a36Sopenharmony_ci * offset2 +------------+ <-- packet 2 11762306a36Sopenharmony_ci * . . 11862306a36Sopenharmony_ci * . ... . 11962306a36Sopenharmony_ci * . . 12062306a36Sopenharmony_ci * offsetN +------------+ <-- packet N 12162306a36Sopenharmony_ci * | header N | 12262306a36Sopenharmony_ci * offsetN + 0x4 +------------+ 12362306a36Sopenharmony_ci * | payload N | 12462306a36Sopenharmony_ci * . . 12562306a36Sopenharmony_ci * . ... . 12662306a36Sopenharmony_ci * . . 12762306a36Sopenharmony_ci * +------------+ 12862306a36Sopenharmony_ci * 12962306a36Sopenharmony_ci * where offset1, offset2, offsetN depends on the size of payload 0, payload 1, 13062306a36Sopenharmony_ci * payload N-1. 13162306a36Sopenharmony_ci * 13262306a36Sopenharmony_ci * The access to memory is done on a per packet basis: the control registers 13362306a36Sopenharmony_ci * need to be updated with an offset address (within a packet range) and the 13462306a36Sopenharmony_ci * data registers will be update by controller with information contained by 13562306a36Sopenharmony_ci * that packet. E.g. if control registers are updated with any address within 13662306a36Sopenharmony_ci * the range [offset1, offset2) the data registers are updated by controller 13762306a36Sopenharmony_ci * with packet 1. Header data is accessible though MCHP_OTPC_HR register. 13862306a36Sopenharmony_ci * Payload data is accessible though MCHP_OTPC_DR and MCHP_OTPC_AR registers. 13962306a36Sopenharmony_ci * There is no direct mapping b/w the offset requested by software and the 14062306a36Sopenharmony_ci * offset returned by hardware. 14162306a36Sopenharmony_ci * 14262306a36Sopenharmony_ci * For this, the read function will return the first requested bytes in the 14362306a36Sopenharmony_ci * packet. The user will have to be aware of the memory footprint before doing 14462306a36Sopenharmony_ci * the read request. 14562306a36Sopenharmony_ci */ 14662306a36Sopenharmony_cistatic int mchp_otpc_read(void *priv, unsigned int off, void *val, 14762306a36Sopenharmony_ci size_t bytes) 14862306a36Sopenharmony_ci{ 14962306a36Sopenharmony_ci struct mchp_otpc *otpc = priv; 15062306a36Sopenharmony_ci struct mchp_otpc_packet *packet; 15162306a36Sopenharmony_ci u32 *buf = val; 15262306a36Sopenharmony_ci u32 offset; 15362306a36Sopenharmony_ci size_t len = 0; 15462306a36Sopenharmony_ci int ret, payload_size; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci /* 15762306a36Sopenharmony_ci * We reach this point with off being multiple of stride = 4 to 15862306a36Sopenharmony_ci * be able to cross the subsystem. Inside the driver we use continuous 15962306a36Sopenharmony_ci * unsigned integer numbers for packet id, thus devide off by 4 16062306a36Sopenharmony_ci * before passing it to mchp_otpc_id_to_packet(). 16162306a36Sopenharmony_ci */ 16262306a36Sopenharmony_ci packet = mchp_otpc_id_to_packet(otpc, off / 4); 16362306a36Sopenharmony_ci if (!packet) 16462306a36Sopenharmony_ci return -EINVAL; 16562306a36Sopenharmony_ci offset = packet->offset; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci while (len < bytes) { 16862306a36Sopenharmony_ci ret = mchp_otpc_prepare_read(otpc, offset); 16962306a36Sopenharmony_ci if (ret) 17062306a36Sopenharmony_ci return ret; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci /* Read and save header content. */ 17362306a36Sopenharmony_ci *buf++ = readl_relaxed(otpc->base + MCHP_OTPC_HR); 17462306a36Sopenharmony_ci len += sizeof(*buf); 17562306a36Sopenharmony_ci offset++; 17662306a36Sopenharmony_ci if (len >= bytes) 17762306a36Sopenharmony_ci break; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci /* Read and save payload content. */ 18062306a36Sopenharmony_ci payload_size = FIELD_GET(MCHP_OTPC_HR_SIZE, *(buf - 1)); 18162306a36Sopenharmony_ci writel_relaxed(0UL, otpc->base + MCHP_OTPC_AR); 18262306a36Sopenharmony_ci do { 18362306a36Sopenharmony_ci *buf++ = readl_relaxed(otpc->base + MCHP_OTPC_DR); 18462306a36Sopenharmony_ci len += sizeof(*buf); 18562306a36Sopenharmony_ci offset++; 18662306a36Sopenharmony_ci payload_size--; 18762306a36Sopenharmony_ci } while (payload_size >= 0 && len < bytes); 18862306a36Sopenharmony_ci } 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci return 0; 19162306a36Sopenharmony_ci} 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_cistatic int mchp_otpc_init_packets_list(struct mchp_otpc *otpc, u32 *size) 19462306a36Sopenharmony_ci{ 19562306a36Sopenharmony_ci struct mchp_otpc_packet *packet; 19662306a36Sopenharmony_ci u32 word, word_pos = 0, id = 0, npackets = 0, payload_size; 19762306a36Sopenharmony_ci int ret; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci INIT_LIST_HEAD(&otpc->packets); 20062306a36Sopenharmony_ci *size = 0; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci while (*size < MCHP_OTPC_SIZE) { 20362306a36Sopenharmony_ci ret = mchp_otpc_prepare_read(otpc, word_pos); 20462306a36Sopenharmony_ci if (ret) 20562306a36Sopenharmony_ci return ret; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci word = readl_relaxed(otpc->base + MCHP_OTPC_HR); 20862306a36Sopenharmony_ci payload_size = FIELD_GET(MCHP_OTPC_HR_SIZE, word); 20962306a36Sopenharmony_ci if (!payload_size) 21062306a36Sopenharmony_ci break; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci packet = devm_kzalloc(otpc->dev, sizeof(*packet), GFP_KERNEL); 21362306a36Sopenharmony_ci if (!packet) 21462306a36Sopenharmony_ci return -ENOMEM; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci packet->id = id++; 21762306a36Sopenharmony_ci packet->offset = word_pos; 21862306a36Sopenharmony_ci INIT_LIST_HEAD(&packet->list); 21962306a36Sopenharmony_ci list_add_tail(&packet->list, &otpc->packets); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci /* Count size by adding header and paload sizes. */ 22262306a36Sopenharmony_ci *size += 4 * (payload_size + 1); 22362306a36Sopenharmony_ci /* Next word: this packet (header, payload) position + 1. */ 22462306a36Sopenharmony_ci word_pos += payload_size + 2; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci npackets++; 22762306a36Sopenharmony_ci } 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci otpc->npackets = npackets; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci return 0; 23262306a36Sopenharmony_ci} 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_cistatic struct nvmem_config mchp_nvmem_config = { 23562306a36Sopenharmony_ci .name = MCHP_OTPC_NAME, 23662306a36Sopenharmony_ci .type = NVMEM_TYPE_OTP, 23762306a36Sopenharmony_ci .read_only = true, 23862306a36Sopenharmony_ci .word_size = 4, 23962306a36Sopenharmony_ci .stride = 4, 24062306a36Sopenharmony_ci .reg_read = mchp_otpc_read, 24162306a36Sopenharmony_ci}; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_cistatic int mchp_otpc_probe(struct platform_device *pdev) 24462306a36Sopenharmony_ci{ 24562306a36Sopenharmony_ci struct nvmem_device *nvmem; 24662306a36Sopenharmony_ci struct mchp_otpc *otpc; 24762306a36Sopenharmony_ci u32 size; 24862306a36Sopenharmony_ci int ret; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci otpc = devm_kzalloc(&pdev->dev, sizeof(*otpc), GFP_KERNEL); 25162306a36Sopenharmony_ci if (!otpc) 25262306a36Sopenharmony_ci return -ENOMEM; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci otpc->base = devm_platform_ioremap_resource(pdev, 0); 25562306a36Sopenharmony_ci if (IS_ERR(otpc->base)) 25662306a36Sopenharmony_ci return PTR_ERR(otpc->base); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci otpc->dev = &pdev->dev; 25962306a36Sopenharmony_ci ret = mchp_otpc_init_packets_list(otpc, &size); 26062306a36Sopenharmony_ci if (ret) 26162306a36Sopenharmony_ci return ret; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci mchp_nvmem_config.dev = otpc->dev; 26462306a36Sopenharmony_ci mchp_nvmem_config.size = size; 26562306a36Sopenharmony_ci mchp_nvmem_config.priv = otpc; 26662306a36Sopenharmony_ci nvmem = devm_nvmem_register(&pdev->dev, &mchp_nvmem_config); 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci return PTR_ERR_OR_ZERO(nvmem); 26962306a36Sopenharmony_ci} 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_cistatic const struct of_device_id __maybe_unused mchp_otpc_ids[] = { 27262306a36Sopenharmony_ci { .compatible = "microchip,sama7g5-otpc", }, 27362306a36Sopenharmony_ci { }, 27462306a36Sopenharmony_ci}; 27562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, mchp_otpc_ids); 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_cistatic struct platform_driver mchp_otpc_driver = { 27862306a36Sopenharmony_ci .probe = mchp_otpc_probe, 27962306a36Sopenharmony_ci .driver = { 28062306a36Sopenharmony_ci .name = MCHP_OTPC_NAME, 28162306a36Sopenharmony_ci .of_match_table = of_match_ptr(mchp_otpc_ids), 28262306a36Sopenharmony_ci }, 28362306a36Sopenharmony_ci}; 28462306a36Sopenharmony_cimodule_platform_driver(mchp_otpc_driver); 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ciMODULE_AUTHOR("Claudiu Beznea <claudiu.beznea@microchip.com>"); 28762306a36Sopenharmony_ciMODULE_DESCRIPTION("Microchip SAMA7G5 OTPC driver"); 28862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 289