mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-12-29 17:25:38 +00:00
2471787c1f
By using NVMEM_DEVID_AUTO we support more than 1 device and
automatically enumerate.
Fixes: 0969001569
("misc: microchip: pci1xxxx: Add support to read and write into PCI1XXXX OTP via NVMEM sysfs")
Cc: stable@vger.kernel.org
Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>
Reviewed-by: Michael Walle <mwalle@kernel.org>
Link: https://lore.kernel.org/r/20241007071120.9522-2-heiko.thiery@gmail.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
442 lines
12 KiB
C
442 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
// Copyright (C) 2022-2023 Microchip Technology Inc.
|
|
// PCI1xxxx OTP/EEPROM driver
|
|
|
|
#include <linux/auxiliary_bus.h>
|
|
#include <linux/device.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/module.h>
|
|
#include <linux/nvmem-provider.h>
|
|
|
|
#include "mchp_pci1xxxx_gp.h"
|
|
|
|
#define AUX_DRIVER_NAME "PCI1xxxxOTPE2P"
|
|
#define EEPROM_NAME "pci1xxxx_eeprom"
|
|
#define OTP_NAME "pci1xxxx_otp"
|
|
|
|
#define PERI_PF3_SYSTEM_REG_ADDR_BASE 0x2000
|
|
#define PERI_PF3_SYSTEM_REG_LENGTH 0x4000
|
|
|
|
#define EEPROM_SIZE_BYTES 8192
|
|
#define OTP_SIZE_BYTES 8192
|
|
|
|
#define CONFIG_REG_ADDR_BASE 0
|
|
#define EEPROM_REG_ADDR_BASE 0x0E00
|
|
#define OTP_REG_ADDR_BASE 0x1000
|
|
|
|
#define MMAP_OTP_OFFSET(x) (OTP_REG_ADDR_BASE + (x))
|
|
#define MMAP_EEPROM_OFFSET(x) (EEPROM_REG_ADDR_BASE + (x))
|
|
#define MMAP_CFG_OFFSET(x) (CONFIG_REG_ADDR_BASE + (x))
|
|
|
|
#define EEPROM_CMD_REG 0x00
|
|
#define EEPROM_DATA_REG 0x04
|
|
|
|
#define EEPROM_CMD_EPC_WRITE (BIT(29) | BIT(28))
|
|
#define EEPROM_CMD_EPC_TIMEOUT_BIT BIT(17)
|
|
#define EEPROM_CMD_EPC_BUSY_BIT BIT(31)
|
|
|
|
#define STATUS_READ_DELAY_US 1
|
|
#define STATUS_READ_TIMEOUT_US 20000
|
|
|
|
#define OTP_ADDR_HIGH_OFFSET 0x04
|
|
#define OTP_ADDR_LOW_OFFSET 0x08
|
|
#define OTP_PRGM_DATA_OFFSET 0x10
|
|
#define OTP_PRGM_MODE_OFFSET 0x14
|
|
#define OTP_RD_DATA_OFFSET 0x18
|
|
#define OTP_FUNC_CMD_OFFSET 0x20
|
|
#define OTP_CMD_GO_OFFSET 0x28
|
|
#define OTP_PASS_FAIL_OFFSET 0x2C
|
|
#define OTP_STATUS_OFFSET 0x30
|
|
|
|
#define OTP_FUNC_RD_BIT BIT(0)
|
|
#define OTP_FUNC_PGM_BIT BIT(1)
|
|
#define OTP_CMD_GO_BIT BIT(0)
|
|
#define OTP_STATUS_BUSY_BIT BIT(0)
|
|
#define OTP_PGM_MODE_BYTE_BIT BIT(0)
|
|
#define OTP_FAIL_BIT BIT(0)
|
|
|
|
#define OTP_PWR_DN_BIT BIT(0)
|
|
#define OTP_PWR_DN_OFFSET 0x00
|
|
|
|
#define CFG_SYS_LOCK_OFFSET 0xA0
|
|
#define CFG_SYS_LOCK_PF3 BIT(5)
|
|
|
|
#define BYTE_LOW (GENMASK(7, 0))
|
|
#define BYTE_HIGH (GENMASK(12, 8))
|
|
|
|
struct pci1xxxx_otp_eeprom_device {
|
|
struct auxiliary_device *pdev;
|
|
void __iomem *reg_base;
|
|
struct nvmem_config nvmem_config_eeprom;
|
|
struct nvmem_device *nvmem_eeprom;
|
|
struct nvmem_config nvmem_config_otp;
|
|
struct nvmem_device *nvmem_otp;
|
|
};
|
|
|
|
static int set_sys_lock(struct pci1xxxx_otp_eeprom_device *priv)
|
|
{
|
|
void __iomem *sys_lock = priv->reg_base +
|
|
MMAP_CFG_OFFSET(CFG_SYS_LOCK_OFFSET);
|
|
u8 data;
|
|
|
|
writel(CFG_SYS_LOCK_PF3, sys_lock);
|
|
data = readl(sys_lock);
|
|
if (data != CFG_SYS_LOCK_PF3)
|
|
return -EPERM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void release_sys_lock(struct pci1xxxx_otp_eeprom_device *priv)
|
|
{
|
|
void __iomem *sys_lock = priv->reg_base +
|
|
MMAP_CFG_OFFSET(CFG_SYS_LOCK_OFFSET);
|
|
writel(0, sys_lock);
|
|
}
|
|
|
|
static bool is_eeprom_responsive(struct pci1xxxx_otp_eeprom_device *priv)
|
|
{
|
|
void __iomem *rb = priv->reg_base;
|
|
u32 regval;
|
|
int ret;
|
|
|
|
writel(EEPROM_CMD_EPC_TIMEOUT_BIT,
|
|
rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG));
|
|
writel(EEPROM_CMD_EPC_BUSY_BIT,
|
|
rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG));
|
|
|
|
/* Wait for the EPC_BUSY bit to get cleared or timeout bit to get set*/
|
|
ret = read_poll_timeout(readl, regval, !(regval & EEPROM_CMD_EPC_BUSY_BIT),
|
|
STATUS_READ_DELAY_US, STATUS_READ_TIMEOUT_US,
|
|
true, rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG));
|
|
|
|
/* Return failure if either of software or hardware timeouts happen */
|
|
if (ret < 0 || (!ret && (regval & EEPROM_CMD_EPC_TIMEOUT_BIT)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static int pci1xxxx_eeprom_read(void *priv_t, unsigned int off,
|
|
void *buf_t, size_t count)
|
|
{
|
|
struct pci1xxxx_otp_eeprom_device *priv = priv_t;
|
|
void __iomem *rb = priv->reg_base;
|
|
char *buf = buf_t;
|
|
u32 regval;
|
|
u32 byte;
|
|
int ret;
|
|
|
|
if (off >= priv->nvmem_config_eeprom.size)
|
|
return -EFAULT;
|
|
|
|
if ((off + count) > priv->nvmem_config_eeprom.size)
|
|
count = priv->nvmem_config_eeprom.size - off;
|
|
|
|
ret = set_sys_lock(priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (byte = 0; byte < count; byte++) {
|
|
writel(EEPROM_CMD_EPC_BUSY_BIT | (off + byte), rb +
|
|
MMAP_EEPROM_OFFSET(EEPROM_CMD_REG));
|
|
|
|
ret = read_poll_timeout(readl, regval,
|
|
!(regval & EEPROM_CMD_EPC_BUSY_BIT),
|
|
STATUS_READ_DELAY_US,
|
|
STATUS_READ_TIMEOUT_US, true,
|
|
rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG));
|
|
if (ret < 0 || (!ret && (regval & EEPROM_CMD_EPC_TIMEOUT_BIT))) {
|
|
ret = -EIO;
|
|
goto error;
|
|
}
|
|
|
|
buf[byte] = readl(rb + MMAP_EEPROM_OFFSET(EEPROM_DATA_REG));
|
|
}
|
|
error:
|
|
release_sys_lock(priv);
|
|
return ret;
|
|
}
|
|
|
|
static int pci1xxxx_eeprom_write(void *priv_t, unsigned int off,
|
|
void *value_t, size_t count)
|
|
{
|
|
struct pci1xxxx_otp_eeprom_device *priv = priv_t;
|
|
void __iomem *rb = priv->reg_base;
|
|
char *value = value_t;
|
|
u32 regval;
|
|
u32 byte;
|
|
int ret;
|
|
|
|
if (off >= priv->nvmem_config_eeprom.size)
|
|
return -EFAULT;
|
|
|
|
if ((off + count) > priv->nvmem_config_eeprom.size)
|
|
count = priv->nvmem_config_eeprom.size - off;
|
|
|
|
ret = set_sys_lock(priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (byte = 0; byte < count; byte++) {
|
|
writel(*(value + byte), rb + MMAP_EEPROM_OFFSET(EEPROM_DATA_REG));
|
|
regval = EEPROM_CMD_EPC_TIMEOUT_BIT | EEPROM_CMD_EPC_WRITE |
|
|
(off + byte);
|
|
writel(regval, rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG));
|
|
writel(EEPROM_CMD_EPC_BUSY_BIT | regval,
|
|
rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG));
|
|
|
|
ret = read_poll_timeout(readl, regval,
|
|
!(regval & EEPROM_CMD_EPC_BUSY_BIT),
|
|
STATUS_READ_DELAY_US,
|
|
STATUS_READ_TIMEOUT_US, true,
|
|
rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG));
|
|
if (ret < 0 || (!ret && (regval & EEPROM_CMD_EPC_TIMEOUT_BIT))) {
|
|
ret = -EIO;
|
|
goto error;
|
|
}
|
|
}
|
|
error:
|
|
release_sys_lock(priv);
|
|
return ret;
|
|
}
|
|
|
|
static void otp_device_set_address(struct pci1xxxx_otp_eeprom_device *priv,
|
|
u16 address)
|
|
{
|
|
u16 lo, hi;
|
|
|
|
lo = address & BYTE_LOW;
|
|
hi = (address & BYTE_HIGH) >> 8;
|
|
writew(lo, priv->reg_base + MMAP_OTP_OFFSET(OTP_ADDR_LOW_OFFSET));
|
|
writew(hi, priv->reg_base + MMAP_OTP_OFFSET(OTP_ADDR_HIGH_OFFSET));
|
|
}
|
|
|
|
static int pci1xxxx_otp_read(void *priv_t, unsigned int off,
|
|
void *buf_t, size_t count)
|
|
{
|
|
struct pci1xxxx_otp_eeprom_device *priv = priv_t;
|
|
void __iomem *rb = priv->reg_base;
|
|
char *buf = buf_t;
|
|
u32 regval;
|
|
u32 byte;
|
|
int ret;
|
|
u8 data;
|
|
|
|
if (off >= priv->nvmem_config_otp.size)
|
|
return -EFAULT;
|
|
|
|
if ((off + count) > priv->nvmem_config_otp.size)
|
|
count = priv->nvmem_config_otp.size - off;
|
|
|
|
ret = set_sys_lock(priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (byte = 0; byte < count; byte++) {
|
|
otp_device_set_address(priv, (u16)(off + byte));
|
|
data = readl(rb + MMAP_OTP_OFFSET(OTP_FUNC_CMD_OFFSET));
|
|
writel(data | OTP_FUNC_RD_BIT,
|
|
rb + MMAP_OTP_OFFSET(OTP_FUNC_CMD_OFFSET));
|
|
data = readl(rb + MMAP_OTP_OFFSET(OTP_CMD_GO_OFFSET));
|
|
writel(data | OTP_CMD_GO_BIT,
|
|
rb + MMAP_OTP_OFFSET(OTP_CMD_GO_OFFSET));
|
|
|
|
ret = read_poll_timeout(readl, regval,
|
|
!(regval & OTP_STATUS_BUSY_BIT),
|
|
STATUS_READ_DELAY_US,
|
|
STATUS_READ_TIMEOUT_US, true,
|
|
rb + MMAP_OTP_OFFSET(OTP_STATUS_OFFSET));
|
|
|
|
data = readl(rb + MMAP_OTP_OFFSET(OTP_PASS_FAIL_OFFSET));
|
|
if (ret < 0 || data & OTP_FAIL_BIT) {
|
|
ret = -EIO;
|
|
goto error;
|
|
}
|
|
|
|
buf[byte] = readl(rb + MMAP_OTP_OFFSET(OTP_RD_DATA_OFFSET));
|
|
}
|
|
error:
|
|
release_sys_lock(priv);
|
|
return ret;
|
|
}
|
|
|
|
static int pci1xxxx_otp_write(void *priv_t, unsigned int off,
|
|
void *value_t, size_t count)
|
|
{
|
|
struct pci1xxxx_otp_eeprom_device *priv = priv_t;
|
|
void __iomem *rb = priv->reg_base;
|
|
char *value = value_t;
|
|
u32 regval;
|
|
u32 byte;
|
|
int ret;
|
|
u8 data;
|
|
|
|
if (off >= priv->nvmem_config_otp.size)
|
|
return -EFAULT;
|
|
|
|
if ((off + count) > priv->nvmem_config_otp.size)
|
|
count = priv->nvmem_config_otp.size - off;
|
|
|
|
ret = set_sys_lock(priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (byte = 0; byte < count; byte++) {
|
|
otp_device_set_address(priv, (u16)(off + byte));
|
|
|
|
/*
|
|
* Set OTP_PGM_MODE_BYTE command bit in OTP_PRGM_MODE register
|
|
* to enable Byte programming
|
|
*/
|
|
data = readl(rb + MMAP_OTP_OFFSET(OTP_PRGM_MODE_OFFSET));
|
|
writel(data | OTP_PGM_MODE_BYTE_BIT,
|
|
rb + MMAP_OTP_OFFSET(OTP_PRGM_MODE_OFFSET));
|
|
writel(*(value + byte), rb + MMAP_OTP_OFFSET(OTP_PRGM_DATA_OFFSET));
|
|
data = readl(rb + MMAP_OTP_OFFSET(OTP_FUNC_CMD_OFFSET));
|
|
writel(data | OTP_FUNC_PGM_BIT,
|
|
rb + MMAP_OTP_OFFSET(OTP_FUNC_CMD_OFFSET));
|
|
data = readl(rb + MMAP_OTP_OFFSET(OTP_CMD_GO_OFFSET));
|
|
writel(data | OTP_CMD_GO_BIT,
|
|
rb + MMAP_OTP_OFFSET(OTP_CMD_GO_OFFSET));
|
|
|
|
ret = read_poll_timeout(readl, regval,
|
|
!(regval & OTP_STATUS_BUSY_BIT),
|
|
STATUS_READ_DELAY_US,
|
|
STATUS_READ_TIMEOUT_US, true,
|
|
rb + MMAP_OTP_OFFSET(OTP_STATUS_OFFSET));
|
|
|
|
data = readl(rb + MMAP_OTP_OFFSET(OTP_PASS_FAIL_OFFSET));
|
|
if (ret < 0 || data & OTP_FAIL_BIT) {
|
|
ret = -EIO;
|
|
goto error;
|
|
}
|
|
}
|
|
error:
|
|
release_sys_lock(priv);
|
|
return ret;
|
|
}
|
|
|
|
static int pci1xxxx_otp_eeprom_probe(struct auxiliary_device *aux_dev,
|
|
const struct auxiliary_device_id *id)
|
|
{
|
|
struct auxiliary_device_wrapper *aux_dev_wrapper;
|
|
struct pci1xxxx_otp_eeprom_device *priv;
|
|
struct gp_aux_data_type *pdata;
|
|
int ret;
|
|
u8 data;
|
|
|
|
aux_dev_wrapper = container_of(aux_dev, struct auxiliary_device_wrapper,
|
|
aux_dev);
|
|
pdata = &aux_dev_wrapper->gp_aux_data;
|
|
if (!pdata)
|
|
return -EINVAL;
|
|
|
|
priv = devm_kzalloc(&aux_dev->dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
priv->pdev = aux_dev;
|
|
|
|
if (!devm_request_mem_region(&aux_dev->dev, pdata->region_start +
|
|
PERI_PF3_SYSTEM_REG_ADDR_BASE,
|
|
PERI_PF3_SYSTEM_REG_LENGTH,
|
|
aux_dev->name))
|
|
return -ENOMEM;
|
|
|
|
priv->reg_base = devm_ioremap(&aux_dev->dev, pdata->region_start +
|
|
PERI_PF3_SYSTEM_REG_ADDR_BASE,
|
|
PERI_PF3_SYSTEM_REG_LENGTH);
|
|
if (!priv->reg_base)
|
|
return -ENOMEM;
|
|
|
|
ret = set_sys_lock(priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Set OTP_PWR_DN to 0 to make OTP Operational */
|
|
data = readl(priv->reg_base + MMAP_OTP_OFFSET(OTP_PWR_DN_OFFSET));
|
|
writel(data & ~OTP_PWR_DN_BIT,
|
|
priv->reg_base + MMAP_OTP_OFFSET(OTP_PWR_DN_OFFSET));
|
|
|
|
dev_set_drvdata(&aux_dev->dev, priv);
|
|
|
|
if (is_eeprom_responsive(priv)) {
|
|
priv->nvmem_config_eeprom.type = NVMEM_TYPE_EEPROM;
|
|
priv->nvmem_config_eeprom.name = EEPROM_NAME;
|
|
priv->nvmem_config_eeprom.id = NVMEM_DEVID_AUTO;
|
|
priv->nvmem_config_eeprom.dev = &aux_dev->dev;
|
|
priv->nvmem_config_eeprom.owner = THIS_MODULE;
|
|
priv->nvmem_config_eeprom.reg_read = pci1xxxx_eeprom_read;
|
|
priv->nvmem_config_eeprom.reg_write = pci1xxxx_eeprom_write;
|
|
priv->nvmem_config_eeprom.priv = priv;
|
|
priv->nvmem_config_eeprom.stride = 1;
|
|
priv->nvmem_config_eeprom.word_size = 1;
|
|
priv->nvmem_config_eeprom.size = EEPROM_SIZE_BYTES;
|
|
|
|
priv->nvmem_eeprom = devm_nvmem_register(&aux_dev->dev,
|
|
&priv->nvmem_config_eeprom);
|
|
if (IS_ERR(priv->nvmem_eeprom))
|
|
return PTR_ERR(priv->nvmem_eeprom);
|
|
}
|
|
|
|
release_sys_lock(priv);
|
|
|
|
priv->nvmem_config_otp.type = NVMEM_TYPE_OTP;
|
|
priv->nvmem_config_otp.name = OTP_NAME;
|
|
priv->nvmem_config_otp.id = NVMEM_DEVID_AUTO;
|
|
priv->nvmem_config_otp.dev = &aux_dev->dev;
|
|
priv->nvmem_config_otp.owner = THIS_MODULE;
|
|
priv->nvmem_config_otp.reg_read = pci1xxxx_otp_read;
|
|
priv->nvmem_config_otp.reg_write = pci1xxxx_otp_write;
|
|
priv->nvmem_config_otp.priv = priv;
|
|
priv->nvmem_config_otp.stride = 1;
|
|
priv->nvmem_config_otp.word_size = 1;
|
|
priv->nvmem_config_otp.size = OTP_SIZE_BYTES;
|
|
|
|
priv->nvmem_otp = devm_nvmem_register(&aux_dev->dev,
|
|
&priv->nvmem_config_otp);
|
|
if (IS_ERR(priv->nvmem_otp))
|
|
return PTR_ERR(priv->nvmem_otp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void pci1xxxx_otp_eeprom_remove(struct auxiliary_device *aux_dev)
|
|
{
|
|
struct pci1xxxx_otp_eeprom_device *priv;
|
|
void __iomem *sys_lock;
|
|
|
|
priv = dev_get_drvdata(&aux_dev->dev);
|
|
sys_lock = priv->reg_base + MMAP_CFG_OFFSET(CFG_SYS_LOCK_OFFSET);
|
|
writel(CFG_SYS_LOCK_PF3, sys_lock);
|
|
|
|
/* Shut down OTP */
|
|
writel(OTP_PWR_DN_BIT,
|
|
priv->reg_base + MMAP_OTP_OFFSET(OTP_PWR_DN_OFFSET));
|
|
|
|
writel(0, sys_lock);
|
|
}
|
|
|
|
static const struct auxiliary_device_id pci1xxxx_otp_eeprom_auxiliary_id_table[] = {
|
|
{.name = "mchp_pci1xxxx_gp.gp_otp_e2p"},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(auxiliary, pci1xxxx_otp_eeprom_auxiliary_id_table);
|
|
|
|
static struct auxiliary_driver pci1xxxx_otp_eeprom_driver = {
|
|
.driver = {
|
|
.name = AUX_DRIVER_NAME,
|
|
},
|
|
.probe = pci1xxxx_otp_eeprom_probe,
|
|
.remove = pci1xxxx_otp_eeprom_remove,
|
|
.id_table = pci1xxxx_otp_eeprom_auxiliary_id_table
|
|
};
|
|
module_auxiliary_driver(pci1xxxx_otp_eeprom_driver);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Kumaravel Thiagarajan <kumaravel.thiagarajan@microchip.com>");
|
|
MODULE_AUTHOR("Tharun Kumar P <tharunkumar.pasumarthi@microchip.com>");
|
|
MODULE_AUTHOR("Vaibhaav Ram T.L <vaibhaavram.tl@microchip.com>");
|
|
MODULE_DESCRIPTION("Microchip Technology Inc. PCI1xxxx OTP EEPROM Programmer");
|