brintos

brintos / linux-shallow public Read only

0
0
Text · 7.8 KiB · 7cf8173 Raw
290 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * OTP Memory controller4 *5 * Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries6 *7 * Author: Claudiu Beznea <claudiu.beznea@microchip.com>8 */9 10#include <linux/bitfield.h>11#include <linux/iopoll.h>12#include <linux/module.h>13#include <linux/nvmem-provider.h>14#include <linux/of.h>15#include <linux/platform_device.h>16 17#define MCHP_OTPC_CR			(0x0)18#define MCHP_OTPC_CR_READ		BIT(6)19#define MCHP_OTPC_MR			(0x4)20#define MCHP_OTPC_MR_ADDR		GENMASK(31, 16)21#define MCHP_OTPC_AR			(0x8)22#define MCHP_OTPC_SR			(0xc)23#define MCHP_OTPC_SR_READ		BIT(6)24#define MCHP_OTPC_HR			(0x20)25#define MCHP_OTPC_HR_SIZE		GENMASK(15, 8)26#define MCHP_OTPC_DR			(0x24)27 28#define MCHP_OTPC_NAME			"mchp-otpc"29#define MCHP_OTPC_SIZE			(11 * 1024)30 31/**32 * struct mchp_otpc - OTPC private data structure33 * @base: base address34 * @dev: struct device pointer35 * @packets: list of packets in OTP memory36 * @npackets: number of packets in OTP memory37 */38struct mchp_otpc {39	void __iomem *base;40	struct device *dev;41	struct list_head packets;42	u32 npackets;43};44 45/**46 * struct mchp_otpc_packet - OTPC packet data structure47 * @list: list head48 * @id: packet ID49 * @offset: packet offset (in words) in OTP memory50 */51struct mchp_otpc_packet {52	struct list_head list;53	u32 id;54	u32 offset;55};56 57static struct mchp_otpc_packet *mchp_otpc_id_to_packet(struct mchp_otpc *otpc,58						       u32 id)59{60	struct mchp_otpc_packet *packet;61 62	if (id >= otpc->npackets)63		return NULL;64 65	list_for_each_entry(packet, &otpc->packets, list) {66		if (packet->id == id)67			return packet;68	}69 70	return NULL;71}72 73static int mchp_otpc_prepare_read(struct mchp_otpc *otpc,74				  unsigned int offset)75{76	u32 tmp;77 78	/* Set address. */79	tmp = readl_relaxed(otpc->base + MCHP_OTPC_MR);80	tmp &= ~MCHP_OTPC_MR_ADDR;81	tmp |= FIELD_PREP(MCHP_OTPC_MR_ADDR, offset);82	writel_relaxed(tmp, otpc->base + MCHP_OTPC_MR);83 84	/* Set read. */85	tmp = readl_relaxed(otpc->base + MCHP_OTPC_CR);86	tmp |= MCHP_OTPC_CR_READ;87	writel_relaxed(tmp, otpc->base + MCHP_OTPC_CR);88 89	/* Wait for packet to be transferred into temporary buffers. */90	return read_poll_timeout(readl_relaxed, tmp, !(tmp & MCHP_OTPC_SR_READ),91				 10000, 2000, false, otpc->base + MCHP_OTPC_SR);92}93 94/*95 * OTPC memory is organized into packets. Each packets contains a header and96 * a payload. Header is 4 bytes long and contains the size of the payload.97 * Payload size varies. The memory footprint is something as follows:98 *99 * Memory offset  Memory footprint     Packet ID100 * -------------  ----------------     ---------101 *102 * 0x0            +------------+   <-- packet 0103 *                | header  0  |104 * 0x4            +------------+105 *                | payload 0  |106 *                .            .107 *                .    ...     .108 *                .            .109 * offset1        +------------+   <-- packet 1110 *                | header  1  |111 * offset1 + 0x4  +------------+112 *                | payload 1  |113 *                .            .114 *                .    ...     .115 *                .            .116 * offset2        +------------+   <-- packet 2117 *                .            .118 *                .    ...     .119 *                .            .120 * offsetN        +------------+   <-- packet N121 *                | header  N  |122 * offsetN + 0x4  +------------+123 *                | payload N  |124 *                .            .125 *                .    ...     .126 *                .            .127 *                +------------+128 *129 * where offset1, offset2, offsetN depends on the size of payload 0, payload 1,130 * payload N-1.131 *132 * The access to memory is done on a per packet basis: the control registers133 * need to be updated with an offset address (within a packet range) and the134 * data registers will be update by controller with information contained by135 * that packet. E.g. if control registers are updated with any address within136 * the range [offset1, offset2) the data registers are updated by controller137 * with packet 1. Header data is accessible though MCHP_OTPC_HR register.138 * Payload data is accessible though MCHP_OTPC_DR and MCHP_OTPC_AR registers.139 * There is no direct mapping b/w the offset requested by software and the140 * offset returned by hardware.141 *142 * For this, the read function will return the first requested bytes in the143 * packet. The user will have to be aware of the memory footprint before doing144 * the read request.145 */146static int mchp_otpc_read(void *priv, unsigned int off, void *val,147			  size_t bytes)148{149	struct mchp_otpc *otpc = priv;150	struct mchp_otpc_packet *packet;151	u32 *buf = val;152	u32 offset;153	size_t len = 0;154	int ret, payload_size;155 156	/*157	 * We reach this point with off being multiple of stride = 4 to158	 * be able to cross the subsystem. Inside the driver we use continuous159	 * unsigned integer numbers for packet id, thus devide off by 4160	 * before passing it to mchp_otpc_id_to_packet().161	 */162	packet = mchp_otpc_id_to_packet(otpc, off / 4);163	if (!packet)164		return -EINVAL;165	offset = packet->offset;166 167	while (len < bytes) {168		ret = mchp_otpc_prepare_read(otpc, offset);169		if (ret)170			return ret;171 172		/* Read and save header content. */173		*buf++ = readl_relaxed(otpc->base + MCHP_OTPC_HR);174		len += sizeof(*buf);175		offset++;176		if (len >= bytes)177			break;178 179		/* Read and save payload content. */180		payload_size = FIELD_GET(MCHP_OTPC_HR_SIZE, *(buf - 1));181		writel_relaxed(0UL, otpc->base + MCHP_OTPC_AR);182		do {183			*buf++ = readl_relaxed(otpc->base + MCHP_OTPC_DR);184			len += sizeof(*buf);185			offset++;186			payload_size--;187		} while (payload_size >= 0 && len < bytes);188	}189 190	return 0;191}192 193static int mchp_otpc_init_packets_list(struct mchp_otpc *otpc, u32 *size)194{195	struct mchp_otpc_packet *packet;196	u32 word, word_pos = 0, id = 0, npackets = 0, payload_size;197	int ret;198 199	INIT_LIST_HEAD(&otpc->packets);200	*size = 0;201 202	while (*size < MCHP_OTPC_SIZE) {203		ret = mchp_otpc_prepare_read(otpc, word_pos);204		if (ret)205			return ret;206 207		word = readl_relaxed(otpc->base + MCHP_OTPC_HR);208		payload_size = FIELD_GET(MCHP_OTPC_HR_SIZE, word);209		if (!payload_size)210			break;211 212		packet = devm_kzalloc(otpc->dev, sizeof(*packet), GFP_KERNEL);213		if (!packet)214			return -ENOMEM;215 216		packet->id = id++;217		packet->offset = word_pos;218		INIT_LIST_HEAD(&packet->list);219		list_add_tail(&packet->list, &otpc->packets);220 221		/* Count size by adding header and paload sizes. */222		*size += 4 * (payload_size + 1);223		/* Next word: this packet (header, payload) position + 1. */224		word_pos += payload_size + 2;225 226		npackets++;227	}228 229	otpc->npackets = npackets;230 231	return 0;232}233 234static struct nvmem_config mchp_nvmem_config = {235	.name = MCHP_OTPC_NAME,236	.type = NVMEM_TYPE_OTP,237	.read_only = true,238	.word_size = 4,239	.stride = 4,240	.reg_read = mchp_otpc_read,241};242 243static int mchp_otpc_probe(struct platform_device *pdev)244{245	struct nvmem_device *nvmem;246	struct mchp_otpc *otpc;247	u32 size;248	int ret;249 250	otpc = devm_kzalloc(&pdev->dev, sizeof(*otpc), GFP_KERNEL);251	if (!otpc)252		return -ENOMEM;253 254	otpc->base = devm_platform_ioremap_resource(pdev, 0);255	if (IS_ERR(otpc->base))256		return PTR_ERR(otpc->base);257 258	otpc->dev = &pdev->dev;259	ret = mchp_otpc_init_packets_list(otpc, &size);260	if (ret)261		return ret;262 263	mchp_nvmem_config.dev = otpc->dev;264	mchp_nvmem_config.add_legacy_fixed_of_cells = true;265	mchp_nvmem_config.size = size;266	mchp_nvmem_config.priv = otpc;267	nvmem = devm_nvmem_register(&pdev->dev, &mchp_nvmem_config);268 269	return PTR_ERR_OR_ZERO(nvmem);270}271 272static const struct of_device_id __maybe_unused mchp_otpc_ids[] = {273	{ .compatible = "microchip,sama7g5-otpc", },274	{ },275};276MODULE_DEVICE_TABLE(of, mchp_otpc_ids);277 278static struct platform_driver mchp_otpc_driver = {279	.probe = mchp_otpc_probe,280	.driver = {281		.name = MCHP_OTPC_NAME,282		.of_match_table = of_match_ptr(mchp_otpc_ids),283	},284};285module_platform_driver(mchp_otpc_driver);286 287MODULE_AUTHOR("Claudiu Beznea <claudiu.beznea@microchip.com>");288MODULE_DESCRIPTION("Microchip SAMA7G5 OTPC driver");289MODULE_LICENSE("GPL");290