forked from 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.
326 lines
8.0 KiB
326 lines
8.0 KiB
/* |
|
* Copyright (C) 2016 Broadcom |
|
* |
|
* This program is free software; you can redistribute it and/or |
|
* modify it under the terms of the GNU General Public License as |
|
* published by the Free Software Foundation version 2. |
|
* |
|
* This program is distributed "as is" WITHOUT ANY WARRANTY of any |
|
* kind, whether express or implied; without even the implied warranty |
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU General Public License for more details. |
|
*/ |
|
|
|
#include <linux/acpi.h> |
|
#include <linux/delay.h> |
|
#include <linux/device.h> |
|
#include <linux/io.h> |
|
#include <linux/module.h> |
|
#include <linux/nvmem-provider.h> |
|
#include <linux/of.h> |
|
#include <linux/of_device.h> |
|
#include <linux/platform_device.h> |
|
|
|
/* |
|
* # of tries for OTP Status. The time to execute a command varies. The slowest |
|
* commands are writes which also vary based on the # of bits turned on. Writing |
|
* 0xffffffff takes ~3800 us. |
|
*/ |
|
#define OTPC_RETRIES 5000 |
|
|
|
/* Sequence to enable OTP program */ |
|
#define OTPC_PROG_EN_SEQ { 0xf, 0x4, 0x8, 0xd } |
|
|
|
/* OTPC Commands */ |
|
#define OTPC_CMD_READ 0x0 |
|
#define OTPC_CMD_OTP_PROG_ENABLE 0x2 |
|
#define OTPC_CMD_OTP_PROG_DISABLE 0x3 |
|
#define OTPC_CMD_PROGRAM 0x8 |
|
|
|
/* OTPC Status Bits */ |
|
#define OTPC_STAT_CMD_DONE BIT(1) |
|
#define OTPC_STAT_PROG_OK BIT(2) |
|
|
|
/* OTPC register definition */ |
|
#define OTPC_MODE_REG_OFFSET 0x0 |
|
#define OTPC_MODE_REG_OTPC_MODE 0 |
|
#define OTPC_COMMAND_OFFSET 0x4 |
|
#define OTPC_COMMAND_COMMAND_WIDTH 6 |
|
#define OTPC_CMD_START_OFFSET 0x8 |
|
#define OTPC_CMD_START_START 0 |
|
#define OTPC_CPU_STATUS_OFFSET 0xc |
|
#define OTPC_CPUADDR_REG_OFFSET 0x28 |
|
#define OTPC_CPUADDR_REG_OTPC_CPU_ADDRESS_WIDTH 16 |
|
#define OTPC_CPU_WRITE_REG_OFFSET 0x2c |
|
|
|
#define OTPC_CMD_MASK (BIT(OTPC_COMMAND_COMMAND_WIDTH) - 1) |
|
#define OTPC_ADDR_MASK (BIT(OTPC_CPUADDR_REG_OTPC_CPU_ADDRESS_WIDTH) - 1) |
|
|
|
|
|
struct otpc_map { |
|
/* in words. */ |
|
u32 otpc_row_size; |
|
/* 128 bit row / 4 words support. */ |
|
u16 data_r_offset[4]; |
|
/* 128 bit row / 4 words support. */ |
|
u16 data_w_offset[4]; |
|
}; |
|
|
|
static struct otpc_map otp_map = { |
|
.otpc_row_size = 1, |
|
.data_r_offset = {0x10}, |
|
.data_w_offset = {0x2c}, |
|
}; |
|
|
|
static struct otpc_map otp_map_v2 = { |
|
.otpc_row_size = 2, |
|
.data_r_offset = {0x10, 0x5c}, |
|
.data_w_offset = {0x2c, 0x64}, |
|
}; |
|
|
|
struct otpc_priv { |
|
struct device *dev; |
|
void __iomem *base; |
|
const struct otpc_map *map; |
|
struct nvmem_config *config; |
|
}; |
|
|
|
static inline void set_command(void __iomem *base, u32 command) |
|
{ |
|
writel(command & OTPC_CMD_MASK, base + OTPC_COMMAND_OFFSET); |
|
} |
|
|
|
static inline void set_cpu_address(void __iomem *base, u32 addr) |
|
{ |
|
writel(addr & OTPC_ADDR_MASK, base + OTPC_CPUADDR_REG_OFFSET); |
|
} |
|
|
|
static inline void set_start_bit(void __iomem *base) |
|
{ |
|
writel(1 << OTPC_CMD_START_START, base + OTPC_CMD_START_OFFSET); |
|
} |
|
|
|
static inline void reset_start_bit(void __iomem *base) |
|
{ |
|
writel(0, base + OTPC_CMD_START_OFFSET); |
|
} |
|
|
|
static inline void write_cpu_data(void __iomem *base, u32 value) |
|
{ |
|
writel(value, base + OTPC_CPU_WRITE_REG_OFFSET); |
|
} |
|
|
|
static int poll_cpu_status(void __iomem *base, u32 value) |
|
{ |
|
u32 status; |
|
u32 retries; |
|
|
|
for (retries = 0; retries < OTPC_RETRIES; retries++) { |
|
status = readl(base + OTPC_CPU_STATUS_OFFSET); |
|
if (status & value) |
|
break; |
|
udelay(1); |
|
} |
|
if (retries == OTPC_RETRIES) |
|
return -EAGAIN; |
|
|
|
return 0; |
|
} |
|
|
|
static int enable_ocotp_program(void __iomem *base) |
|
{ |
|
static const u32 vals[] = OTPC_PROG_EN_SEQ; |
|
int i; |
|
int ret; |
|
|
|
/* Write the magic sequence to enable programming */ |
|
set_command(base, OTPC_CMD_OTP_PROG_ENABLE); |
|
for (i = 0; i < ARRAY_SIZE(vals); i++) { |
|
write_cpu_data(base, vals[i]); |
|
set_start_bit(base); |
|
ret = poll_cpu_status(base, OTPC_STAT_CMD_DONE); |
|
reset_start_bit(base); |
|
if (ret) |
|
return ret; |
|
} |
|
|
|
return poll_cpu_status(base, OTPC_STAT_PROG_OK); |
|
} |
|
|
|
static int disable_ocotp_program(void __iomem *base) |
|
{ |
|
int ret; |
|
|
|
set_command(base, OTPC_CMD_OTP_PROG_DISABLE); |
|
set_start_bit(base); |
|
ret = poll_cpu_status(base, OTPC_STAT_PROG_OK); |
|
reset_start_bit(base); |
|
|
|
return ret; |
|
} |
|
|
|
static int bcm_otpc_read(void *context, unsigned int offset, void *val, |
|
size_t bytes) |
|
{ |
|
struct otpc_priv *priv = context; |
|
u32 *buf = val; |
|
u32 bytes_read; |
|
u32 address = offset / priv->config->word_size; |
|
int i, ret; |
|
|
|
for (bytes_read = 0; bytes_read < bytes;) { |
|
set_command(priv->base, OTPC_CMD_READ); |
|
set_cpu_address(priv->base, address++); |
|
set_start_bit(priv->base); |
|
ret = poll_cpu_status(priv->base, OTPC_STAT_CMD_DONE); |
|
if (ret) { |
|
dev_err(priv->dev, "otp read error: 0x%x", ret); |
|
return -EIO; |
|
} |
|
|
|
for (i = 0; i < priv->map->otpc_row_size; i++) { |
|
*buf++ = readl(priv->base + |
|
priv->map->data_r_offset[i]); |
|
bytes_read += sizeof(*buf); |
|
} |
|
|
|
reset_start_bit(priv->base); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int bcm_otpc_write(void *context, unsigned int offset, void *val, |
|
size_t bytes) |
|
{ |
|
struct otpc_priv *priv = context; |
|
u32 *buf = val; |
|
u32 bytes_written; |
|
u32 address = offset / priv->config->word_size; |
|
int i, ret; |
|
|
|
if (offset % priv->config->word_size) |
|
return -EINVAL; |
|
|
|
ret = enable_ocotp_program(priv->base); |
|
if (ret) |
|
return -EIO; |
|
|
|
for (bytes_written = 0; bytes_written < bytes;) { |
|
set_command(priv->base, OTPC_CMD_PROGRAM); |
|
set_cpu_address(priv->base, address++); |
|
for (i = 0; i < priv->map->otpc_row_size; i++) { |
|
writel(*buf, priv->base + priv->map->data_w_offset[i]); |
|
buf++; |
|
bytes_written += sizeof(*buf); |
|
} |
|
set_start_bit(priv->base); |
|
ret = poll_cpu_status(priv->base, OTPC_STAT_CMD_DONE); |
|
reset_start_bit(priv->base); |
|
if (ret) { |
|
dev_err(priv->dev, "otp write error: 0x%x", ret); |
|
return -EIO; |
|
} |
|
} |
|
|
|
disable_ocotp_program(priv->base); |
|
|
|
return 0; |
|
} |
|
|
|
static struct nvmem_config bcm_otpc_nvmem_config = { |
|
.name = "bcm-ocotp", |
|
.read_only = false, |
|
.word_size = 4, |
|
.stride = 4, |
|
.reg_read = bcm_otpc_read, |
|
.reg_write = bcm_otpc_write, |
|
}; |
|
|
|
static const struct of_device_id bcm_otpc_dt_ids[] = { |
|
{ .compatible = "brcm,ocotp", .data = &otp_map }, |
|
{ .compatible = "brcm,ocotp-v2", .data = &otp_map_v2 }, |
|
{ }, |
|
}; |
|
MODULE_DEVICE_TABLE(of, bcm_otpc_dt_ids); |
|
|
|
static const struct acpi_device_id bcm_otpc_acpi_ids[] = { |
|
{ .id = "BRCM0700", .driver_data = (kernel_ulong_t)&otp_map }, |
|
{ .id = "BRCM0701", .driver_data = (kernel_ulong_t)&otp_map_v2 }, |
|
{ /* sentinel */ } |
|
}; |
|
MODULE_DEVICE_TABLE(acpi, bcm_otpc_acpi_ids); |
|
|
|
static int bcm_otpc_probe(struct platform_device *pdev) |
|
{ |
|
struct device *dev = &pdev->dev; |
|
struct resource *res; |
|
struct otpc_priv *priv; |
|
struct nvmem_device *nvmem; |
|
int err; |
|
u32 num_words; |
|
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); |
|
if (!priv) |
|
return -ENOMEM; |
|
|
|
priv->map = device_get_match_data(dev); |
|
if (!priv->map) |
|
return -ENODEV; |
|
|
|
/* Get OTP base address register. */ |
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
|
priv->base = devm_ioremap_resource(dev, res); |
|
if (IS_ERR(priv->base)) { |
|
dev_err(dev, "unable to map I/O memory\n"); |
|
return PTR_ERR(priv->base); |
|
} |
|
|
|
/* Enable CPU access to OTPC. */ |
|
writel(readl(priv->base + OTPC_MODE_REG_OFFSET) | |
|
BIT(OTPC_MODE_REG_OTPC_MODE), |
|
priv->base + OTPC_MODE_REG_OFFSET); |
|
reset_start_bit(priv->base); |
|
|
|
/* Read size of memory in words. */ |
|
err = device_property_read_u32(dev, "brcm,ocotp-size", &num_words); |
|
if (err) { |
|
dev_err(dev, "size parameter not specified\n"); |
|
return -EINVAL; |
|
} else if (num_words == 0) { |
|
dev_err(dev, "size must be > 0\n"); |
|
return -EINVAL; |
|
} |
|
|
|
bcm_otpc_nvmem_config.size = 4 * num_words; |
|
bcm_otpc_nvmem_config.dev = dev; |
|
bcm_otpc_nvmem_config.priv = priv; |
|
|
|
if (priv->map == &otp_map_v2) { |
|
bcm_otpc_nvmem_config.word_size = 8; |
|
bcm_otpc_nvmem_config.stride = 8; |
|
} |
|
|
|
priv->config = &bcm_otpc_nvmem_config; |
|
|
|
nvmem = devm_nvmem_register(dev, &bcm_otpc_nvmem_config); |
|
if (IS_ERR(nvmem)) { |
|
dev_err(dev, "error registering nvmem config\n"); |
|
return PTR_ERR(nvmem); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static struct platform_driver bcm_otpc_driver = { |
|
.probe = bcm_otpc_probe, |
|
.driver = { |
|
.name = "brcm-otpc", |
|
.of_match_table = bcm_otpc_dt_ids, |
|
.acpi_match_table = ACPI_PTR(bcm_otpc_acpi_ids), |
|
}, |
|
}; |
|
module_platform_driver(bcm_otpc_driver); |
|
|
|
MODULE_DESCRIPTION("Broadcom OTPC driver"); |
|
MODULE_LICENSE("GPL v2");
|
|
|