Quelle microchip-otpc.c
Sprache: 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 (0 x0)
#define MCHP_OTPC_CR_READ BIT(6 )
#define MCHP_OTPC_MR (0 x4)
#define MCHP_OTPC_MR_ADDR GENMASK(31 , 16 )
#define MCHP_OTPC_AR (0 x8)
#define MCHP_OTPC_SR (0 xc)
#define MCHP_OTPC_SR_READ BIT(6 )
#define MCHP_OTPC_HR (0 x20)
#define MCHP_OTPC_HR_SIZE GENMASK(15 , 8 )
#define MCHP_OTPC_DR (0 x24)
#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
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* 0 x0 + - - - - - - - - - - - - + < - - packet 0
* | header 0 |
* 0 x4 + - - - - - - - - - - - - +
* | payload 0 |
* . .
* . . . . .
* . .
* offset1 + - - - - - - - - - - - - + < - - packet 1
* | header 1 |
* offset1 + 0 x4 + - - - - - - - - - - - - +
* | payload 1 |
* . .
* . . . . .
* . .
* offset2 + - - - - - - - - - - - - + < - - packet 2
* . .
* . . . . .
* . .
* offsetN + - - - - - - - - - - - - + < - - packet N
* | header N |
* offsetN + 0 x4 + - - - - - - - - - - - - +
* | 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(0 UL, 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" );
Messung V0.5 in Prozent C=96 H=90 G=93
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland
2026-10-10
Neuigkeiten
Aktuelles
Motto des Tages
Open Source Software
Jenseits des Üblichen ....
Besucherstatistik
Statistik
#Sources=1126864
#Domains=1897691