mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
288 lines
7.7 KiB
288 lines
7.7 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* OTP Memory controller |
|
* |
|
* Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries |
|
* |
|
* Author: Claudiu Beznea <[email protected]> |
|
*/ |
|
|
|
#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.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 <[email protected]>"); |
|
MODULE_DESCRIPTION("Microchip SAMA7G5 OTPC driver"); |
|
MODULE_LICENSE("GPL");
|
|
|