2cc3b37f5b
Binding for fixed NVMEM cells defined directly as NVMEM device subnodes has been deprecated. It has been replaced by the "fixed-layout" NVMEM layout binding. New syntax is meant to be clearer and should help avoiding imprecise bindings. NVMEM subsystem already supports the new binding. It should be a good idea to limit support for old syntax to existing drivers that actually support & use it (we can't break backward compatibility!). That way we additionally encourage new bindings & drivers to ignore deprecated binding. It wasn't clear (to me) if rtc and w1 code actually uses old syntax fixed cells. I enabled them to don't risk any breakage. Signed-off-by: Rafał Miłecki <rafal@milecki.pl> [for meson-{efuse,mx-efuse}.c] Acked-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com> [for mtk-efuse.c, nvmem/core.c, nvmem-provider.h] Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> [MT8192, MT8195 Chromebooks] Tested-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> [for microchip-otpc.c] Reviewed-by: Claudiu Beznea <claudiu.beznea@microchip.com> [SAMA7G5-EK] Tested-by: Claudiu Beznea <claudiu.beznea@microchip.com> Acked-by: Jernej Skrabec <jernej.skrabec@gmail.com> Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org> Link: https://lore.kernel.org/r/20231020105545.216052-3-srinivas.kandagatla@linaro.org Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
290 lines
7.8 KiB
C
290 lines
7.8 KiB
C
// 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 devide 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");
|