Re: [PATCH v7 2/2] misc: Add iop driver for Sunplus SP7021

From: Greg KH
Date: Fri Feb 04 2022 - 10:32:08 EST


On Mon, Jan 10, 2022 at 02:37:54PM +0800, Tony Huang wrote:
> IOP(8051) embedded inside SP7021 which is used as
> Processor for I/O control, monitor RTC interrupt and
> cooperation with CPU & PMC in power management purpose.
> The IOP core is DQ8051, so also named IOP8051,
> it supports dedicated JTAG debug pins which share with SP7021.
> In standby mode operation, the power spec reach 400uA.
>
> Signed-off-by: Tony Huang <tonyhuang.sunplus@xxxxxxxxx>
> ---
> Changes in v7:
> - Addressed comments from Greg KH.
>
> Documentation/ABI/testing/sysfs-platform-soc@B | 28 ++
> MAINTAINERS | 2 +
> drivers/misc/sunplus_iop.c | 455 +++++++++++++++++++++++++
> 3 files changed, 485 insertions(+)
> create mode 100644 Documentation/ABI/testing/sysfs-platform-soc@B
> create mode 100644 drivers/misc/sunplus_iop.c
>
> diff --git a/Documentation/ABI/testing/sysfs-platform-soc@B b/Documentation/ABI/testing/sysfs-platform-soc@B
> new file mode 100644
> index 0000000..1946a6f
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-platform-soc@B
> @@ -0,0 +1,28 @@
> +What: /sys/devices/platform/soc@B/9c000400.iop/sp_iop_mailbox
> +Date: January 2022
> +KernelVersion: 5.16
> +Contact: Tony Huang <tonyhuang.sunplus@xxxxxxxxx>
> +Description:
> + Show IOP's mailbox0 register data.
> + Format: %x
> +
> +What: /sys/devices/platform/soc@B/9c000400.iop/sp_iop_mode
> +Date: January 2022
> +KernelVersion: 5.16
> +Contact: Tony Huang <tonyhuang.sunplus@xxxxxxxxx>
> +Description:
> + Read-Write.
> +
> + Write this file.
> + Operation mode of IOP is switched to standby mode by writing
> + "1" to sysfs.
> + Operation mode of IOP is switched to normal mode by writing
> + "0" to sysfs.
> + Writing of other values is invalid.
> +
> + Read this file.
> + Show operation mode of IOP. "0" is normal mode. "1" is standby
> + mode.
> + Format: %x
> +
> +
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 6f336c9..cbc8dff 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -18245,7 +18245,9 @@ F: drivers/net/ethernet/dlink/sundance.c
> SUNPLUS IOP DRIVER
> M: Tony Huang <tonyhuang.sunplus@xxxxxxxxx>
> S: Maintained
> +F: Documentation/ABI/testing/sysfs-platform-soc@B
> F: Documentation/devicetree/bindings/misc/sunplu-iop.yaml
> +F: drivers/misc/sunplus_iop.c
>
> SUPERH
> M: Yoshinori Sato <ysato@xxxxxxxxxxxxxxxxxxxx>
> diff --git a/drivers/misc/sunplus_iop.c b/drivers/misc/sunplus_iop.c
> new file mode 100644
> index 0000000..c27875d
> --- /dev/null
> +++ b/drivers/misc/sunplus_iop.c
> @@ -0,0 +1,455 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * The IOP driver for Sunplus SP7021
> + *
> + * Copyright (C) 2021 Sunplus Technology Inc.
> + *
> + * All Rights Reserved.
> + */
> +#include <linux/delay.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/firmware.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/miscdevice.h>
> +#include <linux/of_platform.h>
> +#include <linux/of_address.h>
> +#include <linux/of_gpio.h>
> +
> +enum IOP_Status_e {
> + IOP_SUCCESS, /* successful */
> + IOP_ERR_IOP_BUSY, /* IOP is busy */
> +};
> +
> +/* moon0 register offset */
> +#define IOP_CLKEN0 0x04
> +#define IOP_RESET0 0x54
> +
> +/* IOP register offset */
> +#define IOP_CONTROL 0x00
> +#define IOP_DATA0 0x20
> +#define IOP_DATA1 0x24
> +#define IOP_DATA2 0x28
> +#define IOP_DATA3 0x2c
> +#define IOP_DATA4 0x30
> +#define IOP_DATA5 0x34
> +#define IOP_DATA6 0x38
> +#define IOP_DATA7 0x3c
> +#define IOP_DATA8 0x40
> +#define IOP_DATA9 0x44
> +#define IOP_DATA10 0x48
> +#define IOP_DATA11 0x4c
> +#define IOP_BASE_ADR_L 0x50
> +#define IOP_BASE_ADR_H 0x54
> +
> +/* PMC register offset */
> +#define IOP_PMC_TIMER 0x00
> +#define IOP_PMC_CTRL 0x04
> +#define IOP_XTAL27M_PASSWORD_I 0x08
> +#define IOP_XTAL27M_PASSWORD_II 0x0c
> +#define IOP_XTAL32K_PASSWORD_I 0x10
> +#define IOP_XTAL32K_PASSWORD_II 0x14
> +#define IOP_CLK27M_PASSWORD_I 0x18
> +#define IOP_CLK27M_PASSWORD_II 0x1c
> +#define IOP_PMC_TIMER2 0x20
> +
> +#define NORMAL_CODE_MAX_SIZE 0X1000 /* Max size of normal.bin that can be received */
> +#define STANDBY_CODE_MAX_SIZE 0x4000 /* Max size of standby.bin that can be received */
> +struct sp_iop {
> + struct miscdevice dev;
> + struct mutex write_lock; /* avoid parallel access */
> + void __iomem *iop_regs;
> + void __iomem *pmc_regs;
> + void __iomem *moon0_regs;
> + int irq;
> + int gpio_wakeup;
> + unsigned char iop_normal_code[NORMAL_CODE_MAX_SIZE];
> + unsigned char iop_standby_code[STANDBY_CODE_MAX_SIZE];
> + resource_size_t iop_mem_start;
> + resource_size_t iop_mem_size;
> + unsigned char bin_code_mode;
> +};
> +
> +static void sp_iop_normal_mode(struct sp_iop *iop)
> +{
> + void __iomem *iop_kernel_base;
> + unsigned int reg;
> +
> + iop_kernel_base = ioremap(iop->iop_mem_start, NORMAL_CODE_MAX_SIZE);
> + memset(iop_kernel_base, 0, NORMAL_CODE_MAX_SIZE);
> + memcpy(iop_kernel_base, iop->iop_normal_code, NORMAL_CODE_MAX_SIZE);
> +
> + writel(0x00100010, iop->moon0_regs + IOP_CLKEN0);
> +
> + reg = readl(iop->iop_regs + IOP_CONTROL);
> + reg |= 0x01;
> + writel(reg, iop->iop_regs + IOP_CONTROL);
> +
> + reg = readl(iop->iop_regs + IOP_CONTROL);
> + reg &= ~(0x8000);
> + writel(reg, iop->iop_regs + IOP_CONTROL);
> +
> + reg = readl(iop->iop_regs + IOP_CONTROL);
> + reg |= 0x0200;// disable watchdog event reset IOP
> + writel(reg, iop->iop_regs + IOP_CONTROL);
> +
> + reg = (iop->iop_mem_start & 0xFFFF);
> + writel(reg, iop->iop_regs + IOP_BASE_ADR_L);
> + reg = (iop->iop_mem_start >> 16);
> + writel(reg, iop->iop_regs + IOP_BASE_ADR_H);
> +
> + reg = readl(iop->iop_regs + IOP_CONTROL);
> + reg &= ~(0x01);
> + writel(reg, iop->iop_regs + IOP_CONTROL);
> + iop->bin_code_mode = 0;
> +}
> +
> +static void sp_iop_standby_mode(struct sp_iop *iop)
> +{
> + void __iomem *iop_kernel_base;
> + unsigned long reg;
> +
> + iop_kernel_base = ioremap(iop->iop_mem_start, STANDBY_CODE_MAX_SIZE);
> + memset(iop_kernel_base, 0, STANDBY_CODE_MAX_SIZE);
> + memcpy(iop_kernel_base, iop->iop_standby_code, STANDBY_CODE_MAX_SIZE);
> +
> + writel(0x00100010, iop->moon0_regs + IOP_CLKEN0);
> +
> + reg = readl(iop->iop_regs + IOP_CONTROL);
> + reg |= 0x01;
> + writel(reg, iop->iop_regs + IOP_CONTROL);
> +
> + reg = readl(iop->iop_regs + IOP_CONTROL);
> + reg &= ~(0x8000);
> + writel(reg, iop->iop_regs + IOP_CONTROL);
> +
> + reg = readl(iop->iop_regs + IOP_CONTROL);
> + reg |= 0x0200;// disable watchdog event reset IOP
> + writel(reg, iop->iop_regs + IOP_CONTROL);
> +
> + reg = (iop->iop_mem_start & 0xFFFF);
> + writel(reg, iop->iop_regs + IOP_BASE_ADR_L);
> + reg = (iop->iop_mem_start >> 16);
> + writel(reg, iop->iop_regs + IOP_BASE_ADR_H);
> +
> + reg = readl(iop->iop_regs + IOP_CONTROL);
> + reg &= ~(0x01);
> + writel(reg, iop->iop_regs + IOP_CONTROL);
> + iop->bin_code_mode = 1;
> +}
> +
> +/* 8051 informs linux kerenl. 8051 has been switched to standby.bin code. */
> +#define IOP_READY 0x0004
> +#define RISC_READY 0x0008
> +
> +/* System linux kernel tells 8051 which gpio pin to wake-up through. */
> +#define WAKEUP_PIN 0xFE02
> +
> +/* System linux kernel tells 8051 to execute S1 or S3 mode. */
> +#define S1 0x5331
> +#define S3 0x5333
> +
> +static int sp_iop_s3mode(struct device *dev, struct sp_iop *iop)
> +{
> + unsigned int reg;
> + int ret, value;
> +
> + /* PMC set */
> + writel(0x00010001, iop->pmc_regs + IOP_PMC_TIMER);
> + reg = readl(iop->pmc_regs + IOP_PMC_CTRL);
> + /* disable system reset PMC, enalbe power down IOP Domain, enable gating clock 27Mhz */
> + reg |= 0x23;
> + writel(reg, iop->pmc_regs + IOP_PMC_CTRL);
> +
> + writel(0x55aa00ff, iop->pmc_regs + IOP_XTAL27M_PASSWORD_I);
> + writel(0x00ff55aa, iop->pmc_regs + IOP_XTAL27M_PASSWORD_II);
> + writel(0xaa00ff55, iop->pmc_regs + IOP_XTAL32K_PASSWORD_I);
> + writel(0xff55aa00, iop->pmc_regs + IOP_XTAL32K_PASSWORD_II);
> + writel(0xaaff0055, iop->pmc_regs + IOP_CLK27M_PASSWORD_I);
> + writel(0x5500aaff, iop->pmc_regs + IOP_CLK27M_PASSWORD_II);
> + writel(0x01000100, iop->pmc_regs + IOP_PMC_TIMER2);
> +
> + /* IOP Hardware IP reset */
> + reg = readl(iop->moon0_regs + IOP_RESET0);
> + reg |= 0x10;
> + writel(reg, (iop->moon0_regs + IOP_RESET0));
> + reg &= ~(0x10);
> + writel(reg, (iop->moon0_regs + IOP_RESET0));
> +
> + writel(0x00ff0085, (iop->moon0_regs + 32 * 4 * 1 + 4 * 1));
> +
> + reg = readl(iop->moon0_regs + 32 * 4 * 1 + 4 * 2);
> + reg |= 0x08000800;
> + writel(reg, (iop->moon0_regs + 32 * 4 * 1 + 4 * 2));
> +
> + writel(WAKEUP_PIN, iop->iop_regs + IOP_DATA0);
> + writel(iop->gpio_wakeup, iop->iop_regs + IOP_DATA1);
> +
> + ret = readl_poll_timeout(iop->iop_regs + IOP_DATA2, value,
> + (value & IOP_READY) == IOP_READY, 1000, 10000);
> + if (ret) {
> + dev_err(dev, "timed out\n");
> + return ret;
> + }
> +
> + reg = RISC_READY;
> + writel(reg, iop->iop_regs + IOP_DATA2);
> + reg = 0x0000;
> + writel(reg, iop->iop_regs + IOP_DATA5);
> + reg = 0x0060;
> + writel(reg, iop->iop_regs + IOP_DATA6);
> +
> + ret = readl_poll_timeout(iop->iop_regs + IOP_DATA7, value,
> + value == 0xaaaa, 1000, 10000);
> + if (ret) {
> + dev_err(dev, "timed out\n");
> + return ret;
> + }
> +
> + /* 8051 bin file call Ultra low function. */
> + writel(0xdd, iop->iop_regs + IOP_DATA1);
> + /*
> + * When the execution is here, the system linux kernel
> + * is about to be powered off
> + * The purpose of mdelay(10): Do not let the system linux
> + * kernel continue to run other programs.
> + */
> + mdelay(10);
> + return 0;
> +}
> +
> +static int sp_iop_s1mode(struct device *dev, struct sp_iop *iop)
> +{
> + int ret, value;
> +
> + ret = readl_poll_timeout(iop->iop_regs + IOP_DATA2, value,
> + (value & IOP_READY) == IOP_READY, 1000, 10000);
> + if (ret) {
> + dev_err(dev, "timed out\n");
> + return ret;
> + }
> +
> + writel(RISC_READY, iop->iop_regs + IOP_DATA2);
> + writel(0x0000, iop->iop_regs + IOP_DATA5);
> + writel(0x0060, iop->iop_regs + IOP_DATA6);
> +
> + ret = readl_poll_timeout(iop->iop_regs + IOP_DATA7, value,
> + value == 0xaaaa, 1000, 10000);
> + if (ret) {
> + dev_err(dev, "timed out\n");
> + return ret;
> + }
> +
> + /* 8051 bin file call S1_mode function. */
> + writel(0xee, iop->iop_regs + IOP_DATA1);
> + /*
> + * When the execution is here, the system linux kernel
> + * is about to be powered off
> + * The purpose of mdelay(10): Do not let the system linux
> + * kernel continue to run other programs.
> + */
> + mdelay(10);
> + return 0;
> +}
> +
> +static ssize_t sp_iop_mailbox_show(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> + struct sp_iop *iop = dev_get_drvdata(dev);
> + unsigned int mailbox;
> +
> + mailbox = readl(iop->iop_regs + IOP_DATA0);
> + return sysfs_emit(buf, "%x\n", mailbox);
> +}
> +
> +static ssize_t sp_iop_mode_show(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> + struct sp_iop *iop = dev_get_drvdata(dev);
> +
> + return sysfs_emit(buf, "%x\n", iop->bin_code_mode);
> +}
> +
> +static ssize_t sp_iop_mode_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct sp_iop *iop = dev_get_drvdata(dev);
> +
> + if (sysfs_streq(buf, "0"))
> + sp_iop_normal_mode(iop);
> + else if (sysfs_streq(buf, "1"))
> + sp_iop_standby_mode(iop);
> + else
> + return -EINVAL;
> + return count;
> +}
> +
> +static DEVICE_ATTR_RO(sp_iop_mailbox);
> +static DEVICE_ATTR_RW(sp_iop_mode);

Please do not manually create the sysfs files. You race with userspace
and loose. Use the default_groups pointer of the platform driver and
then the driver core will handle that all for you automatically.

thanks,

greg k-h