289 lines · 7.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * OTP Memory controller * * Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries * * Author: Claudiu Beznea <claudiu.beznea@microchip.com> */ #include <linux/bitfield.h>#include <linux/iopoll.h>#include <linux/module.h>#include <linux/nvmem-provider.h>#include <linux/of.h>#include <linux/platform_device.h> #define MCHP_OTPC_CR			(0x0)#define MCHP_OTPC_CR_READ		BIT(6)#define MCHP_OTPC_MR			(0x4)#define MCHP_OTPC_MR_ADDR		GENMASK(31, 16)#define MCHP_OTPC_AR			(0x8)#define MCHP_OTPC_SR			(0xc)#define MCHP_OTPC_SR_READ		BIT(6)#define MCHP_OTPC_HR			(0x20)#define MCHP_OTPC_HR_SIZE		GENMASK(15, 8)#define MCHP_OTPC_DR			(0x24) #define MCHP_OTPC_NAME			"mchp-otpc"#define MCHP_OTPC_SIZE			(11 * 1024) /** * struct mchp_otpc - OTPC private data structure * @base: base address * @dev: struct device pointer * @packets: list of packets in OTP memory * @npackets: number of packets in OTP memory */struct mchp_otpc {	void __iomem *base;	struct device *dev;	struct list_head packets;	u32 npackets;}; /** * struct mchp_otpc_packet - OTPC packet data structure * @list: list head * @id: packet ID * @offset: packet offset (in words) in OTP memory */struct mchp_otpc_packet {	struct list_head list;	u32 id;	u32 offset;}; static struct mchp_otpc_packet *mchp_otpc_id_to_packet(struct mchp_otpc *otpc,						       u32 id){	struct mchp_otpc_packet *packet; 	if (id >= otpc->npackets)		return NULL; 	list_for_each_entry(packet, &otpc->packets, list) {		if (packet->id == id)			return packet;	} 	return NULL;} static int mchp_otpc_prepare_read(struct mchp_otpc *otpc,				  unsigned int offset){	u32 tmp; 	/* Set address. */	tmp = readl_relaxed(otpc->base + MCHP_OTPC_MR);	tmp &= ~MCHP_OTPC_MR_ADDR;	tmp |= FIELD_PREP(MCHP_OTPC_MR_ADDR, offset);	writel_relaxed(tmp, otpc->base + MCHP_OTPC_MR); 	/* Set read. */	tmp = readl_relaxed(otpc->base + MCHP_OTPC_CR);	tmp |= MCHP_OTPC_CR_READ;	writel_relaxed(tmp, otpc->base + MCHP_OTPC_CR); 	/* Wait for packet to be transferred into temporary buffers. */	return read_poll_timeout(readl_relaxed, tmp, !(tmp & MCHP_OTPC_SR_READ),				 10000, 2000, false, otpc->base + MCHP_OTPC_SR);} /* * OTPC memory is organized into packets. Each packets contains a header and * a payload. Header is 4 bytes long and contains the size of the payload. * Payload size varies. The memory footprint is something as follows: * * Memory offset  Memory footprint     Packet ID * -------------  ----------------     --------- * * 0x0            +------------+   <-- packet 0 *                | header  0  | * 0x4            +------------+ *                | payload 0  | *                .            . *                .    ...     . *                .            . * offset1        +------------+   <-- packet 1 *                | header  1  | * offset1 + 0x4  +------------+ *                | payload 1  | *                .            . *                .    ...     . *                .            . * offset2        +------------+   <-- packet 2 *                .            . *                .    ...     . *                .            . * offsetN        +------------+   <-- packet N *                | header  N  | * offsetN + 0x4  +------------+ *                | payload N  | *                .            . *                .    ...     . *                .            . *                +------------+ * * where offset1, offset2, offsetN depends on the size of payload 0, payload 1, * payload N-1. * * The access to memory is done on a per packet basis: the control registers * need to be updated with an offset address (within a packet range) and the * data registers will be update by controller with information contained by * that packet. E.g. if control registers are updated with any address within * the range [offset1, offset2) the data registers are updated by controller * with packet 1. Header data is accessible though MCHP_OTPC_HR register. * Payload data is accessible though MCHP_OTPC_DR and MCHP_OTPC_AR registers. * There is no direct mapping b/w the offset requested by software and the * offset returned by hardware. * * For this, the read function will return the first requested bytes in the * packet. The user will have to be aware of the memory footprint before doing * the read request. */static int mchp_otpc_read(void *priv, unsigned int off, void *val,			  size_t bytes){	struct mchp_otpc *otpc = priv;	struct mchp_otpc_packet *packet;	u32 *buf = val;	u32 offset;	size_t len = 0;	int ret, payload_size; 	/*	 * We reach this point with off being multiple of stride = 4 to	 * be able to cross the subsystem. Inside the driver we use continuous	 * unsigned integer numbers for packet id, thus divide off by 4	 * before passing it to mchp_otpc_id_to_packet().	 */	packet = mchp_otpc_id_to_packet(otpc, off / 4);	if (!packet)		return -EINVAL;	offset = packet->offset; 	while (len < bytes) {		ret = mchp_otpc_prepare_read(otpc, offset);		if (ret)			return ret; 		/* Read and save header content. */		*buf++ = readl_relaxed(otpc->base + MCHP_OTPC_HR);		len += sizeof(*buf);		offset++;		if (len >= bytes)			break; 		/* Read and save payload content. */		payload_size = FIELD_GET(MCHP_OTPC_HR_SIZE, *(buf - 1));		writel_relaxed(0UL, otpc->base + MCHP_OTPC_AR);		do {			*buf++ = readl_relaxed(otpc->base + MCHP_OTPC_DR);			len += sizeof(*buf);			offset++;			payload_size--;		} while (payload_size >= 0 && len < bytes);	} 	return 0;} static int mchp_otpc_init_packets_list(struct mchp_otpc *otpc, u32 *size){	struct mchp_otpc_packet *packet;	u32 word, word_pos = 0, id = 0, npackets = 0, payload_size;	int ret; 	INIT_LIST_HEAD(&otpc->packets);	*size = 0; 	while (*size < MCHP_OTPC_SIZE) {		ret = mchp_otpc_prepare_read(otpc, word_pos);		if (ret)			return ret; 		word = readl_relaxed(otpc->base + MCHP_OTPC_HR);		payload_size = FIELD_GET(MCHP_OTPC_HR_SIZE, word);		if (!payload_size)			break; 		packet = devm_kzalloc(otpc->dev, sizeof(*packet), GFP_KERNEL);		if (!packet)			return -ENOMEM; 		packet->id = id++;		packet->offset = word_pos;		INIT_LIST_HEAD(&packet->list);		list_add_tail(&packet->list, &otpc->packets); 		/* Count size by adding header and paload sizes. */		*size += 4 * (payload_size + 1);		/* Next word: this packet (header, payload) position + 1. */		word_pos += payload_size + 2; 		npackets++;	} 	otpc->npackets = npackets; 	return 0;} static struct nvmem_config mchp_nvmem_config = {	.name = MCHP_OTPC_NAME,	.type = NVMEM_TYPE_OTP,	.read_only = true,	.word_size = 4,	.stride = 4,	.reg_read = mchp_otpc_read,}; static int mchp_otpc_probe(struct platform_device *pdev){	struct nvmem_device *nvmem;	struct mchp_otpc *otpc;	u32 size;	int ret; 	otpc = devm_kzalloc(&pdev->dev, sizeof(*otpc), GFP_KERNEL);	if (!otpc)		return -ENOMEM; 	otpc->base = devm_platform_ioremap_resource(pdev, 0);	if (IS_ERR(otpc->base))		return PTR_ERR(otpc->base); 	otpc->dev = &pdev->dev;	ret = mchp_otpc_init_packets_list(otpc, &size);	if (ret)		return ret; 	mchp_nvmem_config.dev = otpc->dev;	mchp_nvmem_config.add_legacy_fixed_of_cells = true;	mchp_nvmem_config.size = size;	mchp_nvmem_config.priv = otpc;	nvmem = devm_nvmem_register(&pdev->dev, &mchp_nvmem_config); 	return PTR_ERR_OR_ZERO(nvmem);} static const struct of_device_id __maybe_unused mchp_otpc_ids[] = {	{ .compatible = "microchip,sama7g5-otpc", },	{ },};MODULE_DEVICE_TABLE(of, mchp_otpc_ids); static struct platform_driver mchp_otpc_driver = {	.probe = mchp_otpc_probe,	.driver = {		.name = MCHP_OTPC_NAME,		.of_match_table = of_match_ptr(mchp_otpc_ids),	},};module_platform_driver(mchp_otpc_driver); MODULE_AUTHOR("Claudiu Beznea <claudiu.beznea@microchip.com>");MODULE_DESCRIPTION("Microchip SAMA7G5 OTPC driver");MODULE_LICENSE("GPL");