root/drivers/misc/mchp_pci1xxxx/mchp_pci1xxxx_otpe2p.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");