From 0b4bb76a7e80c46b9eed14145fd20ae715a335f1 Mon Sep 17 00:00:00 2001 From: yuanxia Date: Mon, 13 May 2024 18:01:07 +0800 Subject: [PATCH 001/101] dt-bindings: spi: phyhtium: add bindings for Phytium SPI Add binding documentation for Phytium SPI controller. Signed-off-by: yuanxia Signed-off-by: Chen Baozi Signed-off-by: Wang Yinfeng Link: https://github.com/deepin-community/kernel/pull/136 (cherry picked from commit 23b8ee87cc35d2e82419fcc67cb0ad98ad5ca796) Signed-off-by: Wentao Guan --- .../devicetree/bindings/spi/phytium,spi.yaml | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 Documentation/devicetree/bindings/spi/phytium,spi.yaml diff --git a/Documentation/devicetree/bindings/spi/phytium,spi.yaml b/Documentation/devicetree/bindings/spi/phytium,spi.yaml new file mode 100644 index 0000000000000..983c592bae444 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/phytium,spi.yaml @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/phytium,spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium SPI controller + +maintainers: + - Chen Baozi + +allOf: + - $ref: spi-controller.yaml# + +properties: + compatible: + const: phytium,spi + + reg: + minItems: 1 + description: address and length of the spi master registers + + interrupts: + maxItems: 1 + description: should contain one interrupt + + clocks: + maxItems: 1 + description: spi clock phandle + +required: + - compatible + - "#address-cells" + - "#size-cells" + - reg + - interrupts + - clocks + - num-cs + +examples: + - | + + spi0: spi@2800c000 { + compatible = "phytium,spi"; + interrupts = ; + reg = <0x0 0x2800c000 0x0 0x1000>; + clocks = <&sysclk_48mhz>; + num-cs = <4>; + }; From caf2f8ff84f6ab930cb00996e29895c01f3e2726 Mon Sep 17 00:00:00 2001 From: yuanxia Date: Mon, 13 May 2024 18:07:30 +0800 Subject: [PATCH 002/101] arm64: spi: add Phytium SPI controller support Add support for the Phytium SPI controller driver. Signed-off-by: yuanxia Signed-off-by: Zhang Yiqun Signed-off-by: Peng Min Signed-off-by: Chen Baozi Signed-off-by: Wang Yinfeng Link: https://github.com/deepin-community/kernel/pull/136 [ Wentao Guan: rename spi_controller_get_devdata from spi_master_get_devdata,struct spi_controller from struct spi_master ] [ Wentao Guan: XXXX ] (cherry picked from commit 27b76dbe2c90b0f9dd1fd5de074cc0d93b0146a5) Signed-off-by: Wentao Guan --- drivers/spi/Kconfig | 28 +- drivers/spi/Makefile | 3 + drivers/spi/spi-phytium-pci.c | 127 ++++++++ drivers/spi/spi-phytium-plat.c | 163 ++++++++++ drivers/spi/spi-phytium.c | 524 +++++++++++++++++++++++++++++++++ drivers/spi/spi-phytium.h | 71 +++++ 6 files changed, 910 insertions(+), 6 deletions(-) create mode 100644 drivers/spi/spi-phytium-pci.c create mode 100644 drivers/spi/spi-phytium-plat.c create mode 100644 drivers/spi/spi-phytium.c create mode 100644 drivers/spi/spi-phytium.h diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 8782514bb89b0..41602047f176e 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -843,14 +843,30 @@ config SPI_ORION This enables using the SPI master controller on the Orion and MVEBU chips. -config SPI_PCI1XXXX - tristate "PCI1XXXX SPI Bus support" +config SPI_PHYTIUM + tristate + depends on ARCH_PHYTIUM || COMPILE_TEST + +config SPI_PHYTIUM_PLAT + tristate "Phytium SPI controller platform support" + select SPI_PHYTIUM + help + This selects a platform driver for Phytium SPI controller. + + If you say yes to this option, support will be included for + Phytium SoC families of SPI controller. + +config SPI_PHYTIUM_PCI + tristate "Phytium SPI controller PCI support" depends on PCI + select SPI_PHYTIUM help - Say "yes" to Enable the SPI Bus support for the PCI1xxxx card - This is a PCI to SPI Bus driver - This driver can be built as module. If so, the module will be - called as spi-pci1xxxx. + This selects a PCI driver for Phytium SPI controller. + + If you say yes to this option, support will be included for + Phytium PCIe chipsets of SPI controller. + + If unsure, say N. config SPI_PIC32 tristate "Microchip PIC32 series SPI" diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 9fa12498ce8c0..3cf39660c266b 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -112,6 +112,9 @@ obj-$(CONFIG_SPI_OMAP24XX) += spi-omap2-mcspi.o obj-$(CONFIG_SPI_TI_QSPI) += spi-ti-qspi.o obj-$(CONFIG_SPI_ORION) += spi-orion.o obj-$(CONFIG_SPI_PCI1XXXX) += spi-pci1xxxx.o +obj-$(CONFIG_SPI_PHYTIUM) += spi-phytium.o +obj-$(CONFIG_SPI_PHYTIUM_PLAT) += spi-phytium-plat.o +obj-$(CONFIG_SPI_PHYTIUM_PCI) += spi-phytium-pci.o obj-$(CONFIG_SPI_PIC32) += spi-pic32.o obj-$(CONFIG_SPI_PIC32_SQI) += spi-pic32-sqi.o obj-$(CONFIG_SPI_PL022) += spi-pl022.o diff --git a/drivers/spi/spi-phytium-pci.c b/drivers/spi/spi-phytium-pci.c new file mode 100644 index 0000000000000..65797d6c0d2a4 --- /dev/null +++ b/drivers/spi/spi-phytium-pci.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SPI core controller PCI driver. + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd. + * + * Derived from drivers/spi/spi-dw-pci.c + * Copyright (c) 2009, 2014 Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "spi-phytium.h" + +#define DRIVER_NAME "phytium_spi_pci" + +static int phytium_spi_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct phytium_spi *fts; + int pci_bar = 0; + int ret; + + fts = devm_kzalloc(&pdev->dev, sizeof(struct phytium_spi), + GFP_KERNEL); + if (!fts) + return -ENOMEM; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + ret = pcim_iomap_regions(pdev, 1 << pci_bar, pci_name(pdev)); + if (ret) { + dev_err(&pdev->dev, "pci iomap failed?\n"); + return ret; + } + + fts->regs = pcim_iomap_table(pdev)[pci_bar]; + if (IS_ERR(fts->regs)) { + dev_err(&pdev->dev, "SPI region map failed\n"); + return PTR_ERR(fts->regs); + } + + fts->irq = pdev->irq; + if (fts->irq < 0) { + dev_err(&pdev->dev, "no irq resource?\n"); + return fts->irq; /* -ENXIO */ + } + + fts->bus_num = -1; + + fts->max_freq = 48000000; + + fts->num_cs = 4; + + fts->global_cs = 1; + + ret = phytium_spi_add_host(&pdev->dev, fts); + if (ret) + return ret; + + pci_set_drvdata(pdev, fts); + return 0; +} + +static void phytium_spi_pci_remove(struct pci_dev *pdev) +{ + struct phytium_spi *fts = pci_get_drvdata(pdev); + + phytium_spi_remove_host(fts); +} + + +#ifdef CONFIG_PM_SLEEP +static int spi_suspend(struct device *dev) +{ + struct spi_controller *master = dev_get_drvdata(dev); + struct phytium_spi *fts = spi_controller_get_devdata(master); + + return phytium_spi_suspend_host(fts); +} + +static int spi_resume(struct device *dev) +{ + struct spi_controller *master = dev_get_drvdata(dev); + struct phytium_spi *fts = spi_controller_get_devdata(master); + + return phytium_spi_resume_host(fts); +} +#endif + +static SIMPLE_DEV_PM_OPS(phytium_spi_pm_ops, spi_suspend, spi_resume); + +static const struct pci_device_id phytium_device_pci_tbl[] = { + { PCI_VDEVICE(PHYTIUM, 0xdc2c) }, + {}, +}; + +static struct pci_driver phytium_spi_pci_driver = { + .name = DRIVER_NAME, + .id_table = phytium_device_pci_tbl, + .probe = phytium_spi_pci_probe, + .remove = phytium_spi_pci_remove, + .driver = { + .pm = &phytium_spi_pm_ops, + } +}; + +module_pci_driver(phytium_spi_pci_driver); + +MODULE_AUTHOR("Yiqun Zhang "); +MODULE_DESCRIPTION("PCI Driver for Phytium SPI controller core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-phytium-plat.c b/drivers/spi/spi-phytium-plat.c new file mode 100644 index 0000000000000..d75b86dbd3df2 --- /dev/null +++ b/drivers/spi/spi-phytium-plat.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SPI core controller platform driver. + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd. + * + * Derived from drivers/spi/spi-dw-mmio.c + * Copyright (c) 2010, Octasic semiconductor. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "spi-phytium.h" + +#define DRIVER_NAME "phytium_spi" + +struct phytium_spi_clk { + struct phytium_spi fts; + struct clk *clk; +}; + +static int phytium_spi_probe(struct platform_device *pdev) +{ + struct phytium_spi_clk *ftsc; + struct phytium_spi *fts; + struct resource *mem; + int ret; + int num_cs; + int global_cs; + + ftsc = devm_kzalloc(&pdev->dev, sizeof(struct phytium_spi_clk), + GFP_KERNEL); + if (!ftsc) + return -ENOMEM; + + fts = &ftsc->fts; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "no mem resource?\n"); + return -EINVAL; + } + + fts->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(fts->regs)) { + dev_err(&pdev->dev, "SPI region map failed\n"); + return PTR_ERR(fts->regs); + } + + fts->irq = platform_get_irq(pdev, 0); + if (fts->irq < 0) { + dev_err(&pdev->dev, "no irq resource?\n"); + return fts->irq; /* -ENXIO */ + } + + if (pdev->dev.of_node) { + ftsc->clk = devm_clk_get(&pdev->dev, NULL); + + if (IS_ERR(ftsc->clk)) + return PTR_ERR(ftsc->clk); + ret = clk_prepare_enable(ftsc->clk); + if (ret) + return ret; + + fts->max_freq = clk_get_rate(ftsc->clk); + } else if (has_acpi_companion(&pdev->dev)) { + fts->max_freq = 48000000; + } + + fts->bus_num = pdev->id; + device_property_read_u32(&pdev->dev, "reg-io-width", &fts->reg_io_width); + + num_cs = 4; + + device_property_read_u32(&pdev->dev, "num-cs", &num_cs); + + fts->num_cs = num_cs; + + device_property_read_u32(&pdev->dev, "global-cs", &global_cs); + fts->global_cs = global_cs; + + ret = phytium_spi_add_host(&pdev->dev, fts); + if (ret) + goto out; + + platform_set_drvdata(pdev, ftsc); + return 0; + +out: + clk_disable_unprepare(ftsc->clk); + return ret; +} + +static void phytium_spi_remove(struct platform_device *pdev) +{ + struct phytium_spi_clk *ftsc = platform_get_drvdata(pdev); + + phytium_spi_remove_host(&ftsc->fts); + clk_disable_unprepare(ftsc->clk); +} + +#ifdef CONFIG_PM_SLEEP +static int spi_suspend(struct device *dev) +{ + struct spi_controller *master = dev_get_drvdata(dev); + struct phytium_spi *fts = spi_controller_get_devdata(master); + + return phytium_spi_suspend_host(fts); +} + +static int spi_resume(struct device *dev) +{ + struct spi_controller *master = dev_get_drvdata(dev); + struct phytium_spi *fts = spi_controller_get_devdata(master); + + return phytium_spi_resume_host(fts); +} +#endif + +static SIMPLE_DEV_PM_OPS(phytium_spi_pm_ops, spi_suspend, spi_resume); + +static const struct of_device_id phytium_spi_of_match[] = { + { .compatible = "phytium,spi", .data = (void *)0 }, + { /* end of table */} +}; +MODULE_DEVICE_TABLE(of, phytium_spi_of_match); + +static const struct acpi_device_id phytium_spi_acpi_match[] = { + {"PHYT000E", 0}, + {} +}; +MODULE_DEVICE_TABLE(acpi, phytium_spi_acpi_match); + +static struct platform_driver phytium_spi_driver = { + .probe = phytium_spi_probe, + .remove = phytium_spi_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(phytium_spi_of_match), + .acpi_match_table = ACPI_PTR(phytium_spi_acpi_match), + .pm = &phytium_spi_pm_ops, + }, +}; +module_platform_driver(phytium_spi_driver); + +MODULE_AUTHOR("Yiqun Zhang "); +MODULE_DESCRIPTION("Platform Driver for Phytium SPI controller core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-phytium.c b/drivers/spi/spi-phytium.c new file mode 100644 index 0000000000000..8f230280381ac --- /dev/null +++ b/drivers/spi/spi-phytium.c @@ -0,0 +1,524 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SPI core controller driver. + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd.. + * + * Derived from drivers/spi/spi-dw.c + * Copyright (c) 2009, Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "spi-phytium.h" + +static inline u32 phytium_readl(struct phytium_spi *fts, u32 offset) +{ + return __raw_readl(fts->regs + offset); +} + +static inline u16 phytium_readw(struct phytium_spi *fts, u32 offset) +{ + return __raw_readw(fts->regs + offset); +} + +static inline void phytium_writel(struct phytium_spi *fts, u32 offset, u32 val) +{ + __raw_writel(val, fts->regs + offset); +} + +static inline void phytium_writew(struct phytium_spi *fts, u32 offset, u16 val) +{ + __raw_writew(val, fts->regs + offset); +} + +static inline u32 phytium_read_io_reg(struct phytium_spi *fts, u32 offset) +{ + switch (fts->reg_io_width) { + case 2: + return phytium_readw(fts, offset); + case 4: + default: + return phytium_readl(fts, offset); + } +} + +static inline void phytium_write_io_reg(struct phytium_spi *fts, u32 offset, u32 val) +{ + switch (fts->reg_io_width) { + case 2: + phytium_writew(fts, offset, val); + break; + case 4: + default: + phytium_writel(fts, offset, val); + break; + } +} + +static inline void spi_enable_chip(struct phytium_spi *fts, int enable) +{ + phytium_writel(fts, SSIENR, (enable ? 1 : 0)); +} + +static inline void spi_set_clk(struct phytium_spi *fts, u16 div) +{ + phytium_writel(fts, BAUDR, div); +} + +static inline void spi_mask_intr(struct phytium_spi *fts, u32 mask) +{ + u32 new_mask; + + new_mask = phytium_readl(fts, IMR) & ~mask; + phytium_writel(fts, IMR, new_mask); +} + +static inline void spi_umask_intr(struct phytium_spi *fts, u32 mask) +{ + u32 new_mask; + + new_mask = phytium_readl(fts, IMR) | mask; + phytium_writel(fts, IMR, new_mask); +} + +static inline void spi_global_cs(struct phytium_spi *fts) +{ + u32 global_cs_en, mask, setmask; + + mask = GENMASK(fts->num_cs-1, 0) << fts->num_cs; + setmask = ~GENMASK(fts->num_cs-1, 0); + global_cs_en = (phytium_readl(fts, GCSR) | mask) & setmask; + + phytium_writel(fts, GCSR, global_cs_en); +} + +static inline void spi_reset_chip(struct phytium_spi *fts) +{ + spi_enable_chip(fts, 0); + if (fts->global_cs) + spi_global_cs(fts); + spi_mask_intr(fts, 0xff); + spi_enable_chip(fts, 1); +} + +static inline void spi_shutdown_chip(struct phytium_spi *fts) +{ + spi_enable_chip(fts, 0); + spi_set_clk(fts, 0); +} + +struct phytium_spi_chip { + u8 poll_mode; + u8 type; + void (*cs_control)(u32 command); +}; + +struct chip_data { + u8 cs; + u8 tmode; + u8 type; + + u8 poll_mode; + + u16 clk_div; + u32 speed_hz; + void (*cs_control)(u32 command); +}; + +static void phytium_spi_set_cs(struct spi_device *spi, bool enable) +{ + struct phytium_spi *fts = spi_controller_get_devdata(spi->controller); + struct chip_data *chip = spi_get_ctldata(spi); + u32 origin; + + if (chip && chip->cs_control) + chip->cs_control(!enable); + + if (!enable) { + phytium_writel(fts, SER, BIT(spi_get_chipselect(spi, 0))); + if (fts->global_cs) { + origin = phytium_readl(fts, GCSR); + phytium_writel(fts, GCSR, origin | (1 << spi_get_chipselect(spi, 0))); + } + } else { + if (fts->global_cs) { + origin = phytium_readl(fts, GCSR); + phytium_writel(fts, GCSR, origin & ~(1 << spi_get_chipselect(spi, 0))); + } + } +} + +static inline u32 tx_max(struct phytium_spi *fts) +{ + u32 tx_left, tx_room, rxtx_gap; + + tx_left = (fts->tx_end - fts->tx) / fts->n_bytes; + tx_room = fts->fifo_len - phytium_readl(fts, TXFLR); + + rxtx_gap = ((fts->rx_end - fts->rx) - (fts->tx_end - fts->tx)) + / fts->n_bytes; + + return min3(tx_left, tx_room, (u32) (fts->fifo_len - rxtx_gap)); +} + +static inline u32 rx_max(struct phytium_spi *fts) +{ + u32 rx_left = (fts->rx_end - fts->rx) / fts->n_bytes; + + return min_t(u32, rx_left, phytium_readl(fts, RXFLR)); +} + +static void phytium_writer(struct phytium_spi *fts) +{ + u32 max = tx_max(fts); + u16 txw = 0; + + while (max--) { + if (fts->tx_end - fts->len) { + if (fts->n_bytes == 1) + txw = *(u8 *)(fts->tx); + else + txw = *(u16 *)(fts->tx); + } + phytium_write_io_reg(fts, DR, txw); + fts->tx += fts->n_bytes; + } +} + +static void phytium_reader(struct phytium_spi *fts) +{ + u32 max = rx_max(fts); + u16 rxw; + + while (max--) { + rxw = phytium_read_io_reg(fts, DR); + if (fts->rx_end - fts->len) { + if (fts->n_bytes == 1) + *(u8 *)(fts->rx) = rxw; + else + *(u16 *)(fts->rx) = rxw; + } + fts->rx += fts->n_bytes; + } +} + +static void int_error_stop(struct phytium_spi *fts, const char *msg) +{ + spi_reset_chip(fts); + + dev_err(&fts->master->dev, "%s\n", msg); + fts->master->cur_msg->status = -EIO; + spi_finalize_current_transfer(fts->master); +} + +static irqreturn_t interrupt_transfer(struct phytium_spi *fts) +{ + u16 irq_status = phytium_readl(fts, ISR); + + if (irq_status & (INT_TXOI | INT_RXOI | INT_RXUI)) { + phytium_readl(fts, ICR); + int_error_stop(fts, "irq transfer: fifo overrun/underrun"); + return IRQ_HANDLED; + } + + phytium_reader(fts); + if (fts->rx_end == fts->rx) { + spi_mask_intr(fts, INT_TXEI); + spi_finalize_current_transfer(fts->master); + return IRQ_HANDLED; + } + if (irq_status & INT_TXEI) { + spi_mask_intr(fts, INT_TXEI); + phytium_writer(fts); + spi_umask_intr(fts, INT_TXEI); + } + + return IRQ_HANDLED; +} + +static irqreturn_t phytium_spi_irq(int irq, void *dev_id) +{ + struct spi_controller *master = dev_id; + struct phytium_spi *fts = spi_controller_get_devdata(master); + u16 irq_status = phytium_readl(fts, ISR) & 0x3f; + + if (!irq_status) + return IRQ_NONE; + + if (!master->cur_msg) { + spi_mask_intr(fts, INT_TXEI); + return IRQ_HANDLED; + } + + if (fts->transfer_handler) + return fts->transfer_handler(fts); + else + return IRQ_HANDLED; +} + +static int poll_transfer(struct phytium_spi *fts) +{ + do { + phytium_writer(fts); + phytium_reader(fts); + cpu_relax(); + } while (fts->rx_end > fts->rx); + + return 0; +} + +static int phytium_spi_transfer_one(struct spi_controller *master, + struct spi_device *spi, struct spi_transfer *transfer) +{ + struct phytium_spi *fts = spi_controller_get_devdata(master); + struct chip_data *chip = spi_get_ctldata(spi); + u8 imask = 0; + u16 txlevel = 0; + u16 clk_div; + u32 cr0; + + fts->tx = (void *)transfer->tx_buf; + fts->tx_end = fts->tx + transfer->len; + fts->rx = transfer->rx_buf; + fts->rx_end = fts->rx + transfer->len; + fts->len = transfer->len; + + spi_enable_chip(fts, 0); + + if (transfer->speed_hz != chip->speed_hz) { + clk_div = (fts->max_freq / transfer->speed_hz + 1) & 0xfffe; + + chip->speed_hz = transfer->speed_hz; + chip->clk_div = clk_div; + + spi_set_clk(fts, chip->clk_div); + } + + if (transfer->bits_per_word == 8) + fts->n_bytes = 1; + else if (transfer->bits_per_word == 16) + fts->n_bytes = 2; + else + return -EINVAL; + + cr0 = (transfer->bits_per_word - 1) + | (chip->type << FRF_OFFSET) + | (spi->mode << MODE_OFFSET) + | (chip->tmode << TMOD_OFFSET); + + if (chip->cs_control) { + if (fts->rx && fts->tx) + chip->tmode = TMOD_TR; + else if (fts->rx) + chip->tmode = TMOD_RO; + else + chip->tmode = TMOD_TO; + + cr0 &= ~TMOD_MASK; + cr0 |= (chip->tmode << TMOD_OFFSET); + } + + phytium_writel(fts, CTRL0, cr0); + + spi_mask_intr(fts, 0xff); + + if (!chip->poll_mode) { + txlevel = min_t(u16, fts->fifo_len / 2, fts->len / fts->n_bytes); + phytium_writel(fts, TXFLTR, txlevel); + + imask |= INT_TXEI | INT_TXOI | + INT_RXUI | INT_RXOI; + spi_umask_intr(fts, imask); + + fts->transfer_handler = interrupt_transfer; + } + + spi_enable_chip(fts, 1); + + if (chip->poll_mode) + return poll_transfer(fts); + + return 1; +} + +static void phytium_spi_handle_err(struct spi_controller *master, + struct spi_message *msg) +{ + struct phytium_spi *fts = spi_controller_get_devdata(master); + + spi_reset_chip(fts); +} + +static int phytium_spi_setup(struct spi_device *spi) +{ + struct phytium_spi_chip *chip_info = NULL; + struct chip_data *chip; + struct spi_controller *master = spi->controller; + struct phytium_spi *fts = spi_controller_get_devdata(master); + u32 cr0; + + spi_enable_chip(fts, 0); + + chip = spi_get_ctldata(spi); + if (!chip) { + chip = kzalloc(sizeof(struct chip_data), GFP_KERNEL); + if (!chip) + return -ENOMEM; + spi_set_ctldata(spi, chip); + } + + chip_info = spi->controller_data; + + if (chip_info) { + if (chip_info->cs_control) + chip->cs_control = chip_info->cs_control; + + chip->poll_mode = chip_info->poll_mode; + chip->type = chip_info->type; + } + + chip->tmode = 0; + + cr0 = (spi->bits_per_word - 1) | (chip->type << FRF_OFFSET) | + (spi->mode << MODE_OFFSET) | (chip->tmode << TMOD_OFFSET); + + phytium_writel(fts, CTRL0, cr0); + + spi_enable_chip(fts, 1); + + return 0; +} + +static void phytium_spi_cleanup(struct spi_device *spi) +{ + struct chip_data *chip = spi_get_ctldata(spi); + + kfree(chip); + spi_set_ctldata(spi, NULL); +} + +static void spi_hw_init(struct device *dev, struct phytium_spi *fts) +{ + spi_reset_chip(fts); + + if (!fts->fifo_len) { + u32 fifo; + + for (fifo = 1; fifo < 256; fifo++) { + phytium_writel(fts, TXFLTR, fifo); + if (fifo != phytium_readl(fts, TXFLTR)) + break; + } + phytium_writel(fts, TXFLTR, 0); + + fts->fifo_len = (fifo == 1) ? 0 : fifo; + dev_dbg(dev, "Detected FIFO size: %u bytes\n", fts->fifo_len); + } +} + +int phytium_spi_add_host(struct device *dev, struct phytium_spi *fts) +{ + struct spi_controller *master; + int ret; + + WARN_ON(fts == NULL); + + master = spi_alloc_host(dev, 0); + if (!master) + return -ENOMEM; + + fts->master = master; + snprintf(fts->name, sizeof(fts->name), "phytium_spi%d", fts->bus_num); + + ret = request_irq(fts->irq, phytium_spi_irq, IRQF_SHARED, fts->name, master); + if (ret < 0) { + dev_err(dev, "can not get IRQ\n"); + goto err_free_master; + } + + master->use_gpio_descriptors = true; + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP; + master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16); + master->bus_num = fts->bus_num; + master->num_chipselect = fts->num_cs; + master->setup = phytium_spi_setup; + master->cleanup = phytium_spi_cleanup; + master->set_cs = phytium_spi_set_cs; + master->transfer_one = phytium_spi_transfer_one; + master->handle_err = phytium_spi_handle_err; + master->max_speed_hz = fts->max_freq; + master->dev.of_node = dev->of_node; + master->dev.fwnode = dev->fwnode; + master->flags = SPI_CONTROLLER_GPIO_SS; + + spi_hw_init(dev, fts); + + spi_controller_set_devdata(master, fts); + ret = devm_spi_register_controller(dev, master); + if (ret) { + dev_err(&master->dev, "problem registering spi master\n"); + goto err_exit; + } + + return 0; + +err_exit: + spi_enable_chip(fts, 0); + free_irq(fts->irq, master); +err_free_master: + spi_controller_put(master); + return ret; +} +EXPORT_SYMBOL_GPL(phytium_spi_add_host); + +void phytium_spi_remove_host(struct phytium_spi *fts) +{ + spi_shutdown_chip(fts); + + free_irq(fts->irq, fts->master); +} +EXPORT_SYMBOL_GPL(phytium_spi_remove_host); + +int phytium_spi_suspend_host(struct phytium_spi *fts) +{ + int ret; + + ret = spi_controller_suspend(fts->master); + if (ret) + return ret; + + spi_shutdown_chip(fts); + return 0; +} +EXPORT_SYMBOL_GPL(phytium_spi_suspend_host); + +int phytium_spi_resume_host(struct phytium_spi *fts) +{ + int ret; + + spi_hw_init(&fts->master->dev, fts); + ret = spi_controller_resume(fts->master); + if (ret) + dev_err(&fts->master->dev, "fail to start queue (%d)\n", ret); + return ret; +} +EXPORT_SYMBOL_GPL(phytium_spi_resume_host); + +MODULE_AUTHOR("Zhu Mingshuai "); +MODULE_AUTHOR("Chen Baozi "); +MODULE_DESCRIPTION("Driver for Phytium SPI controller core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-phytium.h b/drivers/spi/spi-phytium.h new file mode 100644 index 0000000000000..02aa159ab3961 --- /dev/null +++ b/drivers/spi/spi-phytium.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Phytium SPI controller driver. + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd. + */ +#ifndef PHYTIUM_SPI_HEADER_H +#define PHYTIUM_SPI_HEADER_H + +#include +#include +#include + +#define CTRL0 0x00 +#define SSIENR 0x08 +#define SER 0x10 +#define BAUDR 0x14 +#define TXFLTR 0x18 +#define TXFLR 0x20 +#define RXFLR 0x24 +#define IMR 0x2c +#define ISR 0x30 +#define ICR 0x48 +#define DR 0x60 +#define GCSR 0x100 + +#define FRF_OFFSET 4 +#define MODE_OFFSET 6 +#define TMOD_OFFSET 8 + +#define TMOD_MASK (0x3 << TMOD_OFFSET) +#define TMOD_TR 0x0 +#define TMOD_TO 0x1 +#define TMOD_RO 0x2 + +#define INT_TXEI (1 << 0) +#define INT_TXOI (1 << 1) +#define INT_RXUI (1 << 2) +#define INT_RXOI (1 << 3) + +struct phytium_spi { + struct spi_controller *master; + char name[16]; + + void __iomem *regs; + bool global_cs; + unsigned long paddr; + int irq; + u32 fifo_len; + u32 max_freq; + + u32 reg_io_width; + u16 bus_num; + u16 num_cs; + int *cs; + + size_t len; + void *tx; + void *tx_end; + void *rx; + void *rx_end; + u8 n_bytes; + irqreturn_t (*transfer_handler)(struct phytium_spi *fts); +}; + +extern int phytium_spi_add_host(struct device *dev, struct phytium_spi *fts); +extern void phytium_spi_remove_host(struct phytium_spi *fts); +extern int phytium_spi_suspend_host(struct phytium_spi *fts); +extern int phytium_spi_resume_host(struct phytium_spi *fts); + +#endif /* PHYTIUM_SPI_HEADER_H */ From 9f6b1b5bb88127294c83ce025c1a4ee5d632ca54 Mon Sep 17 00:00:00 2001 From: yuanxia Date: Mon, 13 May 2024 18:09:36 +0800 Subject: [PATCH 003/101] dt-bindings: spi: phytium-qspi: Document device tree binding Add the device tree binding documentation for Phytium QuadSPI controller. Signed-off-by: yuanxia Signed-off-by: Chen Baozi Signed-off-by: Wang Yinfeng Link: https://github.com/deepin-community/kernel/pull/136 (cherry picked from commit 9d11018c325ab41bde8576c792a27b8e139e98bc) Signed-off-by: Wentao Guan --- .../bindings/spi/phytium,qspi-nor.yaml | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 Documentation/devicetree/bindings/spi/phytium,qspi-nor.yaml diff --git a/Documentation/devicetree/bindings/spi/phytium,qspi-nor.yaml b/Documentation/devicetree/bindings/spi/phytium,qspi-nor.yaml new file mode 100644 index 0000000000000..8ff781a145313 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/phytium,qspi-nor.yaml @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/phytium,qspi-nor.yaml# +$schema: http://devicetree.org/schemas/meta-schemas/core.yaml# + +title: Phytium Quad Serial Peripheral Interface (QSPI) bindings + +maintainers: + - Chen Baozi + +allOf: + - $ref: "spi-controller.yaml#" + +properties: + compatible: + const: phytium,qspi-nor + + reg: + items: + - description: registers + - description: memory mapping region + + reg-names: + items: + - const: qspi + - const: qspi_mm + + clocks: + maxItems: 1 + + no-direct-mapping: + $ref: /schemas/types.yaml#/definitions/flag + description: + Indicates if we can use direct mapping to access the flash + +required: + - compatible + - reg + - reg-names + - clocks + +unevaluateProperties: false + +examples: + - | + qspi: qspi@28014000 { + compatible = "phytium,qspi-nor"; + reg = <0x0 0x28014000 0x0 0x1000>, + <0x0 0x0 0x0 0x02000000>; + reg-names = "qspi", "qspi_mm"; + clocks = <&sysclk_600mhz>; + + #address-cells = <1>; + #size-cells = <0>; + + flash@0 { + compatible = "jedec,spi-nor"; + reg = <0>; + spi-rx-bus-width = <4>; + spi-max-frequency = <600000000>; + }; + + flash@1 { + compatible = "jedec,spi-nor"; + reg = <1>; + spi-rx-bus-width = <4>; + spi-max-frequency = <600000000>; + }; + }; From 9f2e21865eb1a61386e0ebcc3c2f6d32bfd3a029 Mon Sep 17 00:00:00 2001 From: yuanxia Date: Mon, 13 May 2024 18:11:58 +0800 Subject: [PATCH 004/101] arm64: spi: Phytium-qspi: Add support for Phytium QSPI controller This patch adds a new driver for Phytium QuadSPI (QSPI) controller, which is used to access NOR Flash memory slaves. The driver implements spi-mem framework and does not support generic SPI operations. Signed-off-by: yuanxia Signed-off-by: Zhou Yulin Signed-off-by: Chen Baozi Signed-off-by: Wang Yinfeng Link: https://github.com/deepin-community/kernel/pull/136 [ Wentao Guan: for v6.18 ] [ Wentao Guan: XXXX ] (cherry picked from commit 68716d1461f9217bf7d885e8877dec3f529d4fa9) Signed-off-by: Wentao Guan --- drivers/spi/Kconfig | 13 + drivers/spi/Makefile | 1 + drivers/spi/spi-phytium-qspi.c | 799 +++++++++++++++++++++++++++++++++ 3 files changed, 813 insertions(+) create mode 100644 drivers/spi/spi-phytium-qspi.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 41602047f176e..52a009754c4e6 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -868,6 +868,19 @@ config SPI_PHYTIUM_PCI If unsure, say N. +config SPI_PHYTIUM_QSPI + tristate "Phytium Quad SPI controller" + depends on ARCH_PHYTIUM || COMPILE_TEST + depends on OF + depends on SPI_MEM + help + This enables support for Phytium Quad SPI flash controller. + + This driver does not support generic SPI. The implementation only + supports spi-mem interface. + + If unsure, say N. + config SPI_PIC32 tristate "Microchip PIC32 series SPI" depends on MACH_PIC32 || COMPILE_TEST diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 3cf39660c266b..5b276edcf57f4 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -115,6 +115,7 @@ obj-$(CONFIG_SPI_PCI1XXXX) += spi-pci1xxxx.o obj-$(CONFIG_SPI_PHYTIUM) += spi-phytium.o obj-$(CONFIG_SPI_PHYTIUM_PLAT) += spi-phytium-plat.o obj-$(CONFIG_SPI_PHYTIUM_PCI) += spi-phytium-pci.o +obj-$(CONFIG_SPI_PHYTIUM_QSPI) += spi-phytium-qspi.o obj-$(CONFIG_SPI_PIC32) += spi-pic32.o obj-$(CONFIG_SPI_PIC32_SQI) += spi-pic32-sqi.o obj-$(CONFIG_SPI_PL022) += spi-pl022.o diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c new file mode 100644 index 0000000000000..21e7b6ccb9620 --- /dev/null +++ b/drivers/spi/spi-phytium-qspi.c @@ -0,0 +1,799 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium Quad SPI controller driver. + * + * Copyright (c) 2022-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +#define QSPI_FLASH_CAP_REG 0x00 +#define QSPI_FLASH_CAP_NUM_SHIFT 3 +#define QSPI_FLASH_CAP_NUM_MASK (0x3 << QSPI_FLASH_CAP_NUM_SHIFT) +#define QSPI_FLASH_CAP_CAP_SHIFT 0 +#define QSPI_FLASH_CAP_CAP_MASK (0x7 << QSPI_FLASH_CAP_CAP_SHIFT) + +#define QSPI_RD_CFG_REG 0x04 +#define QSPI_RD_CFG_RD_CMD_SHIFT 24 +#define QSPI_RD_CFG_RD_CMD_MASK (0xff << QSPI_RD_CFG_RD_CMD_SHIFT) +#define QSPI_RD_CFG_RD_THROUGH_SHIFT 23 +#define QSPI_RD_CFG_RD_THROUGH_MASK (0x1 << QSPI_RD_CFG_RD_THROUGH_SHIFT) +#define QSPI_RD_CFG_RD_TRANSFER_SHIFT 20 +#define QSPI_RD_CFG_RD_TRANSFER_MASK (0x7 << QSPI_RD_CFG_RD_TRANSFER_SHIFT) +#define QSPI_RD_CFG_RD_ADDR_SEL_SHIFT 19 +#define QSPI_RD_CFG_RD_ADDR_SEL_MASK (0x1 << QSPI_RD_CFG_RD_ADDR_SEL_SHIFT) +#define QSPI_RD_CFG_RD_LATENCY_SHIFT 18 +#define QSPI_RD_CFG_RD_LATENCY_MASK (0x1 << QSPI_RD_CFG_RD_LATENCY_SHIFT) +#define QSPI_RD_CFG_MODE_BYTE_SHIFT 17 +#define QSPI_RD_CFG_MODE_BYTE_MASK (0x1 << QSPI_RD_CFG_MODE_BYTE_SHIFT) +#define QSPI_RD_CFG_CMD_SIGN_SHIFT 9 +#define QSPI_RD_CFG_CMD_SIGN_MASK (0xff << QSPI_RD_CFG_CMD_SIGN_SHIFT) +#define QSPI_RD_CFG_DUMMY_SHIFT 4 +#define QSPI_RD_CFG_DUMMY_MASK (0x1f << QSPI_RD_CFG_DUMMY_SHIFT) +#define QSPI_RD_CFG_D_BUFFER_SHIFT 3 +#define QSPI_RD_CFG_D_BUFFER_MASK (0x1 << QSPI_RD_CFG_D_BUFFER_SHIFT) +#define QSPI_RD_CFG_RD_SCK_SEL_SHIFT 0 +#define QSPI_RD_CFG_RD_SCK_SEL_MASK (0x3 << QSPI_RD_CFG_RD_SCK_SEL_SHIFT) + +#define QSPI_WR_CFG_REG 0x08 +#define QSPI_WR_CFG_WR_CMD_SHIFT 24 +#define QSPI_WR_CFG_WR_CMD_MASK (0xff << QSPI_WR_CFG_WR_CMD_SHIFT) +#define QSPI_WR_CFG_WR_WAIT_SHIFT 9 +#define QSPI_WR_CFG_WR_WAIT_MASK (0x01 << QSPI_WR_CFG_WR_WAIT_SHIFT) +#define QSPI_WR_CFG_WR_THROUGH_SHIFT 8 +#define QSPI_WR_CFG_WR_THROUGH_MASK (0x01 << QSPI_WR_CFG_WR_THROUGH_SHIFT) +#define QSPI_WR_CFG_WR_TRANSFER_SHIFT 5 +#define QSPI_WR_CFG_WR_TRANSFER_MASK (0X7 << QSPI_WR_CFG_WR_TRANSFER_SHIFT) +#define QSPI_WR_CFG_WR_ADDR_SEL_SHIFT 4 +#define QSPI_WR_CFG_WR_ADDR_SEL_MASK (0x1 << QSPI_WR_CFG_WR_ADDR_SEL_SHIFT) +#define QSPI_WR_CFG_WR_MODE_SHIFT 3 +#define QSPI_WR_CFG_WR_MODE_MASK (0x1 << QSPI_WR_CFG_WR_MODE_SHIFT) +#define QSPI_WR_CFG_WR_SCK_SEL_SHIFT 0 +#define QSPI_WR_CFG_WR_SCK_SEL_MASK (0x3 << QSPI_WR_CFG_WR_SCK_SEL_SHIFT) + +#define QSPI_FLUSH_REG 0x0c +#define QSPI_FLUSH_EN (0x1 << 0) + +#define QSPI_CMD_PORT_REG 0x10 +#define QSPI_CMD_PORT_CMD_SHIFT 24 +#define QSPI_CMD_PORT_CMD_MASK (0xff << QSPI_CMD_PORT_CMD_SHIFT) +#define QSPI_CMD_PORT_WAIT_SHIFT 22 +#define QSPI_CMD_PORT_WAIT_MASK (0x1 << QSPI_CMD_PORT_WAIT_SHIFT) +#define QSPI_CMD_PORT_THROUGH_SHIFT 21 +#define QSPI_CMD_PORT_THROUGH_MASK (0x1 << QSPI_CMD_PORT_THROUGH_SHIFT) +#define QSPI_CMD_PORT_CS_SHIFT 19 +#define QSPI_CMD_PORT_CS_MASK (0x3 << QSPI_CMD_PORT_CS_SHIFT) +#define QSPI_CMD_PORT_TRANSFER_SHIFT 16 +#define QSPI_CMD_PORT_TRANSFER_MASK (0x7 << QSPI_CMD_PORT_TRANSFER_SHIFT) +#define QSPI_CMD_PORT_CMD_ADDR_SHIFT 15 +#define QSPI_CMD_PORT_CMD_ADDR_MASK (0x1 << QSPI_CMD_PORT_CMD_ADDR_SHIFT) +#define QSPI_CMD_PORT_LATENCY_SHIFT 14 +#define QSPI_CMD_PORT_LATENCY_MASK (0x1 << QSPI_CMD_PORT_LATENCY_SHIFT) +#define QSPI_CMD_PORT_DATA_XFER_SHIFT 13 +#define QSPI_CMD_PORT_DATA_XFER_MASK (0x1 << QSPI_CMD_PORT_DATA_XFER_SHIFT) +#define QSPI_CMD_PORT_ADDR_SEL_SHIFT 12 +#define QSPI_CMD_PORT_ADDR_SEL_MASK (0x1 << QSPI_CMD_PORT_ADDR_SEL_SHIFT) +#define QSPI_CMD_PORT_DUMMY_SHIFT 7 +#define QSPI_CMD_PORT_DUMMY_MASK (0x1f << QSPI_CMD_PORT_DUMMY_SHIFT) +#define QSPI_CMD_PORT_P_BUFFER_SHIFT 6 +#define QSPI_CMD_PORT_P_BUFFER_MASK (0x1 << QSPI_CMD_PORT_P_BUFFER_SHIFT) +#define QSPI_CMD_PORT_RW_NUM_SHIFT 3 +#define QSPI_CMD_PORT_RW_NUM_MASK (0x7 << QSPI_CMD_PORT_RW_NUM_SHIFT) +#define QSPI_CMD_PORT_SCK_SEL_SHIFT 0 +#define QSPI_CMD_PORT_SCK_SEL_MASK (0x3 << QSPI_CMD_PORT_SCK_SEL_SHIFT) + +#define QSPI_ADDR_PORT_REG 0x14 +#define QSPI_HD_PORT_REG 0x18 +#define QSPI_LD_PORT_REG 0x1c + +#define QSPI_FUN_SET_REG 0x20 +#define QSPI_FUN_SET_HOLD_SHIFT 24 +#define QSPI_FUN_SET_HOLD_MASK (0xff << QSPI_FUN_SET_HOLD_SHIFT) +#define QSPI_FUN_SET_SETUP_SHIFT 16 +#define QSPI_FUN_SET_SETUP_MASK (0xff << QSPI_FUN_SET_SETUP_SHIFT) +#define QSPI_FUN_SET_DELAY_SHIFT 0 +#define QSPI_FUN_SET_DELAY_MASK (0xffff << QSPI_FUN_SET_DELAY_SHIFT) + +#define QSPI_WIP_REG 0x24 +#define QSPI_WIP_W_CMD_SHIFT 24 +#define QSPI_WIP_W_CMD_MASK (0xff << QSPI_WIP_W_CMD_SHIFT) +#define QSPI_WIP_W_TRANSFER_SHIFT 3 +#define QSPI_WIP_W_TRANSFER_MASK (0x3 << QSPI_WIP_W_TRANSFER_SHIFT) +#define QSPI_WIP_W_SCK_SEL_SHIFT 0 +#define QSPI_WIP_W_SCK_SEL_MASK (0x7 << QSPI_WIP_W_SCK_SEL_SHIFT) + +#define QSPI_WP_REG 0x28 +#define QSPI_WP_EN_SHIFT 17 +#define QSPI_WP_EN_MASK (0x1 << QSPI_WP_EN_SHIFT) +#define QSPI_WP_IO2_SHIFT 16 +#define QSPI_WP_IO2_MASK (0x1 << QSPI_WP_IO2_SHIFT) +#define QSPI_WP_HOLD_SHIFT 8 +#define QSPI_WP_HOLD_MASK (0xff << QSPI_WP_HOLD_SHIFT) +#define QSPI_WP_SETUP_SHIFT 0 +#define QSPI_WP_SETUP_MASK (0xff << QSPI_WP_SETUP_SHIFT) + +#define QSPI_MODE_REG 0x2c +#define QSPI_MODE_VALID_SHIFT 8 +#define QSPI_MODE_VALID_MASK (0xff << QSPI_MODE_VALID_SHIFT) +#define QSPI_MODE_SHIFT 0 +#define QSPI_MODE_MASK (0xff << QSPI_MODE_SHIFT) + +#define PHYTIUM_QSPI_MAX_NORCHIP 4 +#define PHYTIUM_QSPI_MAX_MMAP_SZ (SZ_256M * PHYTIUM_QSPI_MAX_NORCHIP) +#define PHYTIUM_QSPI_MAX_XFER_SZ 8 +#define PHYTIUM_QSPI_DEFAULT_SCK_SEL 5 + +#define XFER_PROTO_1_1_1 0x0 +#define XFER_PROTO_1_1_2 0x1 +#define XFER_PROTO_1_1_4 0x2 +#define XFER_PROTO_1_2_2 0x3 +#define XFER_PROTO_1_4_4 0x4 +#define XFER_PROTO_2_2_2 0x5 +#define XFER_PROTO_4_4_4 0x6 + +struct phytium_qspi_flash { + u32 cs; + u32 clk_div; + + void __iomem *base; + resource_size_t size; + struct spi_device *spi; +}; + +struct phytium_qspi { + struct device *dev; + struct spi_controller *ctrl; + + void __iomem *io_base; + void __iomem *mm_base; + resource_size_t mm_size; + resource_size_t used_size; + + struct clk *clk; + u32 clk_rate; + + struct phytium_qspi_flash flash[PHYTIUM_QSPI_MAX_NORCHIP]; + u8 fnum; + bool nodirmap; +}; + +static bool phytium_qspi_check_buswidth(u8 width) +{ + switch (width) { + case 1: + case 2: + case 4: + return 0; + } + + return -EOPNOTSUPP; +} + +static uint phytium_spi_nor_clac_clk_div(int div) +{ + uint clk_div = 0; + + if (div <= 2) + clk_div = 1; + else if (div <= 4) + clk_div = 2; + else if (div <= 8) + clk_div = 3; + else if (div <= 16) + clk_div = 4; + else if (div <= 32) + clk_div = 5; + else if (div <= 64) + clk_div = 6; + else if (div <= 128) + clk_div = 7; + else + clk_div = 65535; + + return clk_div; +} + +static int phytium_spi_nor_protocol_encode(const struct spi_mem_op *op, u32 *code) +{ + int ret = 0; + + if (op->cmd.buswidth == 1 && + op->addr.buswidth == 1 && + op->data.buswidth == 1) + *code = XFER_PROTO_1_1_1; + else if (op->cmd.buswidth == 1 && + op->addr.buswidth == 1 && + op->data.buswidth == 2) + *code = XFER_PROTO_1_1_2; + else if (op->cmd.buswidth == 1 && + op->addr.buswidth == 1 && + op->data.buswidth == 4) + *code = XFER_PROTO_1_1_4; + else if (op->cmd.buswidth == 1 && + op->addr.buswidth == 2 && + op->data.buswidth == 2) + *code = XFER_PROTO_1_2_2; + else if (op->cmd.buswidth == 1 && + op->addr.buswidth == 4 && + op->data.buswidth == 4) + *code = XFER_PROTO_1_4_4; + else if (op->cmd.buswidth == 2 && + op->addr.buswidth == 2 && + op->data.buswidth == 2) + *code = XFER_PROTO_2_2_2; + else if (op->cmd.buswidth == 4 && + op->addr.buswidth == 4 && + op->data.buswidth == 4) + *code = XFER_PROTO_4_4_4; + else + *code = XFER_PROTO_1_1_1; + + return ret; +} + +static int phytium_qspi_flash_capacity_encode(u32 size, u32 *cap) +{ + int ret = 0; + + switch (size) { + case SZ_4M: + *cap = 0x0; + break; + case SZ_8M: + *cap = 0x1; + break; + case SZ_16M: + *cap = 0x2; + break; + case SZ_32M: + *cap = 0x3; + break; + case SZ_64M: + *cap = 0x4; + break; + case SZ_128M: + *cap = 0x5; + break; + case SZ_256M: + *cap = 0x6; + break; + case SZ_512M: + *cap = 0x7; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int phytium_qspi_write_port(struct phytium_qspi *qspi, + const u8 *buf, const size_t len) +{ + u32 bouncebuf[2] = { 0 }; + + if (len > PHYTIUM_QSPI_MAX_XFER_SZ) { + dev_err(qspi->dev, "WRITE data exceeds 8 bytes.\n"); + return -EINVAL; + } + + memcpy(bouncebuf, buf, len); + + if (len > 4) + writel_relaxed(bouncebuf[1], qspi->io_base + QSPI_HD_PORT_REG); + writel_relaxed(bouncebuf[0], qspi->io_base + QSPI_LD_PORT_REG); + + return 0; +} + +static int phytium_qspi_read_port(struct phytium_qspi *qspi, + u8 *buf, size_t len) +{ + u32 bouncebuf[2] = { 0 }; + + if (len > PHYTIUM_QSPI_MAX_XFER_SZ) { + dev_err(qspi->dev, "READ data exceeds 8 bytes.\n"); + return -EINVAL; + } + + /* Dummy write to LD_PORT register and issue READ ops*/ + writel_relaxed(0, qspi->io_base + QSPI_LD_PORT_REG); + + /* Read data */ + bouncebuf[0] = readl_relaxed(qspi->io_base + QSPI_LD_PORT_REG); + if (len > 4) + bouncebuf[1] = readl_relaxed(qspi->io_base + QSPI_HD_PORT_REG); + + memcpy(buf, bouncebuf, len); + + return 0; +} + +static int phytium_qspi_adjust_op_size(struct spi_mem *mem, + struct spi_mem_op *op) +{ + if (op->data.nbytes > PHYTIUM_QSPI_MAX_XFER_SZ) + op->data.nbytes = PHYTIUM_QSPI_MAX_XFER_SZ; + + return 0; +} + +static bool phytium_qspi_supports_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + int ret; + + ret = phytium_qspi_check_buswidth(op->cmd.buswidth); + + if (op->addr.nbytes) + ret |= phytium_qspi_check_buswidth(op->addr.buswidth); + + if (op->dummy.nbytes) + ret |= phytium_qspi_check_buswidth(op->dummy.buswidth); + + if (op->data.nbytes) + ret |= phytium_qspi_check_buswidth(op->data.buswidth); + + if (ret) + return false; + + /* Max 32 dummy clock cycles supported */ + if (op->dummy.nbytes && + (op->dummy.nbytes * 8 / op->dummy.buswidth > 32)) + return false; + + return spi_mem_default_supports_op(mem, op); +} + +static int phytium_qspi_exec_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + struct phytium_qspi *qspi = spi_controller_get_devdata(mem->spi->controller); + struct phytium_qspi_flash *flash = &qspi->flash[spi_get_chipselect(mem->spi, 0)]; + u32 cmd, transfer; + int ret; + + dev_dbg(qspi->dev, "cmd:%#x mode: %d.%d.%d.%d addr:%#llx len:%#x\n", + op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth, + op->dummy.buswidth, op->data.buswidth, op->addr.val, + op->data.nbytes); + + cmd = op->cmd.opcode << QSPI_CMD_PORT_CMD_SHIFT; + cmd |= flash->cs << QSPI_CMD_PORT_CS_SHIFT; + + ret = phytium_spi_nor_protocol_encode(op, &transfer); + if (ret) { + dev_err(qspi->dev, "Unsupported SPI NOR protocol.\n"); + goto out; + } + cmd |= transfer << QSPI_CMD_PORT_TRANSFER_SHIFT; + + if (op->addr.nbytes) { + cmd |= QSPI_CMD_PORT_CMD_ADDR_MASK; + if (op->addr.nbytes == 4) + cmd |= QSPI_CMD_PORT_ADDR_SEL_MASK; + + /* Write target address to ADDR_PORT register */ + writel_relaxed(op->addr.val, qspi->io_base + QSPI_ADDR_PORT_REG); + } + + if (op->dummy.nbytes) { + cmd |= QSPI_CMD_PORT_LATENCY_MASK; + cmd |= ((op->dummy.nbytes * 8) / op->dummy.buswidth) << + QSPI_CMD_PORT_LATENCY_SHIFT; + } + + if (op->data.nbytes) { + cmd |= QSPI_CMD_PORT_DATA_XFER_MASK; + cmd &= ~QSPI_CMD_PORT_P_BUFFER_MASK; + cmd |= (op->data.nbytes-1) << QSPI_CMD_PORT_RW_NUM_SHIFT; + } + + cmd |= flash->clk_div; + writel_relaxed(cmd, qspi->io_base + QSPI_CMD_PORT_REG); + + if (op->data.dir == SPI_MEM_DATA_IN) { + ret = phytium_qspi_read_port(qspi, op->data.buf.in, op->data.nbytes); + if (ret) { + dev_err(qspi->dev, "Failed to read data from the port.\n"); + goto out; + } + } else if (op->data.dir == SPI_MEM_DATA_OUT) { + ret = phytium_qspi_write_port(qspi, op->data.buf.out, op->data.nbytes); + if (ret) { + dev_err(qspi->dev, "Failed to write data to the port.\n"); + goto out; + } + } else { + /* Dummy write to LD_PORT register and issue the command */ + writel_relaxed(0, qspi->io_base + QSPI_LD_PORT_REG); + } + +out: + return ret; +} + +static int phytium_qspi_dirmap_create(struct spi_mem_dirmap_desc *desc) +{ + struct spi_device *spi = desc->mem->spi; + struct phytium_qspi *qspi = spi_controller_get_devdata(spi->controller); + struct phytium_qspi_flash *flash = &qspi->flash[spi_get_chipselect(spi, 0)]; + struct spi_nor *nor = spi_mem_get_drvdata(desc->mem); + u32 cmd, transfer; + int ret = 0; + + if (!qspi->mm_base || !qspi->mm_size) { + ret = -EOPNOTSUPP; + goto out; + } + + if (!flash->base) { + flash->base = qspi->mm_base + qspi->used_size; + qspi->used_size += nor->mtd.size; + } + + /* Setup RD/WR_CFG register */ + if (desc->info.op_tmpl->data.dir == SPI_MEM_DATA_IN) { + cmd = desc->info.op_tmpl->cmd.opcode << QSPI_RD_CFG_RD_CMD_SHIFT; + ret = phytium_spi_nor_protocol_encode(desc->info.op_tmpl, &transfer); + if (ret) { + dev_err(qspi->dev, "Unsupported SPI NOR protocol.\n"); + goto out; + } + cmd |= transfer << QSPI_RD_CFG_RD_TRANSFER_SHIFT; + + if (desc->info.op_tmpl->addr.nbytes == 4) + cmd |= QSPI_RD_CFG_RD_ADDR_SEL_MASK; + + if (nor->read_dummy) { + cmd |= QSPI_RD_CFG_RD_LATENCY_MASK; + cmd |= (nor->read_dummy - 1) << QSPI_RD_CFG_DUMMY_SHIFT; + } + + cmd |= QSPI_RD_CFG_D_BUFFER_MASK; + cmd |= flash->clk_div & QSPI_RD_CFG_RD_SCK_SEL_MASK; + + writel_relaxed(cmd, qspi->io_base + QSPI_RD_CFG_REG); + + dev_dbg(qspi->dev, "Create read dirmap and setup RD_CFG_REG [%#x].\n", cmd); + } else if (desc->info.op_tmpl->data.dir == SPI_MEM_DATA_OUT) { + cmd = desc->info.op_tmpl->cmd.opcode << QSPI_WR_CFG_WR_CMD_SHIFT; + ret = phytium_spi_nor_protocol_encode(desc->info.op_tmpl, &transfer); + if (ret) { + dev_err(qspi->dev, "Unsupported SPI NOR protocol.\n"); + goto out; + } + cmd |= transfer << QSPI_WR_CFG_WR_TRANSFER_SHIFT; + + if (desc->info.op_tmpl->addr.nbytes == 4) + cmd |= QSPI_WR_CFG_WR_ADDR_SEL_MASK; + + cmd |= QSPI_WR_CFG_WR_MODE_MASK; + cmd |= flash->clk_div & QSPI_WR_CFG_WR_SCK_SEL_MASK; + + writel_relaxed(cmd, qspi->io_base + QSPI_WR_CFG_REG); + + dev_dbg(qspi->dev, "Create write dirmap and setup WR_CFG_REG [%#x].\n", cmd); + } else { + ret = -EINVAL; + } + +out: + return ret; +} + +static ssize_t phytium_qspi_dirmap_read(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, void *buf) +{ + struct spi_device *spi = desc->mem->spi; + struct phytium_qspi *qspi = spi_controller_get_devdata(spi->controller); + struct phytium_qspi_flash *flash = &qspi->flash[spi_get_chipselect(spi, 0)]; + + void __iomem *src = flash->base + offs; + u8 *buf_rx = buf; + + memcpy_fromio(buf_rx, src, len); + + return len; +} + +static ssize_t phytium_qspi_dirmap_write(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, const void *buf) +{ + struct spi_device *spi = desc->mem->spi; + struct phytium_qspi *qspi = spi_controller_get_devdata(spi->controller); + struct phytium_qspi_flash *flash = &qspi->flash[spi_get_chipselect(spi, 0)]; + + void __iomem *dst = flash->base + offs; + void __iomem *addr; + int i; + size_t mask = 0x03; + u_char tmp[4] = {0}; + + if (offs & 0x03) { + dev_err(qspi->dev, "Addr not four-byte aligned!\n"); + return -EINVAL; + } + + for (i = 0; i < len / 4; i++) + writel_relaxed(*(u32 *)(buf + 4 * i), dst + 4 * i); + + if (len & mask) { + addr = dst + (len & ~mask); + memcpy(tmp, buf + (len & ~mask), len & mask); + writel_relaxed(*(u32 *)(tmp), addr); + } + + //write cache data to flash + writel_relaxed(QSPI_FLUSH_EN, qspi->io_base + QSPI_FLUSH_REG); + + return len; +} + +static int phytium_qspi_setup(struct spi_device *spi) +{ + struct spi_controller *ctrl = spi->controller; + struct phytium_qspi *qspi = spi_controller_get_devdata(ctrl); + struct phytium_qspi_flash *flash; + uint clk_div; + + if (ctrl->busy) + return -EBUSY; + + flash = &qspi->flash[spi_get_chipselect(spi, 0)]; + + flash->cs = spi_get_chipselect(spi, 0); + flash->spi = spi; + if (flash->cs >= PHYTIUM_QSPI_MAX_NORCHIP) { + dev_err(qspi->dev, "Flash CS is out of range.\n"); + return -EINVAL; + } + qspi->fnum++; + + + if (spi->max_speed_hz) { + clk_div = DIV_ROUND_UP(qspi->clk_rate, spi->max_speed_hz); + flash->clk_div = phytium_spi_nor_clac_clk_div(clk_div); + if (flash->clk_div == 65535) { + dev_err(qspi->dev, "qspi maximum frequency setting is error.\n"); + return -EINVAL; + } + } else + flash->clk_div = PHYTIUM_QSPI_DEFAULT_SCK_SEL; + + return 0; +} + +static struct spi_controller_mem_ops phytium_qspi_mem_ops = { + .adjust_op_size = phytium_qspi_adjust_op_size, + .supports_op = phytium_qspi_supports_op, + .exec_op = phytium_qspi_exec_op, + .dirmap_create = phytium_qspi_dirmap_create, + .dirmap_read = phytium_qspi_dirmap_read, + .dirmap_write = phytium_qspi_dirmap_write, +}; + +/** + * Direct mapping is supported only when all flashes under the controller + * are of the same size and the mapping address is continuous. For those + * cases which flashes are of different sizes, the driver offered a non-dirmap + * mem_ops with which read/write ops is executed through command port. + */ +static struct spi_controller_mem_ops phytium_qspi_mem_ops_nodirmap = { + .adjust_op_size = phytium_qspi_adjust_op_size, + .supports_op = phytium_qspi_supports_op, + .exec_op = phytium_qspi_exec_op, +}; + +/** + * phytium_qspi_probe - Probe method for the QSPI driver + * @pdev: Pointer to the platform_device structure + * + * This function initializes the driver data structures and the hardware. + * + * Return: 0 on success and error value on failure + */ +static int phytium_qspi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct spi_controller *ctrl; + struct resource *res; + struct phytium_qspi *qspi; + int i, ret; + u32 flash_cap; + struct spi_mem *mem; + struct spi_nor *nor; + + ctrl = spi_alloc_host(dev, sizeof(*qspi)); + if (!ctrl) + return -ENOMEM; + + ctrl->mode_bits = SPI_CPOL | SPI_CPHA | + SPI_RX_DUAL | SPI_RX_QUAD | + SPI_TX_DUAL | SPI_TX_QUAD; + ctrl->setup = phytium_qspi_setup; + ctrl->num_chipselect = PHYTIUM_QSPI_MAX_NORCHIP; + ctrl->dev.of_node = dev->of_node; + + qspi = spi_controller_get_devdata(ctrl); + qspi->ctrl = ctrl; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi"); + qspi->io_base = devm_ioremap_resource(dev, res); + if (IS_ERR(qspi->io_base)) { + ret = PTR_ERR(qspi->io_base); + goto probe_master_put; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi_mm"); + qspi->mm_base = devm_ioremap_resource(dev, res); + if (IS_ERR(qspi->mm_base)) { + ret = PTR_ERR(qspi->mm_base); + goto probe_master_put; + } + + qspi->mm_size = resource_size(res); + if (qspi->mm_size > PHYTIUM_QSPI_MAX_MMAP_SZ) { + ret = -EINVAL; + goto probe_master_put; + } + qspi->used_size = 0; + + qspi->clk = devm_clk_get(dev, NULL); + if (IS_ERR(qspi->clk)) { + ret = PTR_ERR(qspi->clk); + goto probe_master_put; + } + + qspi->clk_rate = clk_get_rate(qspi->clk); + if (!qspi->clk_rate) { + ret = -EINVAL; + goto probe_master_put; + } + + pm_runtime_enable(dev); + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + pm_runtime_put_noidle(dev); + goto probe_master_put; + } + + ret = clk_prepare_enable(qspi->clk); + if (ret) { + dev_err(dev, "Failed to enable PCLK of the controller.\n"); + goto probe_clk_failed; + } + + qspi->nodirmap = device_property_present(dev, "no-direct-mapping"); + ctrl->mem_ops = qspi->nodirmap ? + &phytium_qspi_mem_ops_nodirmap : + &phytium_qspi_mem_ops; + + qspi->dev = dev; + platform_set_drvdata(pdev, qspi); + + ret = devm_spi_register_controller(dev, ctrl); + if (ret) { + dev_err(dev, "failed to register SPI controller: %d\n", ret); + goto probe_setup_failed; + } + + if (!qspi->nodirmap) { + /* + * The controller supports direct mapping access only if all + * flashes are of same size. + */ + + i = 0; + for (i = 0; qspi->fnum > i && i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { + if (qspi->flash[i].spi) { + mem = spi_get_drvdata(qspi->flash[i].spi); + if (mem) { + nor = spi_mem_get_drvdata(mem); + if (nor) + qspi->flash[i].size = nor->mtd.size; + } + } + } + + for (i = 1; qspi->fnum > i && i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { + if (qspi->flash[i].size != qspi->flash[0].size) { + dev_err(dev, "Flashes are of different sizes.\n"); + ret = -EINVAL; + goto probe_setup_failed; + } + } + + ret = phytium_qspi_flash_capacity_encode(qspi->flash[0].size, + &flash_cap); + if (ret) { + dev_err(dev, "Flash size is invalid.\n"); + goto probe_setup_failed; + } + + flash_cap |= qspi->fnum << QSPI_FLASH_CAP_NUM_SHIFT; + + writel_relaxed(flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); + } + + return 0; + +probe_setup_failed: + clk_disable_unprepare(qspi->clk); +probe_clk_failed: + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); +probe_master_put: + + return ret; +} + +/** + * phytium_qspi_remove - Remove method for the QSPI driver + * @pdev: Pointer to the platform_device structure + * + * This function is called if a device is physically removed from the system + * or if the driver module is being unloaded. It free all resources allocated + * to the device. + * + * Return: 0 on success and error value on failure + */ +static void phytium_qspi_remove(struct platform_device *pdev) +{ + struct phytium_qspi *qspi = platform_get_drvdata(pdev); + + clk_disable_unprepare(qspi->clk); + + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); +} + +static int __maybe_unused phytium_qspi_suspend(struct device *dev) +{ + return pm_runtime_force_suspend(dev); +} + +static int __maybe_unused phytium_qspi_resume(struct device *dev) +{ + return pm_runtime_force_resume(dev); +} + +static const struct dev_pm_ops phytium_qspi_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(phytium_qspi_suspend, + phytium_qspi_resume) +}; + +static const struct of_device_id phytium_qspi_of_match[] = { + { .compatible = "phytium,qspi-nor" }, + { } +}; +MODULE_DEVICE_TABLE(of, phytium_qspi_of_match); + +static struct platform_driver phytium_qspi_driver = { + .probe = phytium_qspi_probe, + .remove = phytium_qspi_remove, + .driver = { + .name = "phytium-qspi", + .of_match_table = of_match_ptr(phytium_qspi_of_match), + .pm = &phytium_qspi_pm_ops, + }, +}; +module_platform_driver(phytium_qspi_driver); + +MODULE_AUTHOR("Chen Baozi "); +MODULE_DESCRIPTION("Phytium Quad SPI driver"); +MODULE_LICENSE("GPL"); From 25449d0e072d4c35813132ba92698669ba614499 Mon Sep 17 00:00:00 2001 From: yuanxia Date: Mon, 13 May 2024 18:13:11 +0800 Subject: [PATCH 005/101] arm64: spi: Phytium: Fix controller unregister order The driver used devm_spi_register_controller() on bind. Therefore, __device_release_driver() first invokes phytium_spi_remove_host() before unregistering the SPI controller via devres_release_all() when unbinding. Since phytium_spi_remove_host() shuts down the chip, rendering the SPI bus inaccessible even through the SPI controller is still registered. When the SPI controller is subsequently unregistered, it unbinds all its slave devices. Because their drivers cannot access the SPI bus, the slave devices may be left in an improper state. Signed-off-by: yuanxia Signed-off-by: Chen Baozi Signed-off-by: Wang Yinfeng (cherry picked from commit d4126eaaeecbc7eb1daaa2abbe384c590195b7b0) Link: https://github.com/deepin-community/kernel/pull/136 [ Wentao Guan: XXXX ] Signed-off-by: Wentao Guan Conflicts: drivers/spi/spi-phytium.c --- drivers/spi/spi-phytium.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-phytium.c b/drivers/spi/spi-phytium.c index 8f230280381ac..eaacb729031cf 100644 --- a/drivers/spi/spi-phytium.c +++ b/drivers/spi/spi-phytium.c @@ -468,7 +468,7 @@ int phytium_spi_add_host(struct device *dev, struct phytium_spi *fts) spi_hw_init(dev, fts); spi_controller_set_devdata(master, fts); - ret = devm_spi_register_controller(dev, master); + ret = spi_register_controller(master); if (ret) { dev_err(&master->dev, "problem registering spi master\n"); goto err_exit; @@ -489,6 +489,8 @@ void phytium_spi_remove_host(struct phytium_spi *fts) { spi_shutdown_chip(fts); + spi_unregister_controller(fts->master); + free_irq(fts->irq, fts->master); } EXPORT_SYMBOL_GPL(phytium_spi_remove_host); From 8daec42314aad2d591cb8e18cff5572df4f66be4 Mon Sep 17 00:00:00 2001 From: yuanxia Date: Mon, 13 May 2024 18:16:37 +0800 Subject: [PATCH 006/101] arm64: spi: phytium: Extract right pointer from driver_data in PM ops Since the platform driver wraps the core phytium_spi into phytium_spi_clk struct to include a platform clock, the pointer extract from driver_data should also be 'struct phytium_spi_clk *'. Signed-off-by: yuanxia Reported-by: Zhou Yulin Signed-off-by: Chen Baozi Signed-off-by: Wang Yinfeng Link: https://github.com/deepin-community/kernel/pull/136 [ Wentao Guan: XXXX ] (cherry picked from commit 7587a36de1ce4390357705c2bdda8b9035fc7765) Signed-off-by: Wentao Guan Conflicts: drivers/spi/spi-phytium-plat.c --- drivers/spi/spi-phytium-plat.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-phytium-plat.c b/drivers/spi/spi-phytium-plat.c index d75b86dbd3df2..2c90511d605c3 100644 --- a/drivers/spi/spi-phytium-plat.c +++ b/drivers/spi/spi-phytium-plat.c @@ -117,18 +117,16 @@ static void phytium_spi_remove(struct platform_device *pdev) #ifdef CONFIG_PM_SLEEP static int spi_suspend(struct device *dev) { - struct spi_controller *master = dev_get_drvdata(dev); - struct phytium_spi *fts = spi_controller_get_devdata(master); + struct phytium_spi_clk *ftsc = dev_get_drvdata(dev); - return phytium_spi_suspend_host(fts); + return phytium_spi_suspend_host(&ftsc->fts); } static int spi_resume(struct device *dev) { - struct spi_controller *master = dev_get_drvdata(dev); - struct phytium_spi *fts = spi_controller_get_devdata(master); + struct phytium_spi_clk *ftsc = dev_get_drvdata(dev); - return phytium_spi_resume_host(fts); + return phytium_spi_resume_host(&ftsc->fts); } #endif From 038f64d867b30970043e90a581dc216cc52e331e Mon Sep 17 00:00:00 2001 From: yuanxia Date: Mon, 13 May 2024 18:18:13 +0800 Subject: [PATCH 007/101] arm64: spi: Phytium: Fix r/w sck_sel_mask 3 to 7 in SPI/QSPI driver The mask of sck_sel should be 0x7 instead of 0x3, otherwise the split is set differently than expected. Signed-off-by: yuanxia Signed-off-by: Liu Tianyu Signed-off-by: Li Mingzhe Signed-off-by: Wang Yinfeng Link: https://github.com/deepin-community/kernel/pull/136 (cherry picked from commit a5a55d4aa2833c643b191961f5ef4c9987b12f99) Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-qspi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index 21e7b6ccb9620..93e6a080ee7c6 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -46,7 +46,7 @@ #define QSPI_RD_CFG_D_BUFFER_SHIFT 3 #define QSPI_RD_CFG_D_BUFFER_MASK (0x1 << QSPI_RD_CFG_D_BUFFER_SHIFT) #define QSPI_RD_CFG_RD_SCK_SEL_SHIFT 0 -#define QSPI_RD_CFG_RD_SCK_SEL_MASK (0x3 << QSPI_RD_CFG_RD_SCK_SEL_SHIFT) +#define QSPI_RD_CFG_RD_SCK_SEL_MASK (0x7 << QSPI_RD_CFG_RD_SCK_SEL_SHIFT) #define QSPI_WR_CFG_REG 0x08 #define QSPI_WR_CFG_WR_CMD_SHIFT 24 @@ -62,7 +62,7 @@ #define QSPI_WR_CFG_WR_MODE_SHIFT 3 #define QSPI_WR_CFG_WR_MODE_MASK (0x1 << QSPI_WR_CFG_WR_MODE_SHIFT) #define QSPI_WR_CFG_WR_SCK_SEL_SHIFT 0 -#define QSPI_WR_CFG_WR_SCK_SEL_MASK (0x3 << QSPI_WR_CFG_WR_SCK_SEL_SHIFT) +#define QSPI_WR_CFG_WR_SCK_SEL_MASK (0x7 << QSPI_WR_CFG_WR_SCK_SEL_SHIFT) #define QSPI_FLUSH_REG 0x0c #define QSPI_FLUSH_EN (0x1 << 0) @@ -93,7 +93,7 @@ #define QSPI_CMD_PORT_RW_NUM_SHIFT 3 #define QSPI_CMD_PORT_RW_NUM_MASK (0x7 << QSPI_CMD_PORT_RW_NUM_SHIFT) #define QSPI_CMD_PORT_SCK_SEL_SHIFT 0 -#define QSPI_CMD_PORT_SCK_SEL_MASK (0x3 << QSPI_CMD_PORT_SCK_SEL_SHIFT) +#define QSPI_CMD_PORT_SCK_SEL_MASK (0x7 << QSPI_CMD_PORT_SCK_SEL_SHIFT) #define QSPI_ADDR_PORT_REG 0x14 #define QSPI_HD_PORT_REG 0x18 From 3674931621b22397f51e6da94a957516d159baef Mon Sep 17 00:00:00 2001 From: yuanxia Date: Mon, 13 May 2024 18:19:45 +0800 Subject: [PATCH 008/101] arm64: spi: Phytium: Adapt SPI driver to use DDMA interface Code has been modified and added in the SPI driver section to adapt to the DDMA controller. Signed-off-by: yuanxia Signed-off-by: Liu Tianyu Signed-off-by: Wang Hanmo Signed-off-by: Wang Yinfeng Link: https://github.com/deepin-community/kernel/pull/136 [ Wentao Guan: by e289df82344f ("spi: Rework per message DMA mapped flag to be per transfer") ] (cherry picked from commit de1e289ec4840bfb970616654cc1768599b78cf7) Signed-off-by: Wentao Guan --- drivers/spi/Makefile | 1 + drivers/spi/spi-phytium-dma.c | 553 +++++++++++++++++++++++++++++++++ drivers/spi/spi-phytium-plat.c | 11 +- drivers/spi/spi-phytium.c | 186 +++++------ drivers/spi/spi-phytium.h | 130 +++++++- 5 files changed, 772 insertions(+), 109 deletions(-) create mode 100644 drivers/spi/spi-phytium-dma.c diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 5b276edcf57f4..26cea07a87368 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -116,6 +116,7 @@ obj-$(CONFIG_SPI_PHYTIUM) += spi-phytium.o obj-$(CONFIG_SPI_PHYTIUM_PLAT) += spi-phytium-plat.o obj-$(CONFIG_SPI_PHYTIUM_PCI) += spi-phytium-pci.o obj-$(CONFIG_SPI_PHYTIUM_QSPI) += spi-phytium-qspi.o +obj-$(CONFIG_SPI_PHYTIUM) += spi-phytium-dma.o obj-$(CONFIG_SPI_PIC32) += spi-pic32.o obj-$(CONFIG_SPI_PIC32_SQI) += spi-pic32-sqi.o obj-$(CONFIG_SPI_PL022) += spi-pl022.o diff --git a/drivers/spi/spi-phytium-dma.c b/drivers/spi/spi-phytium-dma.c new file mode 100644 index 0000000000000..4387dadcd8505 --- /dev/null +++ b/drivers/spi/spi-phytium-dma.c @@ -0,0 +1,553 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Special handling for phytium DMA core + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd.. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "spi-phytium.h" + +#define RX_BUSY 0 +#define RX_BURST_LEVEL 16 +#define TX_BUSY 1 +#define TX_BURST_LEVEL 16 + +#define DMA_MAX_BUF_SIZE 4096 + +static void phytium_spi_dma_maxburst_init(struct phytium_spi *fts) +{ + struct dma_slave_caps caps; + u32 max_burst, def_burst; + int ret; + + def_burst = fts->fifo_len / 2; + + ret = dma_get_slave_caps(fts->rxchan, &caps); + if (!ret && caps.max_burst) + max_burst = caps.max_burst; + else + max_burst = RX_BURST_LEVEL; + + fts->rxburst = min(max_burst, def_burst); + phytium_writel(fts, DMARDLR, 0x0); + + ret = dma_get_slave_caps(fts->txchan, &caps); + if (!ret && caps.max_burst) + max_burst = caps.max_burst; + else + max_burst = TX_BURST_LEVEL; + + /* + * Having a Rx DMA channel serviced with higher priority than a Tx DMA + * channel might not be enough to provide a well balanced DMA-based + * SPI transfer interface. There might still be moments when the Tx DMA + * channel is occasionally handled faster than the Rx DMA channel. + * That in its turn will eventually cause the SPI Rx FIFO overflow if + * SPI bus speed is high enough to fill the SPI Rx FIFO in before it's + * cleared by the Rx DMA channel. In order to fix the problem the Tx + * DMA activity is intentionally slowed down by limiting the SPI Tx + * FIFO depth with a value twice bigger than the Tx burst length. + */ + fts->txburst = min(max_burst, def_burst); + /* set dmatdlr to 0 + 1 */ + phytium_writel(fts, DMATDLR, 0); +} + +static int phytium_spi_dma_init(struct device *dev, + struct phytium_spi *fts) +{ + fts->rxchan = dma_request_chan(dev, "rx"); + if (IS_ERR_OR_NULL(fts->rxchan)) + return -ENODEV; + + fts->txchan = dma_request_chan(dev, "tx"); + if (IS_ERR_OR_NULL(fts->txchan)) { + dev_err(dev, "can't request chan\n"); + dma_release_channel(fts->rxchan); + fts->rxchan = NULL; + return -ENODEV; + } + + fts->master->dma_rx = fts->rxchan; + fts->master->dma_tx = fts->txchan; + init_completion(&fts->dma_completion); + + phytium_spi_dma_maxburst_init(fts); + fts->dma_sg_burst = 0; + + return 0; +} + +static void phytium_spi_dma_exit(struct phytium_spi *fts) +{ + if (fts->txchan) { + dmaengine_terminate_sync(fts->txchan); + dma_release_channel(fts->txchan); + } + + if (fts->rxchan) { + dmaengine_terminate_sync(fts->rxchan); + dma_release_channel(fts->rxchan); + } +} + +static irqreturn_t phytium_spi_dma_transfer_handler +(struct phytium_spi *fts) +{ + phytium_spi_check_status(fts, false); + + complete(&fts->dma_completion); + + return IRQ_HANDLED; +} + +static bool phytium_spi_can_dma(struct spi_controller *master, + struct spi_device *spi, struct spi_transfer *xfer) +{ + struct phytium_spi *fts = spi_controller_get_devdata(master); + + return xfer->len > fts->fifo_len; +} + +static enum dma_slave_buswidth phytium_spi_dma_convert_width(u8 n_bytes) +{ + if (n_bytes == 1) + return DMA_SLAVE_BUSWIDTH_1_BYTE; + else if (n_bytes == 2) + return DMA_SLAVE_BUSWIDTH_2_BYTES; + + return DMA_SLAVE_BUSWIDTH_UNDEFINED; +} + +static int phytium_spi_dma_wait(struct phytium_spi *fts, unsigned int len, + u32 speed) +{ + unsigned long long ms; + + ms = len * MSEC_PER_SEC * BITS_PER_BYTE; + do_div(ms, speed); + ms += ms + 200; + + if (ms > UINT_MAX) + ms = UINT_MAX; + + ms = wait_for_completion_timeout(&fts->dma_completion, + msecs_to_jiffies(ms)); + + if (ms == 0) { + dev_err(&fts->master->cur_msg->spi->dev, + "DMA transaction timed out\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static inline bool phytium_spi_dma_tx_busy(struct phytium_spi *fts) +{ + return !(phytium_readl(fts, SR) & SR_TF_EMPT); +} + +static void spi_transfer_delay_ns(u32 ns) +{ + if (!ns) + return; + if (ns <= 1000) { + ndelay(ns); + } else { + u32 us = DIV_ROUND_UP(ns, 1000); + + if (us <= 10) + udelay(us); + else + usleep_range(us, us + DIV_ROUND_UP(us, 10)); + } +} + +static int phytium_spi_dma_wait_tx_done(struct phytium_spi *fts, + struct spi_transfer *xfer) +{ + int retry = SPI_WAIT_RETRIES; + u32 ns = 0; + u32 nents = 0; + + nents = phytium_readl(fts, TXFLR); + ns = nents * fts->n_bytes * BITS_PER_BYTE; + ns *= DIV_ROUND_UP(1000000000, xfer->speed_hz / 2); + + while (phytium_spi_dma_tx_busy(fts) && retry--) + spi_transfer_delay_ns(ns); + + if (retry < 0) { + dev_err(&fts->master->dev, "Tx hanged up\n"); + return -EIO; + } + + return 0; +} + +/* + * fts->dma_chan_busy is set before the dma transfer starts, + * callback for tx + * channel will clear a corresponding bit. + */ +static void phytium_spi_dma_tx_done(void *arg) +{ + struct phytium_spi *fts = arg; + + clear_bit(TX_BUSY, &fts->dma_chan_busy); + if (test_bit(RX_BUSY, &fts->dma_chan_busy)) + return; + + complete(&fts->dma_completion); +} + +static int phytium_spi_dma_config_tx(struct phytium_spi *fts) +{ + struct dma_slave_config txconf; + + memset(&txconf, 0, sizeof(txconf)); + txconf.direction = DMA_MEM_TO_DEV; + txconf.dst_addr = fts->dma_addr; + txconf.dst_maxburst = fts->txburst; + txconf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + txconf.dst_addr_width = phytium_spi_dma_convert_width(fts->n_bytes); + txconf.device_fc = false; + + return dmaengine_slave_config(fts->txchan, &txconf); +} + +static int phytium_spi_dma_submit_tx(struct phytium_spi *fts, + struct scatterlist *sgl, unsigned int nents) +{ + struct dma_async_tx_descriptor *txdesc; + dma_cookie_t cookie; + int ret; + + txdesc = dmaengine_prep_slave_sg(fts->txchan, sgl, nents, + DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!txdesc) + return -ENOMEM; + + txdesc->callback = phytium_spi_dma_tx_done; + txdesc->callback_param = fts; + + cookie = dmaengine_submit(txdesc); + ret = dma_submit_error(cookie); + if (ret) { + dmaengine_terminate_sync(fts->txchan); + return ret; + } + + set_bit(TX_BUSY, &fts->dma_chan_busy); + + return 0; +} + +static inline bool phytium_spi_dma_rx_busy(struct phytium_spi *fts) +{ + return !!(phytium_readl(fts, SR) & SR_RF_NOT_EMPT); +} + +static int phytium_spi_dma_wait_rx_done(struct phytium_spi *fts) +{ + int retry = SPI_WAIT_RETRIES; + unsigned long ns = 0; + u32 nents = 0; + + /* + * It's unlikely that DMA engine is still doing the data fetching, but + * if it's let's give it some reasonable time. The timeout calculation + * is based on the synchronous APB/SSI reference clock rate, on a + * number of data entries left in the Rx FIFO, times a number of clock + * periods normally needed for a single APB read/write transaction + * without PREADY signal utilized (which is true for the phytium APB SSI + * controller). + */ + nents = phytium_readl(fts, RXFLR); + ns = 4U * NSEC_PER_SEC / fts->max_freq * nents; + + while (phytium_spi_dma_rx_busy(fts) && retry--) + spi_transfer_delay_ns(ns); + + if (retry < 0) { + dev_err(&fts->master->dev, "Rx hanged up, nents = %d\n", nents); + return -EIO; + } + + return 0; +} + +/* + * fts->dma_chan_busy is set before the dma transfer starts, + * callback for rx + * channel will clear a corresponding bit. + */ +static void phytium_spi_dma_rx_done(void *arg) +{ + struct phytium_spi *fts = arg; + + clear_bit(RX_BUSY, &fts->dma_chan_busy); + if (test_bit(TX_BUSY, &fts->dma_chan_busy)) + return; + + complete(&fts->dma_completion); +} + +static int phytium_spi_dma_config_rx(struct phytium_spi *fts) +{ + struct dma_slave_config rxconf; + + memset(&rxconf, 0, sizeof(rxconf)); + rxconf.direction = DMA_DEV_TO_MEM; + rxconf.src_addr = fts->dma_addr; + rxconf.src_maxburst = fts->rxburst; + rxconf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + rxconf.src_addr_width = phytium_spi_dma_convert_width(fts->n_bytes); + rxconf.device_fc = false; + + return dmaengine_slave_config(fts->rxchan, &rxconf); +} + +static int phytium_spi_dma_submit_rx(struct phytium_spi *fts, + struct scatterlist *sgl, unsigned int nents) +{ + struct dma_async_tx_descriptor *rxdesc; + dma_cookie_t cookie; + int ret; + + rxdesc = dmaengine_prep_slave_sg(fts->rxchan, sgl, nents, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!rxdesc) + return -ENOMEM; + + rxdesc->callback = phytium_spi_dma_rx_done; + rxdesc->callback_param = fts; + + cookie = dmaengine_submit(rxdesc); + ret = dma_submit_error(cookie); + if (ret) { + dmaengine_terminate_sync(fts->rxchan); + return ret; + } + + set_bit(RX_BUSY, &fts->dma_chan_busy); + + return 0; +} + +static int phytium_spi_dma_setup(struct phytium_spi *fts, + struct spi_transfer *xfer) +{ + u16 imr, dma_ctrl; + int ret; + + if (!xfer->tx_buf) + return -EINVAL; + + /* Setup DMA channels */ + ret = phytium_spi_dma_config_tx(fts); + if (ret) + return ret; + + if (xfer->rx_buf) { + ret = phytium_spi_dma_config_rx(fts); + if (ret) + return ret; + } + + /* Set the DMA handshaking interface */ + dma_ctrl = SPI_DMA_TDMAE; + if (xfer->rx_buf) + dma_ctrl |= SPI_DMA_RDMAE; + phytium_writel(fts, DMACR, dma_ctrl); + + /* Set the interrupt mask */ + imr = INT_TXOI; + if (xfer->rx_buf) + imr |= INT_RXUI | INT_RXOI; + + spi_umask_intr(fts, imr); + + reinit_completion(&fts->dma_completion); + + fts->transfer_handler = phytium_spi_dma_transfer_handler; + + return 0; +} + +static int phytium_spi_dma_transfer_all(struct phytium_spi *fts, + struct spi_transfer *xfer) +{ + int ret; + + /* Submit the DMA Tx transfer */ + ret = phytium_spi_dma_submit_tx(fts, xfer->tx_sg.sgl, + xfer->tx_sg.nents); + if (ret) + goto err_clear_dmac; + + /* Submit the DMA Rx transfer if required */ + if (xfer->rx_buf) { + ret = phytium_spi_dma_submit_rx(fts, xfer->rx_sg.sgl, + xfer->rx_sg.nents); + if (ret) + goto err_clear_dmac; + + /* rx must be started before tx due to spi instinct */ + dma_async_issue_pending(fts->rxchan); + } + + dma_async_issue_pending(fts->txchan); + + ret = phytium_spi_dma_wait(fts, xfer->len, xfer->speed_hz); + +err_clear_dmac: + phytium_writel(fts, DMACR, 0); + + return ret; +} + +static int phytium_spi_dma_transfer_one(struct phytium_spi *fts, + struct spi_transfer *xfer) +{ + struct scatterlist *tx_sg = NULL, *rx_sg = NULL, tx_tmp, rx_tmp; + unsigned int tx_len = 0, rx_len = 0; + unsigned int base, len; + int ret; + + sg_init_table(&tx_tmp, 1); + sg_init_table(&rx_tmp, 1); + + for (base = 0, len = 0; base < xfer->len; base += len) { + /* Fetch next Tx DMA data chunk */ + if (!tx_len) { + tx_sg = !tx_sg ? &xfer->tx_sg.sgl[0] : sg_next(tx_sg); + sg_dma_address(&tx_tmp) = sg_dma_address(tx_sg); + tx_len = sg_dma_len(tx_sg); + } + + /* Fetch next Rx DMA data chunk */ + if (!rx_len) { + rx_sg = !rx_sg ? &xfer->rx_sg.sgl[0] : sg_next(rx_sg); + sg_dma_address(&rx_tmp) = sg_dma_address(rx_sg); + rx_len = sg_dma_len(rx_sg); + } + + if ((base + DMA_MAX_BUF_SIZE) > xfer->len) + len = xfer->len - base; + else + len = DMA_MAX_BUF_SIZE; + + len = min3(len, tx_len, rx_len); + + sg_dma_len(&tx_tmp) = len; + sg_dma_len(&rx_tmp) = len; + + /* Submit DMA Tx transfer */ + ret = phytium_spi_dma_submit_tx(fts, &tx_tmp, 1); + if (ret) + break; + + /* Submit DMA Rx transfer */ + ret = phytium_spi_dma_submit_rx(fts, &rx_tmp, 1); + if (ret) + break; + + /* Rx must be started before Tx due to SPI instinct */ + dma_async_issue_pending(fts->rxchan); + + dma_async_issue_pending(fts->txchan); + + /* + * Here we only need to wait for the DMA transfer to be + * finished since SPI controller is kept enabled during the + * procedure this loop implements and there is no risk to lose + * data left in the Tx/Rx FIFOs. + */ + ret = phytium_spi_dma_wait(fts, len, xfer->speed_hz); + if (ret) + break; + + reinit_completion(&fts->dma_completion); + + sg_dma_address(&tx_tmp) += len; + sg_dma_address(&rx_tmp) += len; + tx_len -= len; + rx_len -= len; + } + + phytium_writel(fts, DMACR, 0); + + return ret; +} + +static int phytium_spi_dma_transfer(struct phytium_spi *fts, + struct spi_transfer *xfer) +{ + unsigned int nents; + int ret; + + nents = max(xfer->tx_sg.nents, xfer->rx_sg.nents); + + /* + * large transfer length caused spi RX FIFO full event + * transfer 4096 bytes each time + */ + if (xfer->len <= DMA_MAX_BUF_SIZE) + ret = phytium_spi_dma_transfer_all(fts, xfer); + else + ret = phytium_spi_dma_transfer_one(fts, xfer); + if (ret) + return ret; + + if (fts->master->cur_msg->status == -EINPROGRESS) { + ret = phytium_spi_dma_wait_tx_done(fts, xfer); + if (ret) + return ret; + } + + if (xfer->rx_buf && fts->master->cur_msg->status == -EINPROGRESS) + ret = phytium_spi_dma_wait_rx_done(fts); + + return ret; +} + +static void phytium_spi_dma_stop(struct phytium_spi *fts) +{ + if (test_bit(TX_BUSY, &fts->dma_chan_busy)) { + dmaengine_terminate_sync(fts->txchan); + clear_bit(TX_BUSY, &fts->dma_chan_busy); + } + if (test_bit(RX_BUSY, &fts->dma_chan_busy)) { + dmaengine_terminate_sync(fts->rxchan); + clear_bit(RX_BUSY, &fts->dma_chan_busy); + } +} + +static const struct phytium_spi_dma_ops phytium_spi_dma_generic_ops = { + .dma_init = phytium_spi_dma_init, + .dma_exit = phytium_spi_dma_exit, + .dma_setup = phytium_spi_dma_setup, + .can_dma = phytium_spi_can_dma, + .dma_transfer = phytium_spi_dma_transfer, + .dma_stop = phytium_spi_dma_stop, +}; + +void phytium_spi_dmaops_set(struct phytium_spi *fts) +{ + fts->dma_ops = &phytium_spi_dma_generic_ops; +} +EXPORT_SYMBOL_GPL(phytium_spi_dmaops_set); + +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-phytium-plat.c b/drivers/spi/spi-phytium-plat.c index 2c90511d605c3..eb2b3d837693b 100644 --- a/drivers/spi/spi-phytium-plat.c +++ b/drivers/spi/spi-phytium-plat.c @@ -56,6 +56,7 @@ static int phytium_spi_probe(struct platform_device *pdev) return -EINVAL; } + fts->paddr = mem->start; fts->regs = devm_ioremap_resource(&pdev->dev, mem); if (IS_ERR(fts->regs)) { dev_err(&pdev->dev, "SPI region map failed\n"); @@ -86,14 +87,20 @@ static int phytium_spi_probe(struct platform_device *pdev) device_property_read_u32(&pdev->dev, "reg-io-width", &fts->reg_io_width); num_cs = 4; - device_property_read_u32(&pdev->dev, "num-cs", &num_cs); - fts->num_cs = num_cs; device_property_read_u32(&pdev->dev, "global-cs", &global_cs); fts->global_cs = global_cs; + /* check is use dma transfer */ + if ((device_property_read_string_array(&pdev->dev, "dma-names", + NULL, 0) > 0) && + device_property_present(&pdev->dev, "dmas")) { + fts->dma_en = true; + phytium_spi_dmaops_set(fts); + } + ret = phytium_spi_add_host(&pdev->dev, fts); if (ret) goto out; diff --git a/drivers/spi/spi-phytium.c b/drivers/spi/spi-phytium.c index eaacb729031cf..6b9a2351c545b 100644 --- a/drivers/spi/spi-phytium.c +++ b/drivers/spi/spi-phytium.c @@ -25,102 +25,6 @@ #include #include "spi-phytium.h" -static inline u32 phytium_readl(struct phytium_spi *fts, u32 offset) -{ - return __raw_readl(fts->regs + offset); -} - -static inline u16 phytium_readw(struct phytium_spi *fts, u32 offset) -{ - return __raw_readw(fts->regs + offset); -} - -static inline void phytium_writel(struct phytium_spi *fts, u32 offset, u32 val) -{ - __raw_writel(val, fts->regs + offset); -} - -static inline void phytium_writew(struct phytium_spi *fts, u32 offset, u16 val) -{ - __raw_writew(val, fts->regs + offset); -} - -static inline u32 phytium_read_io_reg(struct phytium_spi *fts, u32 offset) -{ - switch (fts->reg_io_width) { - case 2: - return phytium_readw(fts, offset); - case 4: - default: - return phytium_readl(fts, offset); - } -} - -static inline void phytium_write_io_reg(struct phytium_spi *fts, u32 offset, u32 val) -{ - switch (fts->reg_io_width) { - case 2: - phytium_writew(fts, offset, val); - break; - case 4: - default: - phytium_writel(fts, offset, val); - break; - } -} - -static inline void spi_enable_chip(struct phytium_spi *fts, int enable) -{ - phytium_writel(fts, SSIENR, (enable ? 1 : 0)); -} - -static inline void spi_set_clk(struct phytium_spi *fts, u16 div) -{ - phytium_writel(fts, BAUDR, div); -} - -static inline void spi_mask_intr(struct phytium_spi *fts, u32 mask) -{ - u32 new_mask; - - new_mask = phytium_readl(fts, IMR) & ~mask; - phytium_writel(fts, IMR, new_mask); -} - -static inline void spi_umask_intr(struct phytium_spi *fts, u32 mask) -{ - u32 new_mask; - - new_mask = phytium_readl(fts, IMR) | mask; - phytium_writel(fts, IMR, new_mask); -} - -static inline void spi_global_cs(struct phytium_spi *fts) -{ - u32 global_cs_en, mask, setmask; - - mask = GENMASK(fts->num_cs-1, 0) << fts->num_cs; - setmask = ~GENMASK(fts->num_cs-1, 0); - global_cs_en = (phytium_readl(fts, GCSR) | mask) & setmask; - - phytium_writel(fts, GCSR, global_cs_en); -} - -static inline void spi_reset_chip(struct phytium_spi *fts) -{ - spi_enable_chip(fts, 0); - if (fts->global_cs) - spi_global_cs(fts); - spi_mask_intr(fts, 0xff); - spi_enable_chip(fts, 1); -} - -static inline void spi_shutdown_chip(struct phytium_spi *fts) -{ - spi_enable_chip(fts, 0); - spi_set_clk(fts, 0); -} - struct phytium_spi_chip { u8 poll_mode; u8 type; @@ -215,6 +119,40 @@ static void phytium_reader(struct phytium_spi *fts) fts->rx += fts->n_bytes; } } +int phytium_spi_check_status(struct phytium_spi *fts, bool raw) +{ + u32 irq_status; + int ret = 0; + + if (raw) + irq_status = phytium_readl(fts, RISR); + else + irq_status = phytium_readl(fts, ISR); + + if (irq_status & INT_RXOI) { + dev_err(&fts->master->dev, "RX FIFO overflow detected\n"); + ret = -EIO; + } + + if (irq_status & INT_RXUI) { + dev_err(&fts->master->dev, "RX FIFO underflow detected\n"); + ret = -EIO; + } + + if (irq_status & INT_TXOI) { + dev_err(&fts->master->dev, "TX FIFO overflow detected\n"); + ret = -EIO; + } + + /* Generically handle the erroneous situation */ + if (ret) { + spi_reset_chip(fts); + if (fts->master->cur_msg) + fts->master->cur_msg->status = ret; + } + return ret; +} +EXPORT_SYMBOL_GPL(phytium_spi_check_status); static void int_error_stop(struct phytium_spi *fts, const char *msg) { @@ -290,7 +228,9 @@ static int phytium_spi_transfer_one(struct spi_controller *master, u16 txlevel = 0; u16 clk_div; u32 cr0; + int ret = 0; + fts->dma_mapped = 0; fts->tx = (void *)transfer->tx_buf; fts->tx_end = fts->tx + transfer->len; fts->rx = transfer->rx_buf; @@ -299,12 +239,15 @@ static int phytium_spi_transfer_one(struct spi_controller *master, spi_enable_chip(fts, 0); - if (transfer->speed_hz != chip->speed_hz) { - clk_div = (fts->max_freq / transfer->speed_hz + 1) & 0xfffe; - - chip->speed_hz = transfer->speed_hz; - chip->clk_div = clk_div; + if (transfer->speed_hz != fts->current_freq) { + if (transfer->speed_hz != chip->speed_hz) { + clk_div = (fts->max_freq / transfer->speed_hz + 1) & + 0xfffe; + chip->speed_hz = transfer->speed_hz; + chip->clk_div = clk_div; + } + fts->current_freq = transfer->speed_hz; spi_set_clk(fts, chip->clk_div); } @@ -332,11 +275,22 @@ static int phytium_spi_transfer_one(struct spi_controller *master, cr0 |= (chip->tmode << TMOD_OFFSET); } - phytium_writel(fts, CTRL0, cr0); + phytium_writel(fts, CTRLR0, cr0); + + /* check if current transfer is a DMA transcation */ + if (master->can_dma && master->can_dma(master, spi, transfer)) + fts->dma_mapped = (transfer->tx_sg_mapped || transfer->rx_sg_mapped); spi_mask_intr(fts, 0xff); - if (!chip->poll_mode) { + /* DMA setup */ + if (fts->dma_mapped) { + ret = fts->dma_ops->dma_setup(fts, transfer); + if (ret) + return ret; + } + /* interrupt transfer mode setup */ + if (!chip->poll_mode && !fts->dma_mapped) { txlevel = min_t(u16, fts->fifo_len / 2, fts->len / fts->n_bytes); phytium_writel(fts, TXFLTR, txlevel); @@ -349,6 +303,9 @@ static int phytium_spi_transfer_one(struct spi_controller *master, spi_enable_chip(fts, 1); + if (fts->dma_mapped) + return fts->dma_ops->dma_transfer(fts, transfer); + if (chip->poll_mode) return poll_transfer(fts); @@ -360,6 +317,9 @@ static void phytium_spi_handle_err(struct spi_controller *master, { struct phytium_spi *fts = spi_controller_get_devdata(master); + if (fts->dma_mapped) + fts->dma_ops->dma_stop(fts); + spi_reset_chip(fts); } @@ -396,7 +356,7 @@ static int phytium_spi_setup(struct spi_device *spi) cr0 = (spi->bits_per_word - 1) | (chip->type << FRF_OFFSET) | (spi->mode << MODE_OFFSET) | (chip->tmode << TMOD_OFFSET); - phytium_writel(fts, CTRL0, cr0); + phytium_writel(fts, CTRLR0, cr0); spi_enable_chip(fts, 1); @@ -442,9 +402,11 @@ int phytium_spi_add_host(struct device *dev, struct phytium_spi *fts) return -ENOMEM; fts->master = master; + fts->dma_addr = (dma_addr_t)(fts->paddr + DR); snprintf(fts->name, sizeof(fts->name), "phytium_spi%d", fts->bus_num); - ret = request_irq(fts->irq, phytium_spi_irq, IRQF_SHARED, fts->name, master); + ret = request_irq(fts->irq, phytium_spi_irq, IRQF_SHARED, + fts->name, master); if (ret < 0) { dev_err(dev, "can not get IRQ\n"); goto err_free_master; @@ -467,6 +429,16 @@ int phytium_spi_add_host(struct device *dev, struct phytium_spi *fts) spi_hw_init(dev, fts); + if (fts->dma_ops && fts->dma_ops->dma_init) { + ret = fts->dma_ops->dma_init(dev, fts); + if (ret) { + dev_warn(dev, "DMA init failed\n"); + } else { + master->can_dma = fts->dma_ops->can_dma; + master->flags |= SPI_CONTROLLER_MUST_TX; + } + } + spi_controller_set_devdata(master, fts); ret = spi_register_controller(master); if (ret) { @@ -477,6 +449,8 @@ int phytium_spi_add_host(struct device *dev, struct phytium_spi *fts) return 0; err_exit: + if (fts->dma_ops && fts->dma_ops->dma_exit) + fts->dma_ops->dma_exit(fts); spi_enable_chip(fts, 0); free_irq(fts->irq, master); err_free_master: @@ -487,6 +461,8 @@ EXPORT_SYMBOL_GPL(phytium_spi_add_host); void phytium_spi_remove_host(struct phytium_spi *fts) { + if (fts->dma_ops && fts->dma_ops->dma_exit) + fts->dma_ops->dma_exit(fts); spi_shutdown_chip(fts); spi_unregister_controller(fts->master); diff --git a/drivers/spi/spi-phytium.h b/drivers/spi/spi-phytium.h index 02aa159ab3961..f9530e4c99922 100644 --- a/drivers/spi/spi-phytium.h +++ b/drivers/spi/spi-phytium.h @@ -11,16 +11,21 @@ #include #include -#define CTRL0 0x00 +#define CTRLR0 0x00 #define SSIENR 0x08 #define SER 0x10 #define BAUDR 0x14 #define TXFLTR 0x18 #define TXFLR 0x20 #define RXFLR 0x24 +#define SR 0x28 #define IMR 0x2c #define ISR 0x30 +#define RISR 0x34 #define ICR 0x48 +#define DMACR 0x4C +#define DMATDLR 0x50 +#define DMARDLR 0x54 #define DR 0x60 #define GCSR 0x100 @@ -37,6 +42,29 @@ #define INT_TXOI (1 << 1) #define INT_RXUI (1 << 2) #define INT_RXOI (1 << 3) +/* Bit fields in SR, 7 bits */ +#define SR_MASK 0x7f /* cover 7 bits */ +#define SR_BUSY (1 << 0) +#define SR_TF_NOT_FULL (1 << 1) +#define SR_TF_EMPT (1 << 2) +#define SR_RF_NOT_EMPT (1 << 3) +#define SR_RF_FULL (1 << 4) +#define SR_TX_ERR (1 << 5) +#define SR_DCOL (1 << 6) +/* Bit fields in DMACR */ +#define SPI_DMA_RDMAE (1 << 0) +#define SPI_DMA_TDMAE (1 << 1) +#define SPI_WAIT_RETRIES 5 +struct phytium_spi; +struct phytium_spi_dma_ops { + int (*dma_init)(struct device *dev, struct phytium_spi *fts); + void (*dma_exit)(struct phytium_spi *fts); + int (*dma_setup)(struct phytium_spi *fts, struct spi_transfer *xfer); + bool (*can_dma)(struct spi_controller *master, struct spi_device *spi, + struct spi_transfer *xfer); + int (*dma_transfer)(struct phytium_spi *fts, struct spi_transfer *xfer); + void (*dma_stop)(struct phytium_spi *fts); +}; struct phytium_spi { struct spi_controller *master; @@ -44,6 +72,7 @@ struct phytium_spi { void __iomem *regs; bool global_cs; + bool dma_en; unsigned long paddr; int irq; u32 fifo_len; @@ -60,12 +89,109 @@ struct phytium_spi { void *rx; void *rx_end; u8 n_bytes; + int dma_mapped; irqreturn_t (*transfer_handler)(struct phytium_spi *fts); + /* DMA info */ + u32 current_freq; /* frequency in hz */ + struct dma_chan *txchan; + u32 txburst; + struct dma_chan *rxchan; + u32 rxburst; + u32 dma_sg_burst; + unsigned long dma_chan_busy; + dma_addr_t dma_addr; /* phy address of the Data register */ + const struct phytium_spi_dma_ops *dma_ops; + struct completion dma_completion; }; +static inline u32 phytium_readl(struct phytium_spi *fts, u32 offset) +{ + return __raw_readl(fts->regs + offset); +} +static inline u16 phytium_readw(struct phytium_spi *fts, u32 offset) +{ + return __raw_readw(fts->regs + offset); +} +static inline void phytium_writel(struct phytium_spi *fts, u32 offset, u32 val) +{ + __raw_writel(val, fts->regs + offset); +} +static inline void phytium_writew(struct phytium_spi *fts, u32 offset, u16 val) +{ + __raw_writew(val, fts->regs + offset); +} +static inline u32 phytium_read_io_reg(struct phytium_spi *fts, u32 offset) +{ + switch (fts->reg_io_width) { + case 2: + return phytium_readw(fts, offset); + case 4: + default: + return phytium_readl(fts, offset); + } +} +static inline void phytium_write_io_reg(struct phytium_spi *fts, + u32 offset, u32 val) +{ + switch (fts->reg_io_width) { + case 2: + phytium_writew(fts, offset, val); + break; + case 4: + default: + phytium_writel(fts, offset, val); + break; + } +} +static inline void spi_enable_chip(struct phytium_spi *fts, int enable) +{ + phytium_writel(fts, SSIENR, (enable ? 1 : 0)); +} +static inline void spi_set_clk(struct phytium_spi *fts, u16 div) +{ + phytium_writel(fts, BAUDR, div); +} +static inline void spi_mask_intr(struct phytium_spi *fts, u32 mask) +{ + u32 new_mask; + + new_mask = phytium_readl(fts, IMR) & ~mask; + phytium_writel(fts, IMR, new_mask); +} +static inline void spi_umask_intr(struct phytium_spi *fts, u32 mask) +{ + u32 new_mask; + + new_mask = phytium_readl(fts, IMR) | mask; + phytium_writel(fts, IMR, new_mask); +} +static inline void spi_global_cs(struct phytium_spi *fts) +{ + u32 global_cs_en, mask, setmask; + + mask = GENMASK(fts->num_cs-1, 0) << fts->num_cs; + setmask = ~GENMASK(fts->num_cs-1, 0); + global_cs_en = (phytium_readl(fts, GCSR) | mask) & setmask; + phytium_writel(fts, GCSR, global_cs_en); +} +static inline void spi_reset_chip(struct phytium_spi *fts) +{ + spi_enable_chip(fts, 0); + if (fts->global_cs) + spi_global_cs(fts); + spi_mask_intr(fts, 0xff); + spi_enable_chip(fts, 1); +} +static inline void spi_shutdown_chip(struct phytium_spi *fts) +{ + spi_enable_chip(fts, 0); + spi_set_clk(fts, 0); + fts->current_freq = 0; +} extern int phytium_spi_add_host(struct device *dev, struct phytium_spi *fts); extern void phytium_spi_remove_host(struct phytium_spi *fts); extern int phytium_spi_suspend_host(struct phytium_spi *fts); extern int phytium_spi_resume_host(struct phytium_spi *fts); - +extern void phytium_spi_dmaops_set(struct phytium_spi *fts); +extern int phytium_spi_check_status(struct phytium_spi *fts, bool raw); #endif /* PHYTIUM_SPI_HEADER_H */ From 7f4fb2e5cdafa46c0025b7fdd4f42181af9b3e2a Mon Sep 17 00:00:00 2001 From: yuanxia Date: Mon, 13 May 2024 18:21:03 +0800 Subject: [PATCH 009/101] arm64: phytium: UEFI mode acpi table support for qspi/spi driver Add acpi table support for qspi/spi driver, supporting parsing of ACPI tables. Signed-off-by: yuanxia Signed-off-by: Wang Hanmo Signed-off-by: Wang Yinfeng Link: https://github.com/deepin-community/kernel/pull/136 (cherry picked from commit 8b02928a7e2ad613e10234991060256d5995cd55) Signed-off-by: Wentao Guan Conflicts: drivers/mtd/spi-nor/core.c --- drivers/mtd/mtdpart.c | 44 ++++++++- drivers/mtd/parsers/Kconfig | 8 ++ drivers/mtd/parsers/Makefile | 2 + drivers/mtd/parsers/acpipart_core.c | 143 ++++++++++++++++++++++++++++ drivers/mtd/spi-nor/core.c | 13 +++ drivers/spi/Kconfig | 9 ++ drivers/spi/spi-phytium-qspi.c | 77 ++++++++++----- include/linux/mtd/partitions.h | 4 + 8 files changed, 275 insertions(+), 25 deletions(-) create mode 100644 drivers/mtd/parsers/acpipart_core.c diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index 4b41550fd374e..a0064940cc682 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -20,6 +20,8 @@ #include #include +#include +#include #include "mtdcore.h" /* @@ -525,12 +527,14 @@ EXPORT_SYMBOL_GPL(deregister_mtd_parser); static const char * const default_mtd_part_types[] = { "cmdlinepart", "ofpart", + "acpipart", NULL }; /* Check DT only when looking for subpartitions. */ static const char * const default_subpartition_types[] = { "ofpart", + "acpipart", NULL }; @@ -594,6 +598,42 @@ static struct mtd_part_parser *mtd_part_get_compatible_parser(const char *compat return ret; } +static int mtd_part_acpi_parse(struct mtd_info *master, + struct mtd_partitions *pparts) +{ + struct mtd_part_parser *parser; + struct fwnode_handle *child; + const char *compat; + const char *fixed = "acpi-fixed-partitions"; + int ret, err = 0; + int compare = 1; + struct device *dev = &master->dev; + + if (!mtd_is_partition(master)) { + fwnode_property_read_string(dev->fwnode, "fixed", &compat); + if (compat) + compare = strcmp(compat, fixed); + } + + //all child node + device_for_each_child_node(dev, child) { + if (compat && !compare) { + parser = mtd_part_parser_get(fixed); + if (!parser && !request_module("%s", fixed)) + parser = mtd_part_parser_get(fixed); + if (parser) { + ret = mtd_part_do_parse(parser, master, pparts, NULL); + if (ret > 0) + return ret; + mtd_part_parser_put(parser); + if (ret < 0 && !err) + err = ret; + } + } + } + return err; +} + static int mtd_part_of_parse(struct mtd_info *master, struct mtd_partitions *pparts) { @@ -699,7 +739,9 @@ int parse_mtd_partitions(struct mtd_info *master, const char *const *types, * should be used. It requires a bit different logic so it is * handled in a separated function. */ - if (!strcmp(*types, "ofpart")) { + if (!strcmp(*types, "acpipart")) { + ret = mtd_part_acpi_parse(master, &pparts); + } else if (!strcmp(*types, "ofpart")) { ret = mtd_part_of_parse(master, &pparts); } else { pr_debug("%s: parsing partitions %s\n", master->name, diff --git a/drivers/mtd/parsers/Kconfig b/drivers/mtd/parsers/Kconfig index da03ab6efe04c..77e1d1ba5e6a6 100644 --- a/drivers/mtd/parsers/Kconfig +++ b/drivers/mtd/parsers/Kconfig @@ -91,6 +91,14 @@ config MTD_OF_PARTS_LINKSYS_NS two "firmware" partitions. Currently used firmware has to be detected using CFE environment variable. +config MTD_ACPI_PARTS + tristate "ACPI partitioning parser" + depends on ACPI && (ARCH_PHYTIUM || COMPILE_TEST) + help + This provides an acpi partition parser, which is used to parse the + partition map described in ACPI table, as the children of the flash + memory struct. + config MTD_PARSER_IMAGETAG tristate "Parser for BCM963XX Image Tag format partitions" depends on BCM63XX || BMIPS_GENERIC || COMPILE_TEST diff --git a/drivers/mtd/parsers/Makefile b/drivers/mtd/parsers/Makefile index 9b00c62b837ae..5136ad62f4f42 100644 --- a/drivers/mtd/parsers/Makefile +++ b/drivers/mtd/parsers/Makefile @@ -7,6 +7,8 @@ obj-$(CONFIG_MTD_OF_PARTS) += ofpart.o ofpart-y += ofpart_core.o ofpart-$(CONFIG_MTD_OF_PARTS_BCM4908) += ofpart_bcm4908.o ofpart-$(CONFIG_MTD_OF_PARTS_LINKSYS_NS)+= ofpart_linksys_ns.o +obj-$(CONFIG_MTD_ACPI_PARTS) += acpipart.o +acpipart-y += acpipart_core.o obj-$(CONFIG_MTD_PARSER_IMAGETAG) += parser_imagetag.o obj-$(CONFIG_MTD_AFS_PARTS) += afs.o obj-$(CONFIG_MTD_PARSER_TPLINK_SAFELOADER) += tplink_safeloader.o diff --git a/drivers/mtd/parsers/acpipart_core.c b/drivers/mtd/parsers/acpipart_core.c new file mode 100644 index 0000000000000..4ed29fc5e49b7 --- /dev/null +++ b/drivers/mtd/parsers/acpipart_core.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Flash partitions described by the acpi table + * + * Author: Wang Hanmo + */ + +#include +#include +#include +#include +#include +#include +#include + +static const struct acpi_device_id parse_acpipart_match_table[]; + +static int parse_acpi_fixed_partitions(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + struct acpi_device_id *acpi_id; + const char *partname; + int nr_parts, i, ret = 0; + struct acpi_device *adev; + struct fwnode_handle *child; + struct fwnode_handle *child_handle; + bool dedicated = true; + struct device *dev; + + dev = &master->dev; + adev = ACPI_COMPANION(&master->dev); + + if (!master->parent) {/*master*/ + device_get_next_child_node(dev, child_handle); + if (!child_handle) { + pr_debug("%s: 'partitions' subnode not found on %pOF. Trying to parse direct subnodes as partitions.\n", + master->name, child_handle); + dedicated = false; + } + } + + acpi_id = acpi_match_device(parse_acpipart_match_table, dev); + if (dedicated && !acpi_id) + return 0; + + nr_parts = 0; + device_for_each_child_node(dev, child_handle) { + nr_parts++; + } + + if (nr_parts == 0) + return 0; + parts = kcalloc(nr_parts, sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + i = 0; + device_for_each_child_node(dev, child_handle) { + u64 offset, length; + bool bool_match; + + fwnode_property_read_u64(child_handle, "offset", &offset); + fwnode_property_read_u64(child_handle, "length", &length); + if (!offset && !length) { + if (dedicated) { + pr_debug("%s: acpipart partition %pOF (%pOF) missing reg property.\n", + master->name, child_handle, + dev->fwnode); + goto acpipart_fail; + } else { + nr_parts--; + continue; + } + } + + parts[i].offset = offset; + parts[i].size = length; + parts[i].fwnode = child_handle; + if (!fwnode_property_read_string(child_handle, "label", &partname)) + parts[i].name = partname; + bool_match = fwnode_property_read_bool(child_handle, "read-only"); + if (bool_match) + parts[i].mask_flags |= MTD_WRITEABLE; + bool_match = fwnode_property_read_bool(child_handle, "lock"); + if (bool_match) + parts[i].mask_flags |= MTD_POWERUP_LOCK; + bool_match = fwnode_property_read_bool(child_handle, "slc-mode"); + if (bool_match) + parts[i].mask_flags |= MTD_SLC_ON_MLC_EMULATION; + i++; + } + + if (!nr_parts) + goto acpipart_none; + + *pparts = parts; + ret = nr_parts; + return ret; + +acpipart_fail: + pr_err("%s: error parsing acpipart partition %pOF (%pOF)\n", + master->name, child_handle, dev->fwnode); + ret = -EINVAL; +acpipart_none: + kfree(parts); + return ret; +} + +static const struct acpi_device_id parse_acpipart_match_table[] = { + /* Generic */ + { "acpi-fixed-partitions", 0 }, + /* Customized */ + {}, +}; + +MODULE_DEVICE_TABLE(acpi, parse_acpipart_match_table); + +static struct mtd_part_parser acpipart_parser = { + .parse_fn = parse_acpi_fixed_partitions, + .name = "acpi-fixed-partitions", + .acpi_match_table = ACPI_PTR(parse_acpipart_match_table), +}; + +static int __init acpipart_parser_init(void) +{ + register_mtd_parser(&acpipart_parser); + return 0; +} + +static void __exit acpipart_parser_exit(void) +{ + deregister_mtd_parser(&acpipart_parser); +} + +module_init(acpipart_parser_init); +module_exit(acpipart_parser_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parser for MTD partitioning information in acpi table"); +MODULE_AUTHOR("wanghanmo "); +MODULE_ALIAS("acpi-fixed-partitions"); diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index ccf4396cdcd04..fd41f8fb9fee6 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -7,6 +7,7 @@ * Copyright (C) 2014, Freescale Semiconductor, Inc. */ +#include #include #include #include @@ -18,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -3776,6 +3778,7 @@ static int spi_nor_probe(struct spi_mem *spimem) struct device *dev = &spi->dev; struct flash_platform_data *data = dev_get_platdata(dev); struct spi_nor *nor; + struct acpi_device *adev; /* * Enable all caps by default. The core will mask them after * checking what's really supported using spi_mem_supports_op(). @@ -3795,6 +3798,10 @@ static int spi_nor_probe(struct spi_mem *spimem) nor->spimem = spimem; nor->dev = dev; spi_nor_set_flash_node(nor, dev->of_node); + adev = ACPI_COMPANION(nor->dev); + nor->mtd.dev.fwnode = spi->dev.fwnode; + + device_property_read_string(&spi->dev, "_HID", &nor->mtd.name); spi_mem_set_drvdata(spimem, nor); @@ -3933,6 +3940,11 @@ static const struct of_device_id spi_nor_of_table[] = { }; MODULE_DEVICE_TABLE(of, spi_nor_of_table); +static const struct acpi_device_id spi_nor_acpi_table[] = { + {"PHYT8009", 0}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, spi_nor_acpi_table); /* * REVISIT: many of these chips have deep power-down modes, which * should clearly be entered on suspend() to minimize power use. @@ -3944,6 +3956,7 @@ static struct spi_mem_driver spi_nor_driver = { .name = "spi-nor", .of_match_table = spi_nor_of_table, .dev_groups = spi_nor_sysfs_groups, + .acpi_match_table = spi_nor_acpi_table, }, .id_table = spi_nor_dev_ids, }, diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 52a009754c4e6..ff00d22172741 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -843,6 +843,15 @@ config SPI_ORION This enables using the SPI master controller on the Orion and MVEBU chips. +config SPI_PCI1XXXX + tristate "PCI1XXXX SPI Bus support" + depends on PCI + help + Say "yes" to Enable the SPI Bus support for the PCI1xxxx card + This is a PCI to SPI Bus driver + This driver can be built as module. If so, the module will be + called as spi-pci1xxxx. + config SPI_PHYTIUM tristate depends on ARCH_PHYTIUM || COMPILE_TEST diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index 93e6a080ee7c6..fa87ac355f697 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -5,6 +5,7 @@ * Copyright (c) 2022-2023, Phytium Technology Co., Ltd. */ +#include #include #include #include @@ -14,6 +15,7 @@ #include #include #include +#include #include #include @@ -617,6 +619,7 @@ static int phytium_qspi_probe(struct platform_device *pdev) u32 flash_cap; struct spi_mem *mem; struct spi_nor *nor; + const char **reg_name_array; ctrl = spi_alloc_host(dev, sizeof(*qspi)); if (!ctrl) @@ -627,19 +630,36 @@ static int phytium_qspi_probe(struct platform_device *pdev) SPI_TX_DUAL | SPI_TX_QUAD; ctrl->setup = phytium_qspi_setup; ctrl->num_chipselect = PHYTIUM_QSPI_MAX_NORCHIP; - ctrl->dev.of_node = dev->of_node; + if (IS_ENABLED(CONFIG_OF)) + ctrl->dev.of_node = dev->of_node; + else if (IS_ENABLED(CONFIG_ACPI) && has_acpi_companion(dev)) + ctrl->dev.fwnode = dev->fwnode; qspi = spi_controller_get_devdata(ctrl); qspi->ctrl = ctrl; - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi"); + reg_name_array = kcalloc(4, sizeof(*reg_name_array), GFP_KERNEL); + if (dev->of_node) + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi"); + else if (has_acpi_companion(dev)) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + fwnode_property_read_string_array(dev->fwnode, + "reg-names", reg_name_array, 2); + res->name = reg_name_array[0]; + } qspi->io_base = devm_ioremap_resource(dev, res); if (IS_ERR(qspi->io_base)) { ret = PTR_ERR(qspi->io_base); goto probe_master_put; } - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi_mm"); + if (dev->of_node) + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi_mm"); + else if (has_acpi_companion(dev)) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + res->name = reg_name_array[1]; + } + qspi->mm_base = devm_ioremap_resource(dev, res); if (IS_ERR(qspi->mm_base)) { ret = PTR_ERR(qspi->mm_base); @@ -653,31 +673,34 @@ static int phytium_qspi_probe(struct platform_device *pdev) } qspi->used_size = 0; - qspi->clk = devm_clk_get(dev, NULL); - if (IS_ERR(qspi->clk)) { - ret = PTR_ERR(qspi->clk); - goto probe_master_put; - } + if (dev->of_node) { + qspi->clk = devm_clk_get(dev, NULL); + if (IS_ERR(qspi->clk)) { + ret = PTR_ERR(qspi->clk); + goto probe_master_put; + } - qspi->clk_rate = clk_get_rate(qspi->clk); - if (!qspi->clk_rate) { - ret = -EINVAL; - goto probe_master_put; - } + qspi->clk_rate = clk_get_rate(qspi->clk); + if (!qspi->clk_rate) { + ret = -EINVAL; + goto probe_master_put; + } - pm_runtime_enable(dev); - ret = pm_runtime_get_sync(dev); - if (ret < 0) { - pm_runtime_put_noidle(dev); - goto probe_master_put; - } + pm_runtime_enable(dev); + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + pm_runtime_put_noidle(dev); + goto probe_master_put; + } - ret = clk_prepare_enable(qspi->clk); - if (ret) { - dev_err(dev, "Failed to enable PCLK of the controller.\n"); - goto probe_clk_failed; + ret = clk_prepare_enable(qspi->clk); + if (ret) { + dev_err(dev, "Failed to enable PCLK of the controller.\n"); + goto probe_clk_failed; + } + } else if (has_acpi_companion(dev)) { + qspi->clk_rate = 50000000; } - qspi->nodirmap = device_property_present(dev, "no-direct-mapping"); ctrl->mem_ops = qspi->nodirmap ? &phytium_qspi_mem_ops_nodirmap : @@ -781,7 +804,12 @@ static const struct of_device_id phytium_qspi_of_match[] = { { .compatible = "phytium,qspi-nor" }, { } }; +static const struct acpi_device_id phytium_qspi_acpi_match[] = { + { "PHYT0011", 0 }, + { } +}; MODULE_DEVICE_TABLE(of, phytium_qspi_of_match); +MODULE_DEVICE_TABLE(acpi, phytium_qspi_acpi_match); static struct platform_driver phytium_qspi_driver = { .probe = phytium_qspi_probe, @@ -789,6 +817,7 @@ static struct platform_driver phytium_qspi_driver = { .driver = { .name = "phytium-qspi", .of_match_table = of_match_ptr(phytium_qspi_of_match), + .acpi_match_table = ACPI_PTR(phytium_qspi_acpi_match), .pm = &phytium_qspi_pm_ops, }, }; diff --git a/include/linux/mtd/partitions.h b/include/linux/mtd/partitions.h index b74a539ec5819..73f16747d9bcc 100644 --- a/include/linux/mtd/partitions.h +++ b/include/linux/mtd/partitions.h @@ -51,6 +51,7 @@ struct mtd_partition { uint32_t mask_flags; /* master MTD flags to mask out for this partition */ uint32_t add_flags; /* flags to add to the partition */ struct device_node *of_node; + struct fwnode_handle *fwnode; }; #define MTDPART_OFS_RETAIN (-3) @@ -61,6 +62,8 @@ struct mtd_partition { struct mtd_info; struct device_node; +struct acpi_device; +struct hwnode_handle; /** * struct mtd_part_parser_data - used to pass data to MTD partition parsers. @@ -80,6 +83,7 @@ struct mtd_part_parser { struct module *owner; const char *name; const struct of_device_id *of_match_table; + const struct acpi_device_id *acpi_match_table; int (*parse_fn)(struct mtd_info *, const struct mtd_partition **, struct mtd_part_parser_data *); void (*cleanup)(const struct mtd_partition *pparts, int nr_parts); From 1d7af1319174d4fb70a19b8d92a26d2b28229804 Mon Sep 17 00:00:00 2001 From: yuanxia Date: Tue, 28 May 2024 14:40:39 +0800 Subject: [PATCH 010/101] mtd: phytium: Resolve some errors when '-Werror' was enabled This patch resolves some errors when '-Werror' was enabled Signed-off-by: yuanxia Signed-off-by: Wang Hanmo Link: https://github.com/deepin-community/kernel/pull/233 [ fixed for v6.18 ] Log: drivers/mtd/parsers/acpipart_core.c:35:35: error: variable 'child_handle' is uninitialized when used here [-Werror,-Wuninitialized] 35 | device_get_next_child_node(dev, child_handle); | ^~~~~~~~~~~~ drivers/mtd/parsers/acpipart_core.c:27:36: note: initialize the variable 'child_handle' to silence this warning 27 | struct fwnode_handle *child_handle; | ^ | = NULL 1 error generated. (cherry picked from commit c1de28d10f4873ce460cc13f04a0e3e4d154bbb1) Signed-off-by: Wentao Guan --- drivers/mtd/parsers/acpipart_core.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/mtd/parsers/acpipart_core.c b/drivers/mtd/parsers/acpipart_core.c index 4ed29fc5e49b7..501d6f54b2c79 100644 --- a/drivers/mtd/parsers/acpipart_core.c +++ b/drivers/mtd/parsers/acpipart_core.c @@ -20,12 +20,11 @@ static int parse_acpi_fixed_partitions(struct mtd_info *master, struct mtd_part_parser_data *data) { struct mtd_partition *parts; - struct acpi_device_id *acpi_id; + const struct acpi_device_id *acpi_id; const char *partname; int nr_parts, i, ret = 0; struct acpi_device *adev; - struct fwnode_handle *child; - struct fwnode_handle *child_handle; + struct fwnode_handle *child_handle = NULL; bool dedicated = true; struct device *dev; @@ -110,7 +109,7 @@ static int parse_acpi_fixed_partitions(struct mtd_info *master, static const struct acpi_device_id parse_acpipart_match_table[] = { /* Generic */ - { "acpi-fixed-partitions", 0 }, + { "acpi-partitions", 0 }, /* Customized */ {}, }; From a8b77803d5d803cb6fe5e0c5bb771b85269b08b9 Mon Sep 17 00:00:00 2001 From: Li Guohui Date: Thu, 16 May 2024 15:30:49 +0800 Subject: [PATCH 011/101] firmware: arm_scmi: Make mailbox transport poll on Phytium mailbox There is no completion irq available with Phytium mailbox implementation. Use the core common polling machinery if we detect phytium mailbox. Signed-off-by: Li Guohui Signed-off-by: Chen Baozi Signed-off-by: Wang Yinfeng Link: https://github.com/deepin-community/kernel/pull/153 (cherry picked from commit 061a2bd6802bbd9f63585703b943585220828381) Signed-off-by: Wentao Guan Conflicts: drivers/firmware/arm_scmi/transports/mailbox.c --- drivers/firmware/arm_scmi/transports/mailbox.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/firmware/arm_scmi/transports/mailbox.c b/drivers/firmware/arm_scmi/transports/mailbox.c index ae0f67e6cc45f..cc522e61d81f1 100644 --- a/drivers/firmware/arm_scmi/transports/mailbox.c +++ b/drivers/firmware/arm_scmi/transports/mailbox.c @@ -187,6 +187,7 @@ static int mailbox_chan_setup(struct scmi_chan_info *cinfo, struct device *dev, struct scmi_mailbox *smbox; int ret, a2p_rx_chan, p2a_chan, p2a_rx_chan; struct mbox_client *cl; + struct of_phandle_args args; ret = mailbox_chan_validate(cdev, &a2p_rx_chan, &p2a_chan, &p2a_rx_chan); if (ret) @@ -241,6 +242,16 @@ static int mailbox_chan_setup(struct scmi_chan_info *cinfo, struct device *dev, } } + ret = of_parse_phandle_with_args(cdev->of_node, "mboxes", + "#mbox-cells", 1, &args); + if (ret) { + dev_err(cdev, "failed to get SCMI %s mailbox\n", desc); + return ret; + } + + if (of_device_is_compatible(args.np, "phytium,mbox")) + cinfo->no_completion_irq = true; + cinfo->transport_info = smbox; smbox->cinfo = cinfo; mutex_init(&smbox->chan_lock); From 8da93e4a61f7348c0c7043cb81eea34fbdf515af Mon Sep 17 00:00:00 2001 From: tianwei Date: Fri, 17 May 2024 06:25:07 +0000 Subject: [PATCH 012/101] hda: phytium: Add Phytium hda driver support This patch adds the Phytium hda driver support. Signed-off-by: Chen Baozi Signed-off-by: Wang Yinfeng Signed-off-by: Tian Wei Reviewed-by: Wentao Guan Signed-off-by: WangYuli [ fix build for v6.18 ] (cherry picked from commit 22e34c34cbffaa988176200eb484512026700e77) Signed-off-by: Wentao Guan Conflicts: include/sound/hdaudio.h sound/hda/common/hda_phytium.c sound/hda/common/hda_phytium.h sound/hda/core/controller.c sound/pci/hda/Kconfig sound/pci/hda/Makefile --- include/sound/hdaudio.h | 1 + sound/hda/common/controller.c | 5 + sound/hda/common/hda_controller.h | 7 + sound/hda/controllers/Kconfig | 16 + sound/hda/controllers/Makefile | 2 + sound/hda/controllers/phytium.c | 1083 +++++++++++++++++++++++++++++ sound/hda/controllers/phytium.h | 34 + sound/hda/core/controller.c | 37 + 8 files changed, 1185 insertions(+) create mode 100644 sound/hda/controllers/phytium.c create mode 100644 sound/hda/controllers/phytium.h diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 8ee94377155b2..d8cb083fb780d 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -353,6 +353,7 @@ struct hdac_bus { bool not_use_interrupts:1; /* prohibiting the RIRB IRQ */ bool access_sdnctl_in_dword:1; /* accessing the sdnctl register by dword */ bool use_pio_for_commands:1; /* Use PIO instead of CORB for commands */ + bool cmd_resend; /* command resend */ bool hygon_dword_access:1; int poll_count; diff --git a/sound/hda/common/controller.c b/sound/hda/common/controller.c index afec5c5546ec7..3cfda58329979 100644 --- a/sound/hda/common/controller.c +++ b/sound/hda/common/controller.c @@ -17,6 +17,8 @@ #include #include +#include "../controllers/phytium.h" + #ifdef CONFIG_X86 /* for art-tsc conversion */ #include @@ -163,6 +165,9 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream) struct hda_spdif_out *spdif = snd_hda_spdif_out_of_nid(apcm->codec, hinfo->nid); unsigned short ctls = spdif ? spdif->ctls : 0; + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + + hda->substream = substream; trace_azx_pcm_prepare(chip, azx_dev); guard_dsp_lock(azx_dev); diff --git a/sound/hda/common/hda_controller.h b/sound/hda/common/hda_controller.h index 38227f82e7040..775d1ce6e8980 100644 --- a/sound/hda/common/hda_controller.h +++ b/sound/hda/common/hda_controller.h @@ -63,6 +63,13 @@ struct azx_dev { * when link position is not greater than FIFO size */ bool insufficient; + + /* + * Delayed IRQ handling flag. + * Upstream moved this into the snd-hda-intel private stream + * (e36a88b33cbe3); keep it here for the Phytium HDA controller. + */ + bool irq_pending; }; #define azx_stream(dev) (&(dev)->core) diff --git a/sound/hda/controllers/Kconfig b/sound/hda/controllers/Kconfig index 5d6a77e68588e..e689fa829fbde 100644 --- a/sound/hda/controllers/Kconfig +++ b/sound/hda/controllers/Kconfig @@ -14,6 +14,22 @@ config SND_HDA_INTEL To compile this driver as a module, choose M here: the module will be called snd-hda-intel. +config SND_HDA_PHYTIUM + tristate "PHYTIUM HD Audio" + depends on SOUND + select SND_HDA + select SND_HDA_ALIGNED_MMIO + help + Say Y here to support the HDA controller present in PHYTIUM + SoCs + + This options enables support for the HD Audio controller + present in some PHYTIUM SoCs, used to communicate audio + to the "High Definition Audio" codec. + + To compile this driver as a module, choose M here: the module + will be called snd-hda-phytium. + config SND_HDA_TEGRA tristate "NVIDIA Tegra HD Audio" depends on ARCH_TEGRA diff --git a/sound/hda/controllers/Makefile b/sound/hda/controllers/Makefile index 8967b6771d904..b6491155e8e53 100644 --- a/sound/hda/controllers/Makefile +++ b/sound/hda/controllers/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 snd-hda-intel-y := intel.o +snd-hda-phytium-y := phytium.o snd-hda-tegra-y := tegra.o snd-hda-cix-ipbloq-y := cix-ipbloq.o snd-hda-acpi-y := acpi.o @@ -10,6 +11,7 @@ subdir-ccflags-y += -I$(src)/../common CFLAGS_intel.o := -I$(src) obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-intel.o +obj-$(CONFIG_SND_HDA_PHYTIUM) += snd-hda-phytium.o obj-$(CONFIG_SND_HDA_TEGRA) += snd-hda-tegra.o obj-$(CONFIG_SND_HDA_CIX_IPBLOQ) += snd-hda-cix-ipbloq.o obj-$(CONFIG_SND_HDA_ACPI) += snd-hda-acpi.o diff --git a/sound/hda/controllers/phytium.c b/sound/hda/controllers/phytium.c new file mode 100644 index 0000000000000..4da5e06be9adc --- /dev/null +++ b/sound/hda/controllers/phytium.c @@ -0,0 +1,1083 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Implementation of primary ALSA driver code for Phytium HD Audio. + * + * Copyright(c) 2018-2022, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "phytium.h" + +#include "intel_trace.h" + +/* position fix mode */ +enum { + POS_FIX_AUTO, + POS_FIX_LPIB, + POS_FIX_POSBUF, + POS_FIX_VIACOMBO, + POS_FIX_COMBO, +}; + +/* Define IN stream 0 FIFO size offset in VIA controller */ +#define VIA_IN_STREAM0_FIFO_SIZE_OFFSET 0x90 + +/* FT have 4 playback and 4 capture */ +#define FT4C_NUM_CAPTURE 4 +#define FT4C_NUM_PLAYBACK 4 + +#define DWORD_BYTE_WIDTH 4 +#define BYTE_BIT_WIDTH 8 + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +static char *model[SNDRV_CARDS]; +static int position_fix[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1}; +static int bdl_pos_adj[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1}; +static int probe_mask[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1}; +static int probe_only[SNDRV_CARDS]; +static int jackpoll_ms[SNDRV_CARDS]; +static int single_cmd = -1; +static int enable_msi = -1; +#ifdef CONFIG_SND_HDA_INPUT_BEEP +static bool beep_mode[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = + CONFIG_SND_HDA_INPUT_BEEP_MODE}; +#endif + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Intel HD audio interface."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Intel HD audio interface."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Intel HD audio interface."); +module_param_array(model, charp, NULL, 0444); +MODULE_PARM_DESC(model, "Use the given board model."); +module_param_array(position_fix, int, NULL, 0444); +MODULE_PARM_DESC(position_fix, "DMA pointer read method. (-1 = system default, 0 = auto, 1 = LPIB, 2 = POSBUF, 3 = VIACOMBO, 4 = COMBO)."); +module_param_array(bdl_pos_adj, int, NULL, 0644); +MODULE_PARM_DESC(bdl_pos_adj, "BDL position adjustment offset."); +module_param_array(probe_mask, int, NULL, 0444); +MODULE_PARM_DESC(probe_mask, "Bitmask to probe codecs (default = -1)."); +module_param_array(probe_only, int, NULL, 0444); +MODULE_PARM_DESC(probe_only, "Only probing and no codec initialization."); +module_param_array(jackpoll_ms, int, NULL, 0444); +MODULE_PARM_DESC(jackpoll_ms, "Ms between polling for jack events (default = 0, using unsol events only)"); +module_param(single_cmd, bint, 0444); +MODULE_PARM_DESC(single_cmd, "Use single command to communicate with codecs (for debugging only)."); +module_param(enable_msi, bint, 0444); +MODULE_PARM_DESC(enable_msi, "Enable Message Signaled Interrupt (MSI)"); +#ifdef CONFIG_SND_HDA_INPUT_BEEP +module_param_array(beep_mode, bool, NULL, 0444); +MODULE_PARM_DESC(beep_mode, "Select HDA Beep registration mode (0=off, 1=on) (default=1)."); +#endif + +#define power_save 0 + +static int align_buffer_size = -1; +module_param(align_buffer_size, bint, 0644); +MODULE_PARM_DESC(align_buffer_size, + "Force buffer and period sizes to be multiple of 128 bytes."); + +/* driver types */ +enum { + AZX_DRIVER_ICH, + AZX_DRIVER_PCH, + AZX_DRIVER_SCH, + AZX_DRIVER_HDMI, + AZX_DRIVER_ATI, + AZX_DRIVER_ATIHDMI, + AZX_DRIVER_ATIHDMI_NS, + AZX_DRIVER_VIA, + AZX_DRIVER_SIS, + AZX_DRIVER_ULI, + AZX_DRIVER_NVIDIA, + AZX_DRIVER_TERA, + AZX_DRIVER_CTX, + AZX_DRIVER_CTHDA, + AZX_DRIVER_CMEDIA, + AZX_DRIVER_GENERIC, + AZX_DRIVER_FT, + AZX_NUM_DRIVERS, /* keep this as last entry */ +}; + +/* NOP for other archs */ +static inline void mark_pages_wc(struct azx *chip, struct snd_dma_buffer *buf, + bool on) +{ +} + +static inline void mark_runtime_wc(struct azx *chip, struct azx_dev *azx_dev, + struct snd_pcm_substream *substream, bool on) +{ +} + +static int azx_acquire_irq(struct azx *chip, int do_disconnect); + +/* calculate runtime delay from LPIB */ +static int azx_get_delay_from_lpib(struct azx *chip, struct azx_dev *azx_dev, + unsigned int pos) +{ + struct snd_pcm_substream *substream = azx_dev->core.substream; + int stream = substream->stream; + unsigned int lpib_pos = azx_get_pos_lpib(chip, azx_dev); + int delay; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + delay = pos - lpib_pos; + else + delay = lpib_pos - pos; + if (delay < 0) { + if (delay >= azx_dev->core.delay_negative_threshold) + delay = 0; + else + delay += azx_dev->core.bufsize; + } + + if (delay >= azx_dev->core.period_bytes) { + dev_info(chip->card->dev, + "Unstable LPIB (%d >= %d); disabling LPIB delay counting\n", + delay, azx_dev->core.period_bytes); + delay = 0; + chip->driver_caps &= ~AZX_DCAPS_COUNT_LPIB_DELAY; + chip->get_delay[stream] = NULL; + } + + return bytes_to_frames(substream->runtime, delay); +} + +static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev); + +/* called from IRQ */ +static int azx_position_check(struct azx *chip, struct azx_dev *azx_dev) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + int ok; + + ok = azx_position_ok(chip, azx_dev); + if (ok == 1) { + azx_dev->irq_pending = 0; + return ok; + } else if (ok == 0) { + /* bogus IRQ, process it later */ + azx_dev->irq_pending = 1; + schedule_work(&hda->irq_pending_work); + } + return 0; +} + +static int azx_ft_link_power(struct azx *chip, bool enable) +{ + return 0; +} + +/* + * Check whether the current DMA position is acceptable for updating + * periods. Returns non-zero if it's OK. + * + * Many HD-audio controllers appear pretty inaccurate about + * the update-IRQ timing. The IRQ is issued before actually the + * data is processed. So, we need to process it afterwords in a + * workqueue. + */ +static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev) +{ + struct snd_pcm_substream *substream = azx_dev->core.substream; + int stream = substream->stream; + u32 wallclk; + unsigned int pos; + + wallclk = (azx_readl(chip, WALLCLK) - azx_dev->core.start_wallclk); + + if (wallclk < (azx_dev->core.period_wallclk * 2) / 3) + return -1; /* bogus (too early) interrupt */ + + if (chip->get_position[stream]) + pos = chip->get_position[stream](chip, azx_dev); + else { /* use the position buffer as default */ + pos = azx_get_pos_posbuf(chip, azx_dev); + if (!pos || pos == (u32)-1) { + dev_info(chip->card->dev, + "Invalid position buffer, using LPIB read method instead.\n"); + chip->get_position[stream] = azx_get_pos_lpib; + if (chip->get_position[0] == azx_get_pos_lpib && + chip->get_position[1] == azx_get_pos_lpib) + azx_bus(chip)->use_posbuf = false; + pos = azx_get_pos_lpib(chip, azx_dev); + chip->get_delay[stream] = NULL; + } else { + chip->get_position[stream] = azx_get_pos_posbuf; + if (chip->driver_caps & AZX_DCAPS_COUNT_LPIB_DELAY) + chip->get_delay[stream] = azx_get_delay_from_lpib; + } + } + + if (pos >= azx_dev->core.bufsize) + pos = 0; + + if (WARN_ONCE(!azx_dev->core.period_bytes, + "hda-ft: zero azx_dev->period_bytes")) + return -1; /* this shouldn't happen! */ + if (wallclk < (azx_dev->core.period_wallclk * 5) / 4 && + pos % azx_dev->core.period_bytes > azx_dev->core.period_bytes / 2) + /* NG - it's below the first next period boundary */ + return chip->bdl_pos_adj ? 0 : -1; + + azx_dev->core.start_wallclk += wallclk; + + return 1; /* OK, it's fine */ +} + +/* The work for pending PCM period updates. */ +static void azx_irq_pending_work(struct work_struct *work) +{ + struct hda_ft *hda = container_of(work, struct hda_ft, irq_pending_work); + struct azx *chip = &hda->chip; + struct hdac_bus *bus = azx_bus(chip); + struct hdac_stream *s; + int pending, ok; + + if (!hda->irq_pending_warned) { + dev_info(chip->card->dev, + "IRQ timing workaround is activated for card #%d. Suggest a bigger bdl_pos_adj.\n", + chip->card->number); + hda->irq_pending_warned = 1; + } + + for (;;) { + pending = 0; + spin_lock_irq(&bus->reg_lock); + list_for_each_entry(s, &bus->stream_list, list) { + struct azx_dev *azx_dev = stream_to_azx_dev(s); + + if (!azx_dev->irq_pending || + !s->substream || !s->running) + continue; + ok = azx_position_ok(chip, azx_dev); + if (ok > 0) { + azx_dev->irq_pending = 0; + spin_unlock(&bus->reg_lock); + snd_pcm_period_elapsed(s->substream); + spin_lock(&bus->reg_lock); + } else if (ok < 0) { + pending = 0; /* too early */ + } else { + pending++; + } + } + spin_unlock_irq(&bus->reg_lock); + if (!pending) + return; + udelay(1000); + } +} + +/* clear irq_pending flags and assure no on-going workq */ +static void azx_clear_irq_pending(struct azx *chip) +{ + struct hdac_bus *bus = azx_bus(chip); + struct hdac_stream *s; + + spin_lock_irq(&bus->reg_lock); + list_for_each_entry(s, &bus->stream_list, list) { + struct azx_dev *azx_dev = stream_to_azx_dev(s); + + azx_dev->irq_pending = 0; + } + spin_unlock_irq(&bus->reg_lock); +} + +static int azx_acquire_irq(struct azx *chip, int do_disconnect) +{ + struct hdac_bus *bus = azx_bus(chip); + + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + struct platform_device *pdev = to_platform_device(hda->dev); + int irq_id = platform_get_irq(pdev, 0); + int err; + + err = request_irq(irq_id, azx_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip); + if (err) { + dev_err(chip->card->dev, + "unable to request IRQ %d, disabling device\n", + irq_id); + if (do_disconnect) + snd_card_disconnect(chip->card); + return err; + } + bus->irq = irq_id; + + return 0; +} + +/* get the current DMA position with correction on VIA chips */ +static unsigned int azx_via_get_position(struct azx *chip, + struct azx_dev *azx_dev) +{ + unsigned int link_pos, mini_pos, bound_pos; + unsigned int mod_link_pos, mod_dma_pos, mod_mini_pos; + unsigned int fifo_size; + + link_pos = snd_hdac_stream_get_pos_lpib(azx_stream(azx_dev)); + if (azx_dev->core.substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Playback, no problem using link position */ + return link_pos; + } + + /* Capture */ + /* For new chipset, + * use mod to get the DMA position just like old chipset + */ + mod_dma_pos = le32_to_cpu(*azx_dev->core.posbuf); + mod_dma_pos %= azx_dev->core.period_bytes; + + /* azx_dev->fifo_size can't get FIFO size of in stream. + * Get from base address + offset. + */ + fifo_size = readw(azx_bus(chip)->remap_addr + + VIA_IN_STREAM0_FIFO_SIZE_OFFSET); + + if (azx_dev->insufficient) { + /* Link position never gather than FIFO size */ + if (link_pos <= fifo_size) + return 0; + + azx_dev->insufficient = 0; + } + + if (link_pos <= fifo_size) + mini_pos = azx_dev->core.bufsize + link_pos - fifo_size; + else + mini_pos = link_pos - fifo_size; + + /* Find nearest previous boudary */ + mod_mini_pos = mini_pos % azx_dev->core.period_bytes; + mod_link_pos = link_pos % azx_dev->core.period_bytes; + if (mod_link_pos >= fifo_size) + bound_pos = link_pos - mod_link_pos; + else if (mod_dma_pos >= mod_mini_pos) + bound_pos = mini_pos - mod_mini_pos; + else { + bound_pos = mini_pos - mod_mini_pos + azx_dev->core.period_bytes; + if (bound_pos >= azx_dev->core.bufsize) + bound_pos = 0; + } + + /* Calculate real DMA position we want */ + return bound_pos + mod_dma_pos; +} + +#ifdef CONFIG_PM +static DEFINE_MUTEX(card_list_lock); +static LIST_HEAD(card_list); + +static void azx_add_card_list(struct azx *chip) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + + mutex_lock(&card_list_lock); + list_add(&hda->list, &card_list); + mutex_unlock(&card_list_lock); +} + +static void azx_del_card_list(struct azx *chip) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + + mutex_lock(&card_list_lock); + list_del_init(&hda->list); + mutex_unlock(&card_list_lock); +} + +#else +#define azx_add_card_list(chip) /* NOP */ +#define azx_del_card_list(chip) /* NOP */ +#endif /* CONFIG_PM */ + +#if defined(CONFIG_PM_SLEEP) +/* power management */ +static int azx_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + struct hda_ft *hda; + struct hdac_bus *bus; + + if (!card) + return 0; + + chip = card->private_data; + hda = container_of(chip, struct hda_ft, chip); + if (chip->disabled || !chip->running) + return 0; + + bus = azx_bus(chip); + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + azx_clear_irq_pending(chip); + azx_stop_chip(chip); + if (bus->irq >= 0) { + free_irq(bus->irq, (void *)chip); + bus->irq = -1; + } + + return 0; +} + +static int azx_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + struct hda_ft *hda; + struct hdac_bus *bus; + int index; + struct snd_pcm_substream *substream; + struct azx_dev *azx_dev; + int err; + + if (!card) + return 0; + + chip = card->private_data; + hda = container_of(chip, struct hda_ft, chip); + bus = azx_bus(chip); + if (chip->disabled || !chip->running) + return 0; + + if (azx_acquire_irq(chip, 1) < 0) + return -EIO; + + index = chip->dev_index; + + snd_hdac_bus_exit_link_reset(bus); + usleep_range(1000, 1200); + + azx_init_chip(chip, 0); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + + if (hda->substream && hda->substream->runtime) { + substream = hda->substream; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { + substream->runtime->status->state = + substream->runtime->status->suspended_state; + err = substream->ops->prepare(substream); + if (err < 0) + return err; + } + + azx_dev = get_azx_dev(substream); + hda->substream = NULL; + } + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM +static int azx_runtime_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + struct hda_ft *hda; + + if (!card) + return 0; + + chip = card->private_data; + hda = container_of(chip, struct hda_ft, chip); + if (chip->disabled) + return 0; + + if (!azx_has_pm_runtime(chip)) + return 0; + + azx_stop_chip(chip); + azx_enter_link_reset(chip); + azx_clear_irq_pending(chip); + + return 0; +} + +static int azx_runtime_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + struct hda_ft *hda; + struct hdac_bus *bus; + struct hda_codec *codec; + int status; + int index; + + if (!card) + return 0; + + chip = card->private_data; + hda = container_of(chip, struct hda_ft, chip); + bus = azx_bus(chip); + if (chip->disabled) + return 0; + + if (!azx_has_pm_runtime(chip)) + return 0; + + /* Read STATESTS before controller reset */ + status = azx_readw(chip, STATESTS); + + index = chip->dev_index; + + snd_hdac_bus_exit_link_reset(bus); + usleep_range(1000, 1200); + + azx_init_chip(chip, 0); + + if (status) { + list_for_each_codec(codec, &chip->bus) + if (status & (1 << codec->addr)) + schedule_delayed_work(&codec->jackpoll_work, + codec->jackpoll_interval); + } + + return 0; +} + +static int azx_runtime_idle(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + struct hda_ft *hda; + + if (!card) + return 0; + + chip = card->private_data; + hda = container_of(chip, struct hda_ft, chip); + if (chip->disabled) + return 0; + + if (!azx_has_pm_runtime(chip) || + azx_bus(chip)->codec_powered || !chip->running) + return -EBUSY; + + return 0; +} + +static const struct dev_pm_ops azx_pm = { + SET_SYSTEM_SLEEP_PM_OPS(azx_suspend, azx_resume) + SET_RUNTIME_PM_OPS(azx_runtime_suspend, azx_runtime_resume, azx_runtime_idle) +}; + +#define hda_ft_pm (&azx_pm) +#else +#define hda_ft_pm NULL +#endif /* CONFIG_PM */ + +static int azx_probe_continue(struct azx *chip); + +/* + * destructor + */ +static int azx_free(struct azx *chip) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + struct hdac_bus *bus = azx_bus(chip); + struct platform_device *pdev = to_platform_device(hda->dev); + struct device *hddev = hda->dev; + + if (azx_has_pm_runtime(chip) && chip->running) + pm_runtime_get_noresume(&pdev->dev); + + azx_del_card_list(chip); + + complete_all(&hda->probe_wait); + + if (bus->chip_init) { + azx_clear_irq_pending(chip); + azx_stop_all_streams(chip); + azx_stop_chip(chip); + } + + if (bus->irq >= 0) { + free_irq(bus->irq, (void *)chip); + bus->irq = -1; + } + + devm_iounmap(hddev, bus->remap_addr); + + azx_free_stream_pages(chip); + azx_free_streams(chip); + snd_hdac_bus_exit(bus); + + return 0; +} + +static int azx_dev_disconnect(struct snd_device *device) +{ + struct azx *chip = device->device_data; + + chip->bus.shutdown = 1; + return 0; +} + +static int azx_dev_free(struct snd_device *device) +{ + return azx_free(device->device_data); +} + +static int check_position_fix(struct azx *chip, int fix) +{ + switch (fix) { + case POS_FIX_AUTO: + case POS_FIX_LPIB: + case POS_FIX_POSBUF: + case POS_FIX_VIACOMBO: + case POS_FIX_COMBO: + return fix; + } + + if (chip->driver_caps & AZX_DCAPS_POSFIX_LPIB) { + dev_dbg(chip->card->dev, "Using LPIB position fix\n"); + return POS_FIX_LPIB; + } + return POS_FIX_AUTO; +} + +static void assign_position_fix(struct azx *chip, int fix) +{ + static azx_get_pos_callback_t callbacks[] = { + [POS_FIX_AUTO] = NULL, + [POS_FIX_LPIB] = azx_get_pos_lpib, + [POS_FIX_POSBUF] = azx_get_pos_posbuf, + [POS_FIX_VIACOMBO] = azx_via_get_position, + [POS_FIX_COMBO] = azx_get_pos_lpib, + }; + + chip->get_position[0] = chip->get_position[1] = callbacks[fix]; + + /* combo mode uses LPIB only for playback */ + if (fix == POS_FIX_COMBO) + chip->get_position[1] = NULL; + + if (fix == POS_FIX_POSBUF && + (chip->driver_caps & AZX_DCAPS_COUNT_LPIB_DELAY)) { + chip->get_delay[0] = chip->get_delay[1] = + azx_get_delay_from_lpib; + } + +} + +#define AZX_FORCE_CODEC_MASK 0x100 + +static void check_probe_mask(struct azx *chip, int dev) +{ + chip->codec_probe_mask = probe_mask[dev]; + + /* check forced option */ + if (chip->codec_probe_mask != -1 && + (chip->codec_probe_mask & AZX_FORCE_CODEC_MASK)) { + azx_bus(chip)->codec_mask = chip->codec_probe_mask & 0xff; + dev_info(chip->card->dev, "codec_mask forced to 0x%x\n", + (int)azx_bus(chip)->codec_mask); + } +} + +static void azx_probe_work(struct work_struct *work) +{ + struct hda_ft *hda = container_of(work, struct hda_ft, probe_work); + + azx_probe_continue(&hda->chip); +} + +/* + * constructor + */ +static const struct hda_controller_ops axi_hda_ops; + +static int hda_ft_create(struct snd_card *card, struct platform_device *pdev, + int dev, unsigned int driver_caps, + struct azx **rchip) +{ + static struct snd_device_ops ops = { + .dev_disconnect = azx_dev_disconnect, + .dev_free = azx_dev_free, + }; + struct hda_ft *hda; + struct azx *chip; + int err; + + *rchip = NULL; + + hda = devm_kzalloc(&pdev->dev, sizeof(*hda), GFP_KERNEL); + if (!hda) + return -ENOMEM; + hda->dev = &pdev->dev; + chip = &hda->chip; + mutex_init(&chip->open_mutex); + chip->card = card; + chip->ops = &axi_hda_ops; + chip->driver_caps = driver_caps; + chip->driver_type = driver_caps & 0xff; + chip->dev_index = dev; + if (jackpoll_ms[dev] >= 50 && jackpoll_ms[dev] <= 60000) + chip->jackpoll_interval = msecs_to_jiffies(jackpoll_ms[dev]); + INIT_LIST_HEAD(&chip->pcm_list); + INIT_WORK(&hda->irq_pending_work, azx_irq_pending_work); + INIT_LIST_HEAD(&hda->list); + + init_completion(&hda->probe_wait); + assign_position_fix(chip, check_position_fix(chip, position_fix[dev])); + check_probe_mask(chip, dev); + + if (single_cmd < 0) /* allow fallback to single_cmd at errors */ + chip->fallback_to_single_cmd = 0; + else /* explicitly set to single_cmd or not */ + chip->single_cmd = single_cmd; + + if (bdl_pos_adj[dev] < 0) { + switch (chip->driver_type) { + case AZX_DRIVER_FT: + bdl_pos_adj[dev] = 32; + break; + default: + bdl_pos_adj[dev] = 32; + break; + } + } + chip->bdl_pos_adj = bdl_pos_adj[dev]; + + err = azx_bus_init(chip, model[dev]); + if (err < 0) + return err; + + chip->bus.core.aligned_mmio = 1; + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err < 0) { + dev_err(card->dev, "Error creating device [card]!\n"); + azx_free(chip); + return err; + } + + /* continue probing in work context as may trigger request module */ + INIT_WORK(&hda->probe_work, azx_probe_work); + + *rchip = chip; + + return 0; +} + +static int azx_first_init(struct azx *chip) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + struct platform_device *pdev = to_platform_device(hda->dev); + struct device *hddev = hda->dev; + + int dev = chip->dev_index; + bool full_reset; + + struct snd_card *card = chip->card; + struct hdac_bus *bus = azx_bus(chip); + int err; + unsigned short gcap; + unsigned int dma_bits = 64; + + struct resource *res; + const struct acpi_device_id *match; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hda->regs = devm_ioremap_resource(hddev, res); + if (IS_ERR(hda->regs)) + return PTR_ERR(hda->regs); + + bus->addr = res->start; + bus->remap_addr = hda->regs; + if (bus->remap_addr == NULL) { + dev_err(card->dev, "ioremap error\n"); + return -ENXIO; + } + + bus->cmd_resend = 1; + + if (azx_acquire_irq(chip, 0) < 0) + return -EBUSY; + + synchronize_irq(bus->irq); + + gcap = azx_readw(chip, GCAP); + dev_dbg(card->dev, "chipset global capabilities = 0x%x\n", gcap); + + /* disable 64bit DMA address on some devices */ + if (chip->driver_caps & AZX_DCAPS_NO_64BIT) { + dev_dbg(card->dev, "Disabling 64bit DMA\n"); + gcap &= ~AZX_GCAP_64OK; + } + + /* disable buffer size rounding to 128-byte multiples if supported */ + if (align_buffer_size >= 0) + chip->align_buffer_size = !!align_buffer_size; + else { + if (chip->driver_caps & AZX_DCAPS_NO_ALIGN_BUFSIZE) + chip->align_buffer_size = 0; + else + chip->align_buffer_size = 1; + } + + if (has_acpi_companion(hddev)) { + match = acpi_match_device(hddev->driver->acpi_match_table, hddev); + if (!match) { + dev_err(hddev, "Error ACPI match data is missing\n"); + return -ENODEV; + } + set_dma_ops(hddev, NULL); + acpi_dma_configure(hddev, DEV_DMA_NON_COHERENT); + } + + /* allow 64bit DMA address if supported by H/W */ + if (!(gcap & AZX_GCAP_64OK)) + dma_bits = 32; + if (!dma_set_mask(hddev, DMA_BIT_MASK(dma_bits))) { + dma_set_coherent_mask(hddev, DMA_BIT_MASK(dma_bits)); + } else { + dma_set_mask(hddev, DMA_BIT_MASK(32)); + dma_set_coherent_mask(hddev, DMA_BIT_MASK(32)); + } + + /* read number of streams from GCAP register instead of using + * hardcoded value + */ + chip->capture_streams = (gcap >> 8) & 0x0f; + chip->playback_streams = (gcap >> 12) & 0x0f; + if (!chip->playback_streams && !chip->capture_streams) { + /* gcap didn't give any info, switching to old method */ + chip->playback_streams = FT4C_NUM_PLAYBACK; + chip->capture_streams = FT4C_NUM_CAPTURE; + } + chip->capture_index_offset = 0; + chip->playback_index_offset = chip->capture_streams; + chip->num_streams = chip->playback_streams + chip->capture_streams; + + /* initialize streams */ + err = azx_init_streams(chip); + if (err < 0) + return err; + + err = azx_alloc_stream_pages(chip); + if (err < 0) + return err; + + full_reset = (probe_only[dev] & 2) ? false : true; + azx_init_chip(chip, full_reset); + + /* codec detection */ + if (!azx_bus(chip)->codec_mask) { + dev_err(card->dev, "no codecs found!\n"); + return -ENODEV; + } + + strscpy(card->driver, "ft-hda", sizeof(card->driver)); + strscpy(card->shortname, "ft-hda", sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx irq %i", + card->shortname, bus->addr, bus->irq); + + return 0; +} + + +static const struct hda_controller_ops axi_hda_ops = { + .position_check = azx_position_check, + .link_power = azx_ft_link_power, +}; + +static DECLARE_BITMAP(probed_devs, SNDRV_CARDS); + +static int hda_ft_probe(struct platform_device *pdev) +{ + const unsigned int driver_flags = AZX_DRIVER_FT; + struct snd_card *card; + struct hda_ft *hda; + struct azx *chip; + bool schedule_probe; + int err; + int dev; + + dev = find_first_zero_bit(probed_devs, SNDRV_CARDS); + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + set_bit(dev, probed_devs); + return -ENOENT; + } + + err = snd_card_new(&pdev->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err < 0) { + dev_err(&pdev->dev, "Error creating card!\n"); + return err; + } + + err = hda_ft_create(card, pdev, dev, driver_flags, &chip); + if (err < 0) + goto out_free; + card->private_data = chip; + hda = container_of(chip, struct hda_ft, chip); + + dev_set_drvdata(&pdev->dev, card); + + schedule_probe = !chip->disabled; + + if (schedule_probe) + schedule_work(&hda->probe_work); + + set_bit(dev, probed_devs); + if (chip->disabled) + complete_all(&hda->probe_wait); + return 0; + +out_free: + snd_card_free(card); + return err; +} + +/* number of codec slots for each chipset: 0 = default slots (i.e. 4) */ +static unsigned int azx_max_codecs[AZX_NUM_DRIVERS] = { + [AZX_DRIVER_FT] = 4, +}; + +static int azx_probe_continue(struct azx *chip) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + struct device *hddev = hda->dev; + int dev = chip->dev_index; + int err; + struct hdac_bus *bus = azx_bus(chip); + + hda->probe_continued = 1; + + err = azx_first_init(chip); + if (err < 0) + goto out_free; + +#ifdef CONFIG_SND_HDA_INPUT_BEEP + chip->beep_mode = beep_mode[dev]; +#endif + + /* create codec instances */ + err = azx_probe_codecs(chip, azx_max_codecs[chip->driver_type]); + if (err < 0) + goto out_free; + + if ((probe_only[dev] & 1) == 0) { + err = azx_codec_configure(chip); + if (err < 0) + goto out_free; + } + + err = snd_card_register(chip->card); + if (err < 0) + goto out_free; + + chip->running = 1; + azx_add_card_list(chip); + snd_hda_set_power_save(&chip->bus, power_save * 1000); + + if (azx_has_pm_runtime(chip)) + pm_runtime_put_noidle(hddev); + return err; + +out_free: + if (bus->irq >= 0) { + free_irq(bus->irq, (void *)chip); + bus->irq = -1; + } + return err; +} + +static void hda_ft_remove(struct platform_device *pdev) +{ + struct snd_card *card = dev_get_drvdata(&pdev->dev); + struct azx *chip; + struct hda_ft *hda; + + if (card) { + /* cancel the pending probing work */ + chip = card->private_data; + hda = container_of(chip, struct hda_ft, chip); + cancel_work_sync(&hda->probe_work); + clear_bit(chip->dev_index, probed_devs); + + snd_card_free(card); + } +} + +static void hda_ft_shutdown(struct platform_device *pdev) +{ + struct snd_card *card = dev_get_drvdata(&pdev->dev); + struct azx *chip; + + if (!card) + return; + chip = card->private_data; + if (chip && chip->running) + azx_stop_chip(chip); +} + +static const struct of_device_id hda_ft_of_match[] = { + { .compatible = "phytium,hda" }, + {}, +}; +MODULE_DEVICE_TABLE(of, hda_ft_of_match); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id hda_ft_acpi_match[] = { + { .id = "PHYT0006" }, + {} +}; +MODULE_DEVICE_TABLE(acpi, hda_ft_acpi_match); +#else +#define hda_ft_acpi_match NULL +#endif + +static struct platform_driver ft_platform_hda = { + .driver = { + .name = "ft-hda", + .pm = hda_ft_pm, + .of_match_table = hda_ft_of_match, + .acpi_match_table = hda_ft_acpi_match, + }, + .probe = hda_ft_probe, + .remove = hda_ft_remove, + .shutdown = hda_ft_shutdown, +}; + +module_platform_driver(ft_platform_hda); + +MODULE_DESCRIPTION("FT HDA bus driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/hda/controllers/phytium.h b/sound/hda/controllers/phytium.h new file mode 100644 index 0000000000000..a2183ef5e0d83 --- /dev/null +++ b/sound/hda/controllers/phytium.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Implementation of primary ALSA driver code base for Phytium HD Audio. + * + * Copyright(c) 2018-2022, Phytium Technology Co., Ltd. + */ +#ifndef __SOUND_HDA_PHYTIUM_H__ +#define __SOUND_HDA_PHYTIUM_H__ + +#include "hda_controller.h" + +struct hda_ft { + struct azx chip; + struct snd_pcm_substream *substream; + struct device *dev; + void __iomem *regs; + + /* for pending irqs */ + struct work_struct irq_pending_work; + + /* sync probing */ + struct completion probe_wait; + struct work_struct probe_work; + + /* card list (for power_save trigger) */ + struct list_head list; + + /* extra flags */ + unsigned int irq_pending_warned:1; + unsigned int probe_continued:1; + +}; + +#endif diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c index bfe8170457247..1975328bae9ef 100644 --- a/sound/hda/core/controller.c +++ b/sound/hda/core/controller.c @@ -220,6 +220,9 @@ static int snd_hdac_bus_send_cmd_corb(struct hdac_bus *bus, unsigned int val) { unsigned int addr = azx_command_addr(val); unsigned int wp, rp; + unsigned long timeout; + unsigned int rirb_wp; + int i = 0; guard(spinlock_irq)(&bus->reg_lock); @@ -244,6 +247,40 @@ static int snd_hdac_bus_send_cmd_corb(struct hdac_bus *bus, unsigned int val) bus->corb.buf[wp] = cpu_to_le32(val); snd_hdac_chip_writew(bus, CORBWP, wp); + if (bus->cmd_resend) { + timeout = jiffies + msecs_to_jiffies(1000); + udelay(80); + rirb_wp = snd_hdac_chip_readw(bus, RIRBWP); + while (rirb_wp == bus->rirb.wp) { + udelay(80); + rirb_wp = snd_hdac_chip_readw(bus, RIRBWP); + if (rirb_wp != bus->rirb.wp) + break; + if (i > 5) + break; + if (time_after(jiffies, timeout)) + break; + + /* add command to corb */ + wp = snd_hdac_chip_readw(bus, CORBWP); + if (wp == 0xffff) { + /* something wrong, controller likely turned to D3 */ + return -EIO; + } + wp++; + wp %= AZX_MAX_CORB_ENTRIES; + + rp = snd_hdac_chip_readw(bus, CORBRP); + if (wp == rp) { + /* oops, it's full */ + return -EAGAIN; + } + bus->corb.buf[wp] = cpu_to_le32(val); + snd_hdac_chip_writew(bus, CORBWP, wp); + i++; + } + } + return 0; } From 24e866adbd0cbc69d9b88ed5fb78c8cc5497dbba Mon Sep 17 00:00:00 2001 From: WangYuli Date: Mon, 8 Jul 2024 02:33:16 +0800 Subject: [PATCH 013/101] hda: Implement a bit field in cmd_resend A ':1' bit field gives the compiler the option to squeeze it in with other booleans, thereby optimizing memory usage. Suggested-by: Icenowy Signed-off-by: WangYuli (cherry picked from commit 7a249f8d3458e913677f95075522aef90cbb862c) Signed-off-by: Wentao Guan Conflicts: include/sound/hdaudio.h --- include/sound/hdaudio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index d8cb083fb780d..861588eb1002d 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -353,7 +353,7 @@ struct hdac_bus { bool not_use_interrupts:1; /* prohibiting the RIRB IRQ */ bool access_sdnctl_in_dword:1; /* accessing the sdnctl register by dword */ bool use_pio_for_commands:1; /* Use PIO instead of CORB for commands */ - bool cmd_resend; /* command resend */ + bool cmd_resend:1; /* command resend */ bool hygon_dword_access:1; int poll_count; From eae726fc31cc117d4f110eb8470625c83fe9d4d9 Mon Sep 17 00:00:00 2001 From: wenlunpeng Date: Fri, 26 Aug 2022 15:23:07 +0800 Subject: [PATCH 014/101] spi: phytium: fix 'Disabling IRQ #18' It also casue FT-D3000 board m70f request_irq register a handler 'phytium_spi_irq', which calls spi_master_get_devdata. so it should be called after spi_master_set_devdata, where the handler can really get the fts structure. Log: [ 1.535532] irq 18: nobody cared (try booting with the "irqpoll" option) [ 1.542258] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 4.19.0-arm64-desktop #5300 [ 1.542260] Hardware name: N/A N/A/L21K-2041K, BIOS KL4.27.BD.D.062.D 03/10/2022 [ 1.542261] Call trace: [ 1.542267] dump_backtrace+0x0/0x190 [ 1.542269] show_stack+0x14/0x20 [ 1.542273] dump_stack+0xa8/0xcc [ 1.542277] __report_bad_irq+0x48/0x100 [ 1.542279] note_interrupt+0x280/0x2e4 [ 1.542281] handle_irq_event_percpu+0x54/0x68 [ 1.542283] handle_irq_event+0x40/0x98 [ 1.542285] handle_fasteoi_irq+0xd4/0x1a0 [ 1.542287] generic_handle_irq+0x2c/0x40 [ 1.542289] __handle_domain_irq+0x60/0xb8 [ 1.542291] gic_handle_irq+0x7c/0x178 [ 1.542292] el1_irq+0xb0/0x140 [ 1.542294] __do_softirq+0x84/0x2e8 [ 1.542297] irq_exit+0x9c/0xb8 [ 1.542298] __handle_domain_irq+0x64/0xb8 [ 1.542300] gic_handle_irq+0x7c/0x178 [ 1.542301] el1_irq+0xb0/0x140 [ 1.542303] __setup_irq+0x478/0x6d8 [ 1.542305] request_threaded_irq+0xdc/0x198 [ 1.542308] phytium_spi_add_host+0x70/0x198 [ 1.542310] phytium_spi_probe+0x2ec/0x328 [ 1.542313] platform_drv_probe+0x50/0xa0 [ 1.542315] really_probe+0x23c/0x3c8 [ 1.542317] driver_probe_device+0xdc/0x130 [ 1.542318] __driver_attach+0x128/0x150 [ 1.542320] bus_for_each_dev+0x60/0x98 [ 1.542322] driver_attach+0x20/0x28 [ 1.542323] bus_add_driver+0x1a0/0x280 [ 1.542325] driver_register+0x60/0x110 [ 1.542327] __platform_driver_register+0x44/0x50 [ 1.542330] phytium_spi_driver_init+0x18/0x20 [ 1.542332] do_one_initcall+0x30/0x19c [ 1.542335] kernel_init_freeable+0x27c/0x320 [ 1.542338] kernel_init+0x10/0x100 [ 1.542340] ret_from_fork+0x10/0x18 [ 1.542341] handlers: [ 1.544611] [<0000000057fd3891>] phytium_spi_irq [ 1.549235] Disabling IRQ #18 same problem link: https://gitee.com/openeuler/kernel/commit/7e9c59f90e0d0b78aa3ad27fc2baf37b9333b41f Signed-off-by: wenlunpeng Link: https://github.com/deepin-community/kernel/pull/355 (cherry picked from commit 050c4bfe40587e4833a8f72e6c14d279b58e7cef) Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/spi/spi-phytium.c b/drivers/spi/spi-phytium.c index 6b9a2351c545b..b4df5cdf411a7 100644 --- a/drivers/spi/spi-phytium.c +++ b/drivers/spi/spi-phytium.c @@ -405,13 +405,6 @@ int phytium_spi_add_host(struct device *dev, struct phytium_spi *fts) fts->dma_addr = (dma_addr_t)(fts->paddr + DR); snprintf(fts->name, sizeof(fts->name), "phytium_spi%d", fts->bus_num); - ret = request_irq(fts->irq, phytium_spi_irq, IRQF_SHARED, - fts->name, master); - if (ret < 0) { - dev_err(dev, "can not get IRQ\n"); - goto err_free_master; - } - master->use_gpio_descriptors = true; master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP; master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16); @@ -440,6 +433,14 @@ int phytium_spi_add_host(struct device *dev, struct phytium_spi *fts) } spi_controller_set_devdata(master, fts); + + ret = request_irq(fts->irq, phytium_spi_irq, IRQF_SHARED, + fts->name, master); + if (ret < 0) { + dev_err(dev, "can not get IRQ\n"); + goto err_free_master; + } + ret = spi_register_controller(master); if (ret) { dev_err(&master->dev, "problem registering spi master\n"); From fc4554f6ef04a2175544eca7672387ca40fa23b2 Mon Sep 17 00:00:00 2001 From: WangYuli Date: Sat, 17 Aug 2024 15:55:41 +0800 Subject: [PATCH 015/101] spi: Introduce dependencise for Phytium to avoid warnings Fix follow warnings in Kconfig: WARNING: unmet direct dependencies detected for SPI_PHYTIUM Depends on [n]: SPI [=y] && SPI_MASTER [=y] && (ARCH_PHYTIUM || COMPILE_TEST [=n]) Selected by [m]: - SPI_PHYTIUM_PLAT [=m] && SPI [=y] && SPI_MASTER [=y] - SPI_PHYTIUM_PCI [=m] && SPI [=y] && SPI_MASTER [=y] && PCI [=y] WARNING: unmet direct dependencies detected for SPI_PHYTIUM Depends on [n]: SPI [=y] && SPI_MASTER [=y] && (ARCH_PHYTIUM || COMPILE_TEST [=n]) Selected by [m]: - SPI_PHYTIUM_PLAT [=m] && SPI [=y] && SPI_MASTER [=y] - SPI_PHYTIUM_PCI [=m] && SPI [=y] && SPI_MASTER [=y] && PCI [=y] Signed-off-by: WangYuli Link: https://github.com/deepin-community/kernel/pull/376 (cherry picked from commit cc12f457b221b73370ca4e7dbc902eb5fb3d113d) Signed-off-by: Wentao Guan --- drivers/spi/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index ff00d22172741..62e29d4126697 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -858,6 +858,7 @@ config SPI_PHYTIUM config SPI_PHYTIUM_PLAT tristate "Phytium SPI controller platform support" + depends on ARCH_PHYTIUM || COMPILE_TEST select SPI_PHYTIUM help This selects a platform driver for Phytium SPI controller. @@ -867,6 +868,7 @@ config SPI_PHYTIUM_PLAT config SPI_PHYTIUM_PCI tristate "Phytium SPI controller PCI support" + depends on ARCH_PHYTIUM || COMPILE_TEST depends on PCI select SPI_PHYTIUM help From 86858d4871e34273c2b2b6dc76319ad07f9347aa Mon Sep 17 00:00:00 2001 From: jasontao Date: Tue, 3 Dec 2024 15:35:29 +0800 Subject: [PATCH 016/101] add gf hdaudio 001 patch in deepin kernel 6.6 Signed-off-by: jasontao Change-Id: Ief9a58e29ec73540da4d05e0fa79bf75e0f0da6f (cherry picked from commit b7e8bf15860fe2033100182dee657be2add75211) Signed-off-by: Wentao Guan Conflicts: sound/hda/common/controller.c sound/hda/controllers/intel.c sound/pci/hda/patch_hdmi.c --- sound/hda/codecs/hdmi/hdmi.c | 2 + sound/hda/common/controller.c | 107 ++++++++++++++++++++++++++++++ sound/hda/common/hda_controller.h | 3 + sound/hda/controllers/intel.c | 93 +++++++++++++++++++++++++- sound/hda/controllers/intel.h | 11 +++ 5 files changed, 214 insertions(+), 2 deletions(-) diff --git a/sound/hda/codecs/hdmi/hdmi.c b/sound/hda/codecs/hdmi/hdmi.c index a4a2f2f0540c9..a65348a3856bd 100644 --- a/sound/hda/codecs/hdmi/hdmi.c +++ b/sound/hda/codecs/hdmi/hdmi.c @@ -2346,6 +2346,8 @@ static const struct hda_device_id snd_hda_id_generichdmi[] = { HDA_CODEC_ID_MODEL(0x1d179f8e, "KX-7000 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x1d179f8f, "KX-7000 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x1d179f90, "KX-7000 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x67663d80, "Arise 80 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x67663d81, "Arise 81 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x67663d82, "Arise 82 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x67663d83, "Arise 83 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x67663d84, "Arise 84 HDMI/DP", MODEL_GF), diff --git a/sound/hda/common/controller.c b/sound/hda/common/controller.c index 3cfda58329979..ec893c4c6e816 100644 --- a/sound/hda/common/controller.c +++ b/sound/hda/common/controller.c @@ -18,6 +18,8 @@ #include #include "../controllers/phytium.h" +#include "../controllers/intel.h" +#include #ifdef CONFIG_X86 /* for art-tsc conversion */ @@ -86,6 +88,104 @@ static u64 azx_adjust_codec_delay(struct snd_pcm_substream *substream, return (nsec > codec_nsecs) ? nsec - codec_nsecs : 0; } +int gf_setup_bdle(struct snd_pcm_substream *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct azx *chip = apcm->chip; + struct azx_dev *azx_dev = get_azx_dev(substream); + __le32 *bdl; + unsigned int i, stream_idx; + struct gf_private *gf_chip = NULL; + + // setup BDL and BDLE + if ((chip->pci != NULL) && (chip->pci->vendor == 0x6766) && (chip->pci->device == 0x3d40)) { + gf_chip = container_of(chip, struct gf_private, hda.chip); + if (gf_chip == NULL) { + return -1; + } + stream_idx = apcm->codec->addr - 1; + if ((stream_idx <= 1) && (gf_chip->diu_fb_bdl_vaddr[stream_idx])) { + if (azx_dev->core.bdl.bytes <= BDL_SIZE) { + memcpy(gf_chip->diu_fb_bdl_vaddr[stream_idx], azx_dev->core.bdl.area, azx_dev->core.bdl.bytes); + } else { + memcpy(gf_chip->diu_fb_bdl_vaddr[stream_idx], azx_dev->core.bdl.area, BDL_SIZE); + } + bdl = (__le32 *)gf_chip->diu_fb_bdl_vaddr[stream_idx]; + for (i = 0; i < azx_dev->core.frags; i++) { + if (i > 0) { + bdl[i*4] = cpu_to_le32((u32)(bdl[(i-1)*4] + bdl[(i-1)*4+2])); + bdl[i*4+1] = cpu_to_le32(upper_32_bits(gf_chip->diu_fb_stream_ofs[stream_idx])); + } + else { + bdl[i*4] = cpu_to_le32((u32)gf_chip->diu_fb_stream_ofs[stream_idx]); + bdl[i*4+1] = cpu_to_le32(upper_32_bits(gf_chip->diu_fb_stream_ofs[stream_idx])); + } + } + snd_hdac_stream_writel((azx_stream(azx_dev)), SD_BDLPL, (u32)gf_chip->diu_fb_bdl_ofs[stream_idx]); + snd_hdac_stream_writel((azx_stream(azx_dev)), SD_BDLPU, upper_32_bits(gf_chip->diu_fb_bdl_ofs[stream_idx])); + } + } + return 0; +} + +int gf_pre_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct azx *chip = apcm->chip; + unsigned int stream_idx; + struct gf_private *gf_chip = NULL; + + if ((substream->runtime) && (chip->pci != NULL) && (chip->pci->vendor == 0x6766) && (chip->pci->device == 0x3d40)) { + gf_chip = container_of(chip, struct gf_private, hda.chip); + if (gf_chip == NULL) { + return -1; + } + stream_idx = apcm->codec->addr - 1; + if ((cmd == SNDRV_PCM_TRIGGER_START) && + (stream_idx <= 1) && (gf_chip->diu_fb_stream_vaddr[stream_idx]) && (substream->runtime->dma_area)) { + memcpy(gf_chip->diu_fb_stream_vaddr[stream_idx], substream->runtime->dma_area, substream->runtime->dma_bytes); + gf_chip->diu_fb_stream_pos[stream_idx] = 0; + } + } + return 0; +} + +int gf_update_stream(struct snd_pcm_substream *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct azx *chip = apcm->chip; + unsigned int stream_idx, hw_pos, appl_pos; + struct gf_private *gf_chip = NULL; + + if ((substream->runtime) && (chip->pci != NULL) && (chip->pci->vendor == 0x6766) && (chip->pci->device == 0x3d40)) { + gf_chip = container_of(chip, struct gf_private, hda.chip); + if (gf_chip == NULL) { + return -1; + } + stream_idx = apcm->codec->addr - 1; + if ((stream_idx <= 1) && (gf_chip->diu_fb_stream_vaddr[stream_idx]) && (substream->runtime->dma_area) && + (substream->runtime->dma_bytes <= GF_HDA_FB_STREAM_SIZE) && snd_pcm_running(substream)) { + hw_pos = frames_to_bytes(substream->runtime, substream->runtime->status->hw_ptr % substream->runtime->buffer_size); + appl_pos = frames_to_bytes(substream->runtime, substream->runtime->control->appl_ptr % substream->runtime->buffer_size); + + if (hw_pos == appl_pos) { + memcpy(gf_chip->diu_fb_stream_vaddr[stream_idx], substream->runtime->dma_area, substream->runtime->dma_bytes); + } + else if (appl_pos > gf_chip->diu_fb_stream_pos[stream_idx]) { + memcpy(gf_chip->diu_fb_stream_vaddr[stream_idx] + gf_chip->diu_fb_stream_pos[stream_idx], substream->runtime->dma_area + gf_chip->diu_fb_stream_pos[stream_idx], (appl_pos - gf_chip->diu_fb_stream_pos[stream_idx])); + } + else if (appl_pos < gf_chip->diu_fb_stream_pos[stream_idx]) { + if(substream->runtime->dma_bytes > gf_chip->diu_fb_stream_pos[stream_idx]) { + memcpy(gf_chip->diu_fb_stream_vaddr[stream_idx] + gf_chip->diu_fb_stream_pos[stream_idx], substream->runtime->dma_area + gf_chip->diu_fb_stream_pos[stream_idx], (substream->runtime->dma_bytes - gf_chip->diu_fb_stream_pos[stream_idx])); + } + memcpy(gf_chip->diu_fb_stream_vaddr[stream_idx], substream->runtime->dma_area, appl_pos); + } + gf_chip->diu_fb_stream_pos[stream_idx] = appl_pos; + } + } + return 0; +} + /* * PCM ops */ @@ -191,6 +291,8 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream) snd_hdac_stream_setup(azx_stream(azx_dev), false); + gf_setup_bdle(substream); + stream_tag = azx_dev->core.stream_tag; /* CA-IBG chips need the playback stream starting from 1 */ if ((chip->driver_caps & AZX_DCAPS_CTX_WORKAROUND) && @@ -244,6 +346,8 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) return -EINVAL; } + gf_pre_trigger(substream, cmd); + snd_pcm_group_for_each_entry(s, substream) { if (s->pcm->card != substream->pcm->card) continue; @@ -328,6 +432,9 @@ static snd_pcm_uframes_t azx_pcm_pointer(struct snd_pcm_substream *substream) struct azx_pcm *apcm = snd_pcm_substream_chip(substream); struct azx *chip = apcm->chip; struct azx_dev *azx_dev = get_azx_dev(substream); + + gf_update_stream(substream); + return bytes_to_frames(substream->runtime, azx_get_position(chip, azx_dev)); } diff --git a/sound/hda/common/hda_controller.h b/sound/hda/common/hda_controller.h index 775d1ce6e8980..12f93b43c066b 100644 --- a/sound/hda/common/hda_controller.h +++ b/sound/hda/common/hda_controller.h @@ -17,6 +17,9 @@ #define AZX_MAX_CODECS HDA_MAX_CODECS #define AZX_DEFAULT_CODECS 4 +#define GF_HDA_PATCH_VERSION 1 +#define GF_HDA_FB_STREAM_SIZE 7*1024*1024 + /* driver quirks (capabilities) */ /* bits 0-7 are used for indicating driver type */ #define AZX_DCAPS_NO_TCSEL (1 << 8) /* No Intel TCSEL bit */ diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c index 2095cf84bd5b6..2865654753c61 100644 --- a/sound/hda/controllers/intel.c +++ b/sound/hda/controllers/intel.c @@ -383,6 +383,84 @@ static void update_pci_byte(struct pci_dev *pci, unsigned int reg, pci_write_config_byte(pci, reg, data); } + +static int gf_init_pci(struct azx *chip) +{ + struct pci_dev *diu_pci = NULL; + unsigned long fb_size; + phys_addr_t diu_fb_base; + phys_addr_t diu_fb_stream[2]; + phys_addr_t diu_fb_bdl[2]; + struct gf_private *gf_chip = NULL; + + dev_info(chip->card->dev, "gf_hda patch version %03d \n", GF_HDA_PATCH_VERSION); + + if ((chip->pci != NULL) && (chip->pci->vendor == 0x6766) && (chip->pci->device == 0x3d40)) { + gf_chip = container_of(chip, struct gf_private, hda.chip); + if (gf_chip == NULL) { + return -1; + } + gf_chip->diu_fb_stream_vaddr[0] = NULL; + gf_chip->diu_fb_stream_vaddr[1] = NULL; + gf_chip->diu_fb_bdl_vaddr[0] = NULL; + gf_chip->diu_fb_bdl_vaddr[1] = NULL; + + if (chip->pci->bus != NULL) + diu_pci = pci_get_slot(chip->pci->bus, PCI_DEVFN(PCI_SLOT(chip->pci->devfn), 0)); + + if (!diu_pci) { + dev_info(chip->card->dev, "gf_hda can't get display device\n"); + } + else { + diu_fb_base = pci_resource_start(diu_pci, 1); + fb_size = pci_resource_len(diu_pci, 1); + + diu_fb_stream[0] = diu_fb_base + fb_size - (4+16)*1024*1024; + gf_chip->diu_fb_stream_ofs[0] = diu_fb_stream[0] - diu_fb_base; // stream offset = fb_size -4M-16M + gf_chip->diu_fb_stream_vaddr[0] = ioremap_wc(diu_fb_stream[0], GF_HDA_FB_STREAM_SIZE); // size = 7M + + diu_fb_stream[1] = diu_fb_stream[0] + GF_HDA_FB_STREAM_SIZE; + gf_chip->diu_fb_stream_ofs[1] = diu_fb_stream[1] - diu_fb_base; // stream offset = fb_size -4M-16M+7M + gf_chip->diu_fb_stream_vaddr[1] = ioremap_wc(diu_fb_stream[1], GF_HDA_FB_STREAM_SIZE); // size = 7M + + diu_fb_bdl[0] = diu_fb_stream[1] + GF_HDA_FB_STREAM_SIZE; + gf_chip->diu_fb_bdl_ofs[0] = diu_fb_bdl[0] - diu_fb_base; // stream offset = fb_size -4M-16M+7M*2 + gf_chip->diu_fb_bdl_vaddr[0] = ioremap_wc(diu_fb_bdl[0], BDL_SIZE); // size = 4K + + diu_fb_bdl[1] = diu_fb_bdl[0] + BDL_SIZE; + gf_chip->diu_fb_bdl_ofs[1] = diu_fb_bdl[1] - diu_fb_base; // stream offset = fb_size -4M-16M+7M*2+4K + gf_chip->diu_fb_bdl_vaddr[1] = ioremap_wc(diu_fb_bdl[1], BDL_SIZE); // size = 4K + + dev_info(chip->card->dev, "gf_hda diu fb base=0x%llx, size=%dM.\n", diu_fb_base, (unsigned int)(fb_size >> 20)); + } + } + return 0; +} + +static void gf_free_pci(struct azx *chip) +{ + struct gf_private *gf_chip = NULL; + + if ((chip->pci != NULL) && (chip->pci->vendor == 0x6766) && (chip->pci->device == 0x3d40)) { + gf_chip = container_of(chip, struct gf_private, hda.chip); + if (gf_chip == NULL) { + return; + } + + if(gf_chip->diu_fb_stream_vaddr[0]) + iounmap(gf_chip->diu_fb_stream_vaddr[0]); + + if(gf_chip->diu_fb_stream_vaddr[1]) + iounmap(gf_chip->diu_fb_stream_vaddr[1]); + + if(gf_chip->diu_fb_bdl_vaddr[0]) + iounmap(gf_chip->diu_fb_bdl_vaddr[0]); + + if(gf_chip->diu_fb_bdl_vaddr[1]) + iounmap(gf_chip->diu_fb_bdl_vaddr[1]); + } +} + static void azx_init_pci(struct azx *chip) { int snoop_type = azx_get_snoop_type(chip); @@ -1394,6 +1472,10 @@ static void azx_free(struct azx *chip) if (bus->irq >= 0) free_irq(bus->irq, (void*)chip); + if (chip->driver_type == AZX_DRIVER_GFHDMI) { + gf_free_pci(chip); + } + azx_free_stream_pages(chip); azx_free_streams(chip); snd_hdac_bus_exit(bus); @@ -1795,7 +1877,12 @@ static int azx_create(struct snd_card *card, struct pci_dev *pci, if (err < 0) return err; - hda = devm_kzalloc(&pci->dev, sizeof(*hda), GFP_KERNEL); + if ((pci != NULL) && (pci->vendor == 0x6766) && (pci->device == 0x3d40)) { + hda = devm_kzalloc(&pci->dev, sizeof(struct gf_private), GFP_KERNEL); + } + else { + hda = devm_kzalloc(&pci->dev, sizeof(*hda), GFP_KERNEL); + } if (!hda) return -ENOMEM; @@ -1915,8 +2002,10 @@ static int azx_first_init(struct azx *chip) * Fix response write request not synced to memory when handle * hdac interrupt on Glenfly Gpus */ - if (chip->driver_type == AZX_DRIVER_GFHDMI) + if (chip->driver_type == AZX_DRIVER_GFHDMI) { bus->polling_mode = 1; + gf_init_pci(chip); + } if (chip->driver_type == AZX_DRIVER_LOONGSON) { bus->polling_mode = 1; diff --git a/sound/hda/controllers/intel.h b/sound/hda/controllers/intel.h index 4efb3b0fc2d81..e6f1f443bea11 100644 --- a/sound/hda/controllers/intel.h +++ b/sound/hda/controllers/intel.h @@ -44,4 +44,15 @@ struct hda_intel_stream { #define azx_dev_to_istream(azx_dev) \ container_of(azx_dev, struct hda_intel_stream, azx_dev) +struct gf_private { + struct hda_intel hda; + + phys_addr_t diu_fb_stream_ofs[2]; + void __iomem *diu_fb_stream_vaddr[2]; + unsigned int diu_fb_stream_pos[2]; + + phys_addr_t diu_fb_bdl_ofs[2]; + void __iomem *diu_fb_bdl_vaddr[2]; +}; + #endif From 0dc6b70ed1b90d0b40498c5f0080e5a80af6b8ef Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Fri, 26 Dec 2025 09:46:19 +0800 Subject: [PATCH 017/101] sound: gf: adapt for v6.18 Link: https://github.com/deepin-community/kernel/pull/512 Signed-off-by: Wentao Guan --- sound/hda/common/controller.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/hda/common/controller.c b/sound/hda/common/controller.c index ec893c4c6e816..85e13ff7011c5 100644 --- a/sound/hda/common/controller.c +++ b/sound/hda/common/controller.c @@ -88,7 +88,7 @@ static u64 azx_adjust_codec_delay(struct snd_pcm_substream *substream, return (nsec > codec_nsecs) ? nsec - codec_nsecs : 0; } -int gf_setup_bdle(struct snd_pcm_substream *substream) +static int gf_setup_bdle(struct snd_pcm_substream *substream) { struct azx_pcm *apcm = snd_pcm_substream_chip(substream); struct azx *chip = apcm->chip; @@ -128,7 +128,7 @@ int gf_setup_bdle(struct snd_pcm_substream *substream) return 0; } -int gf_pre_trigger(struct snd_pcm_substream *substream, int cmd) +static int gf_pre_trigger(struct snd_pcm_substream *substream, int cmd) { struct azx_pcm *apcm = snd_pcm_substream_chip(substream); struct azx *chip = apcm->chip; @@ -150,7 +150,7 @@ int gf_pre_trigger(struct snd_pcm_substream *substream, int cmd) return 0; } -int gf_update_stream(struct snd_pcm_substream *substream) +static int gf_update_stream(struct snd_pcm_substream *substream) { struct azx_pcm *apcm = snd_pcm_substream_chip(substream); struct azx *chip = apcm->chip; From e85784027633c9b6b27a2fc4b748c9594c9aeb6f Mon Sep 17 00:00:00 2001 From: WangYuli Date: Mon, 12 May 2025 21:55:34 +0800 Subject: [PATCH 018/101] ALSA: hda: Add FTHD0001 match for Phytium Some Phytium ALSA HDA devices declare their ID as FTHD0001 instead of PHYT0006 (following Phytium's deprecated older ACPI specification). Add the old ID to the ACPI match list so that the audio driver for these devices can be loaded correctly. Similar to ("ALSA: hda: Resolving the issue of the ALC662 sound card failing to load.") in openKylin tree. Link: https://gitee.com/openkylin/linux/commit/6ad24efc4daecb54c99e31862d3cc0220e5d8a72 Codeveloped-by: wangdicheng Signed-off-by: wangdicheng Signed-off-by: WangYuli (cherry picked from commit 555481f035ced671de4f819362ce590cd91a25fe) --- sound/hda/controllers/phytium.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/controllers/phytium.c b/sound/hda/controllers/phytium.c index 4da5e06be9adc..40c820c503b1c 100644 --- a/sound/hda/controllers/phytium.c +++ b/sound/hda/controllers/phytium.c @@ -1058,6 +1058,7 @@ MODULE_DEVICE_TABLE(of, hda_ft_of_match); #ifdef CONFIG_ACPI static const struct acpi_device_id hda_ft_acpi_match[] = { { .id = "PHYT0006" }, + { .id = "FTHD0001" }, {} }; MODULE_DEVICE_TABLE(acpi, hda_ft_acpi_match); From c6e9de22cc5a7165d9c602183eb673b4e01d0729 Mon Sep 17 00:00:00 2001 From: Cui Fulong Date: Tue, 10 Mar 2026 17:11:10 +0800 Subject: [PATCH 019/101] qspi: phytium: Slove the capacity register configured error issue Functions that configure capacity registers should shift left instead of right, which will lead to capacity register configured error. Mainline: Open-Source Signed-off-by: Cui Fulong Signed-off-by: Peng Min Signed-off-by: Wang Yinfeng --- drivers/spi/spi-phytium-qspi.c | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index fa87ac355f697..9919a0e3890ad 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -246,6 +246,44 @@ static int phytium_spi_nor_protocol_encode(const struct spi_mem_op *op, u32 *cod return ret; } +static int phytium_qspi_flash_capacity_encode_new(u32 size, + u32 *cap, int i) +{ + int ret = 0; + + switch (size) { + case SZ_4M: + *cap |= (0x0 << (4 * i)); + break; + case SZ_8M: + *cap |= (0x1 << (4 * i)); + break; + case SZ_16M: + *cap |= (0x2 << (4 * i)); + break; + case SZ_32M: + *cap |= (0x3 << (4 * i)); + break; + case SZ_64M: + *cap |= (0x4 << (4 * i)); + break; + case SZ_128M: + *cap |= (0x5 << (4 * i)); + break; + case SZ_256M: + *cap |= (0x6 << (4 * i)); + break; + case SZ_512M: + *cap |= (0x7 << (4 * i)); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + static int phytium_qspi_flash_capacity_encode(u32 size, u32 *cap) { int ret = 0; From eba7d1cacc1e5f4484b41f890c3ca2de705decdf Mon Sep 17 00:00:00 2001 From: Cui Fulong Date: Tue, 10 Mar 2026 17:12:50 +0800 Subject: [PATCH 020/101] qspi: phytium: Fix the cmd_port register incorrectly configured issue The dummy bit of the cmd_port register is incorrectly configured, which will results in a 1-1-4 indirect read error. Mainline: Open-Source Signed-off-by: Cui Fulong Signed-off-by: Peng Min Signed-off-by: Wang Yinfeng --- drivers/spi/spi-phytium-qspi.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index 9919a0e3890ad..45d55e601e20c 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -21,6 +21,11 @@ #include #include +#define DRIVER_VERSION "1.0.3" + +#define PHYTIUM_CPU_PART_FTC862 0x862 + +#define MIDR_PHYTIUM_FTC862 MIDR_CPU_MODEL(ARM_CPU_IMP_PHYTIUM, PHYTIUM_CPU_PART_FTC862) #define QSPI_FLASH_CAP_REG 0x00 #define QSPI_FLASH_CAP_NUM_SHIFT 3 @@ -433,8 +438,8 @@ static int phytium_qspi_exec_op(struct spi_mem *mem, if (op->dummy.nbytes) { cmd |= QSPI_CMD_PORT_LATENCY_MASK; - cmd |= ((op->dummy.nbytes * 8) / op->dummy.buswidth) << - QSPI_CMD_PORT_LATENCY_SHIFT; + cmd |= ((op->dummy.nbytes * 8 - 1) / op->dummy.buswidth) << + QSPI_CMD_PORT_DUMMY_SHIFT; } if (op->data.nbytes) { From 5073a06fc47bf1466fd31fc8aa1b872d049348f3 Mon Sep 17 00:00:00 2001 From: Cui Fulong Date: Tue, 10 Mar 2026 17:14:37 +0800 Subject: [PATCH 021/101] qspi: phytium: Capacity register can't be configured without flash node If the device tree or ACPI table does not describe the flash node, the capacity register should not be configured for the QSPI controller driver. Mainline: Open-Source Signed-off-by: Cui Fulong Signed-off-by: Peng Min Signed-off-by: Wang Yinfeng --- drivers/spi/spi-phytium-qspi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index 45d55e601e20c..2296078a943cc 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -758,7 +758,7 @@ static int phytium_qspi_probe(struct platform_device *pdev) goto probe_setup_failed; } - if (!qspi->nodirmap) { + if (!qspi->nodirmap && qspi->fnum != 0) { /* * The controller supports direct mapping access only if all * flashes are of same size. From b4cb0174adb16ddde793bc84dd07448c6f2994f0 Mon Sep 17 00:00:00 2001 From: Cui Fulong Date: Tue, 10 Mar 2026 17:15:54 +0800 Subject: [PATCH 022/101] spi-v2: phytium: Add the debug log function to the driver The error information is stored to the specified ddr address, and when an exception occurs, the error information can be retrieved from this address. Mainline: Open-Source Signed-off-by: Cui Fulong Signed-off-by: Peng Min Signed-off-by: Wang Yinfeng --- drivers/spi/Makefile | 2 + drivers/spi/spi-phytium-common.c | 457 ++++++++++++++++++++++++ drivers/spi/spi-phytium-plat-v2.c | 326 +++++++++++++++++ drivers/spi/spi-phytium-v2.c | 559 ++++++++++++++++++++++++++++++ drivers/spi/spi-phytium.h | 124 +++++++ 5 files changed, 1468 insertions(+) create mode 100644 drivers/spi/spi-phytium-common.c create mode 100644 drivers/spi/spi-phytium-plat-v2.c create mode 100644 drivers/spi/spi-phytium-v2.c diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 26cea07a87368..0a4a6c47f2e10 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -117,6 +117,8 @@ obj-$(CONFIG_SPI_PHYTIUM_PLAT) += spi-phytium-plat.o obj-$(CONFIG_SPI_PHYTIUM_PCI) += spi-phytium-pci.o obj-$(CONFIG_SPI_PHYTIUM_QSPI) += spi-phytium-qspi.o obj-$(CONFIG_SPI_PHYTIUM) += spi-phytium-dma.o +obj-$(CONFIG_SPI_PHYTIUM_V2) += spi-phytium-common.o spi-phytium-v2.o +obj-$(CONFIG_SPI_PHYTIUM_PLAT_V2) += spi-phytium-plat-v2.o obj-$(CONFIG_SPI_PIC32) += spi-pic32.o obj-$(CONFIG_SPI_PIC32_SQI) += spi-pic32-sqi.o obj-$(CONFIG_SPI_PL022) += spi-pl022.o diff --git a/drivers/spi/spi-phytium-common.c b/drivers/spi/spi-phytium-common.c new file mode 100644 index 0000000000000..96a30a01ebe50 --- /dev/null +++ b/drivers/spi/spi-phytium-common.c @@ -0,0 +1,457 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SPI core controller driver. + * + * Copyright (c) 2023-2024, Phytium Technology Co., Ltd.. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "spi-phytium.h" + +#define SPI_SHOW_MSG_DEBUG 0 + +void spi_phytium_show_msg(struct msg *info) +{ + if (SPI_SHOW_MSG_DEBUG) { + pr_err("module:0x%4x, cmd:0x%04x, sub:0x%04x\n", + info->seq, info->cmd_id, info->cmd_subid); + + pr_err("0x%02x 0x%02x 0x%02x 0x%02x", info->data[0], + info->data[1], info->data[2], info->data[3]); + + pr_err("0x%02x 0x%02x 0x%02x 0x%02x", info->data[4], + info->data[5], info->data[6], info->data[7]); + + pr_err("0x%02x 0x%02x 0x%02x 0x%02x", info->data[8], + info->data[9], info->data[10], info->data[11]); + + pr_err("0x%02x 0x%02x 0x%02x 0x%02x", info->data[12], + info->data[13], info->data[14], info->data[15]); + } +} + +void *memcpy_byte(void *_dest, const void *_src, size_t sz) +{ + while (sz >= 8) { + *(u64 *)_dest = *(u64 *)_src; + _dest += 8; + _src += 8; + sz -= 8; + } + + while (sz) { + *(u8 *)_dest = *(u8 *)_src; + _dest++; + _src++; + sz--; + } + + return _dest; +} + +int spi_phytium_print_status(struct phytium_spi *fts, u8 status0, + u8 status1) +{ + if (status1 == 0) + return 0; + + switch (status1) { + case 1: + pr_err("SPI Bus is busy.\n"); + break; + case 2: + pr_err("DMA queue transfer error.\n"); + break; + case 3: + pr_err("DMA transfer timeout.\n"); + break; + case 4: + pr_err("Operating flash timeout.\n"); + break; + case 5: + pr_err("spi_tx channel:DMA initialization failure.\n"); + break; + case 6: + pr_err("spi_rx channel:DMA initialization failure.\n"); + break; + case 7: + pr_err("DMA queue initialization failure.\n"); + break; + default: + pr_err("status=0x%x,Unknown error\n", status1); + break; + } + + if (fts->debug_enabled) + fts->handle_debug_err(fts); + + return -1; +} + +int spi_phytium_check_result(struct phytium_spi *fts) +{ + unsigned long long ms = 300000; + struct msg *msg = (struct msg *)fts->tx_shmem_addr; + + reinit_completion(&fts->cmd_completion); + ms = wait_for_completion_timeout(&fts->cmd_completion, msecs_to_jiffies(ms)); + + if (ms == 0) { + dev_err(&fts->master->dev, "SPI controller timed out\n"); + return -1; + } + + return spi_phytium_print_status(fts, msg->status0, msg->status1); +} + +int spi_phytium_set(struct phytium_spi *fts) +{ + int ret; + + spi_phytium_show_msg(fts->msg); + phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); + ret = spi_phytium_check_result(fts); + + return ret; +} + +void spi_phytium_default(struct phytium_spi *fts) +{ + memset(fts->msg, 0, sizeof(struct msg)); + + fts->msg->cmd_id = PHYTSPI_MSG_CMD_DEFAULT; + + spi_phytium_show_msg(fts->msg); + phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); + spi_phytium_check_result(fts); +} +EXPORT_SYMBOL_GPL(spi_phytium_default); + +void spi_phytium_set_subid(struct phytium_spi *fts, u16 sub_cmd) +{ + fts->msg->cmd_id = PHYTSPI_MSG_CMD_SET; + fts->msg->cmd_subid = sub_cmd; +} + +void spi_phytium_set_cmd8(struct phytium_spi *fts, u16 sub_cmd, + u8 data) +{ + memset(fts->msg, 0, sizeof(struct msg)); + spi_phytium_set_subid(fts, sub_cmd); + fts->msg->data[0] = data; + spi_phytium_show_msg(fts->msg); + phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); + spi_phytium_check_result(fts); +} +EXPORT_SYMBOL_GPL(spi_phytium_set_cmd8); + +void spi_phytium_set_cmd16(struct phytium_spi *fts, u16 sub_cmd, + u16 data) +{ + u16 *cp_data = (u16 *)&fts->msg->data[0]; + + memset(fts->msg, 0, sizeof(struct msg)); + spi_phytium_set_subid(fts, sub_cmd); + *cp_data = data; + spi_phytium_show_msg(fts->msg); + phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); + spi_phytium_check_result(fts); +} +EXPORT_SYMBOL_GPL(spi_phytium_set_cmd16); + +void spi_phytium_set_cmd32(struct phytium_spi *fts, u16 sub_cmd, + u32 data) +{ + u32 *cp_data = (u32 *)&fts->msg->data[0]; + + memset(fts->msg, 0, sizeof(struct msg)); + spi_phytium_set_subid(fts, sub_cmd); + *cp_data = data; + spi_phytium_show_msg(fts->msg); + phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); + spi_phytium_check_result(fts); +} +EXPORT_SYMBOL_GPL(spi_phytium_set_cmd32); + +void spi_phytium_data_subid(struct phytium_spi *fts, u16 sub_cmd) +{ + fts->msg->cmd_id = PHYTSPI_MSG_CMD_DATA; + fts->msg->cmd_subid = sub_cmd; +} + +void spi_phytium_write_pre(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, u8 tmode, + u8 flags, u8 spi_write_flag) +{ + struct msg *msg; + u32 len; + u64 smem_tx; + u8 first = 1; + u64 tx_addr; + + len = min((u32)(fts->tx_end - fts->tx), (u32)SPI_TRANS_DATA_SIZE); + msg = (struct msg *)((u64)fts->msg + (sizeof(struct msg) + FLASH_PAGE_SIZE)*spi_write_flag); + memset(msg, 0, sizeof(struct msg)); + + msg->cmd_id = PHYTSPI_MSG_CMD_DATA; + if (spi_write_flag) { + if (len > 16 && fts->dma_get_ddrdata) + msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_FLASH_DMA_TX; + else + msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_FLASH_TX; + } else { + if (len > 16 && fts->dma_get_ddrdata) + msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_DMA_TX; + else + msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_TX; + } + + if (len > 16 && fts->dma_get_ddrdata) { + tx_addr = (u64)__virt_to_phys((u64)fts->tx); + if (!tx_addr) { + dev_err(&fts->master->dev, "tx address translation failed\n"); + return; + } + *(u64 *)&msg->data[0] = tx_addr; + } else { + smem_tx = (u64)msg + sizeof(struct msg); + memcpy((void *)smem_tx, fts->tx, len); + *(u64 *)&msg->data[0] = sizeof(struct msg); + } + + *(u32 *)&msg->data[8] = len; + fts->tx += len; + msg->data[12] = cs; + msg->data[13] = dfs; + msg->data[14] = mode; + msg->data[15] = tmode; + msg->data[16] = flags; + msg->data[17] = first; + first = 0; +} +EXPORT_SYMBOL_GPL(spi_phytium_write_pre); + +int spi_phytium_flash_erase(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, + u8 tmode, u8 flags, u8 cmd) +{ + u32 len; + u64 smem_tx; + u8 first = 1; + u8 cmd_addr[8]; + int ret; + + len = (u32)(fts->tx_end - fts->tx); + + memset(fts->msg, 0, sizeof(struct msg)); + + fts->msg->cmd_id = PHYTSPI_MSG_CMD_DATA; + + if (cmd == SPINOR_OP_BE_4K || cmd == SPINOR_OP_CHIP_ERASE) + fts->msg->cmd_subid = PHYTSPI_MSG_CMD_FLASH_ERASE; + else if (cmd == SPINOR_OP_READ || cmd == SPINOR_OP_READ_FAST || + cmd == SPINOR_OP_READ_4B || cmd == SPINOR_OP_READ_FAST_4B) + fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_TX; + + smem_tx = (u64)fts->msg + sizeof(struct msg); + + cmd_addr[0] = cmd; + if (cmd == SPINOR_OP_BE_4K || cmd == SPINOR_OP_READ || + cmd == SPINOR_OP_READ_FAST || cmd == SPINOR_OP_READ_4B || + cmd == SPINOR_OP_READ_FAST_4B) { + memcpy_byte((void *)&cmd_addr[1], fts->tx, len); + memcpy_byte((void *)smem_tx, (void *)&cmd_addr[0], len + 1); + *(u32 *)&fts->msg->data[8] = len + 1; + } else if (cmd == SPINOR_OP_CHIP_ERASE) { + memcpy_byte((void *)smem_tx, (void *)&cmd_addr[0], 1); + *(u32 *)&fts->msg->data[8] = len; + } + + *(u32 *)&fts->msg->data[0] = sizeof(struct msg); + fts->tx += len; + fts->msg->data[12] = cs; + fts->msg->data[13] = dfs; + fts->msg->data[14] = mode; + fts->msg->data[15] = tmode; + fts->msg->data[16] = flags; + fts->msg->data[17] = first; + + ret = spi_phytium_set(fts); + if (ret) { + dev_err(&fts->master->dev, "AP <-> RV interaction failed\n"); + return ret; + } + return ret; +} +EXPORT_SYMBOL_GPL(spi_phytium_flash_erase); + +int spi_phytium_flash_write(struct phytium_spi *fts, u8 cmd) +{ + u8 cmd_addr[8] = {0}; + + cmd_addr[0] = fts->len + 1; + cmd_addr[1] = cmd; + memcpy_byte((void *)&cmd_addr[2], fts->tx, fts->len); + + fts->msg->data[18] = cmd_addr[0]; + fts->msg->data[19] = cmd_addr[1]; + fts->msg->data[20] = cmd_addr[2]; + fts->msg->data[21] = cmd_addr[3]; + fts->msg->data[22] = cmd_addr[4]; + fts->msg->data[23] = cmd_addr[5]; + + return 0; +} +EXPORT_SYMBOL_GPL(spi_phytium_flash_write); + +int spi_phytium_write(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, + u8 tmode, u8 flags, u8 spi_write_flag) +{ + int ret = 0; + u32 len; + u64 smem_tx; + u8 first = 1; + u64 tx_addr; + + if (spi_write_flag == 1) { + spi_phytium_set(fts); + if (ret) { + dev_err(&fts->master->dev, "AP <-> RV interaction failed\n"); + return ret; + } + } + + do { + len = min_t(u32, (u32)(fts->tx_end - fts->tx), + (u32)SPI_TRANS_DATA_SIZE); + + fts->msg->cmd_id = PHYTSPI_MSG_CMD_DATA; + + if (spi_write_flag == 2) { + if (len > 16 && fts->dma_get_ddrdata) + fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_FLASH_DMA_TX; + else + fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_FLASH_TX; + } else { + if (len > 16 && fts->dma_get_ddrdata) + fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_DMA_TX; + else + fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_TX; + } + + if (len > 16 && fts->dma_get_ddrdata) { + tx_addr = __virt_to_phys((u64)fts->tx); + if (!tx_addr) { + dev_err(&fts->master->dev, "tx address translation failed\n"); + return -1; + } + *(u64 *)&fts->msg->data[0] = tx_addr; + } else { + smem_tx = (u64)fts->msg + sizeof(struct msg); + memcpy_byte((void *)smem_tx, fts->tx, len); + *(u64 *)&fts->msg->data[0] = sizeof(struct msg); + } + + *(u32 *)&fts->msg->data[8] = len; + fts->tx += len; + fts->msg->data[12] = cs; + fts->msg->data[13] = dfs; + fts->msg->data[14] = mode; + fts->msg->data[15] = tmode; + fts->msg->data[16] = flags; + fts->msg->data[17] = first; + ret = spi_phytium_set(fts); + if (ret) { + dev_err(&fts->master->dev, "AP <-> RV interaction failed\n"); + return ret; + } + + first = 0; + } while (fts->tx_end > fts->tx); + + return ret; +} +EXPORT_SYMBOL_GPL(spi_phytium_write); + +int spi_phytium_read(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, + u8 tmode, u8 flags) +{ + int ret; + u32 len; + u64 smem_rx; + u8 first = 1; + u64 rx_addr; + + do { + if (fts->dma_get_ddrdata) + len = min_t(u32, (u32)(fts->rx_end - fts->rx), + (u32)(fts->rx_end - fts->rx)); + else + len = min_t(u32, (u32)(fts->rx_end - fts->rx), 128); + + fts->msg->cmd_id = PHYTSPI_MSG_CMD_DATA; + + smem_rx = (u64)fts->msg + sizeof(struct msg); + + if (len > 16 && fts->dma_get_ddrdata) { + fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_DMA_RX; + rx_addr = __virt_to_phys((u64)fts->rx); + if (!rx_addr) { + dev_err(&fts->master->dev, "rx address translation failed\n"); + return -1; + } + *(u64 *)&fts->msg->data[0] = rx_addr; + } else { + fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_RX; + *(u64 *)&fts->msg->data[0] = sizeof(struct msg); + } + + *(u32 *)&fts->msg->data[8] = len; + fts->msg->data[12] = cs; + fts->msg->data[13] = dfs; + fts->msg->data[14] = mode; + fts->msg->data[15] = tmode; + if (fts->rx_end <= fts->rx + len) + fts->msg->data[16] = flags; + else if (first == 1) + fts->msg->data[16] = 1; + else + fts->msg->data[16] = 0; + fts->msg->data[17] = first; + ret = spi_phytium_set(fts); + if (ret) { + dev_err(&fts->master->dev, "AP <-> RV interaction failed\n"); + return ret; + } + if (len <= 16 || !fts->dma_get_ddrdata) + memcpy_byte(fts->rx, (void *)smem_rx, len); + + fts->rx += len; + first = 0; + } while (fts->rx_end > fts->rx); + + return ret; +} +EXPORT_SYMBOL_GPL(spi_phytium_read); + +MODULE_AUTHOR("Peng Min "); +MODULE_DESCRIPTION("Phytium SPI adapter core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-phytium-plat-v2.c b/drivers/spi/spi-phytium-plat-v2.c new file mode 100644 index 0000000000000..7dff26be48df3 --- /dev/null +++ b/drivers/spi/spi-phytium-plat-v2.c @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SPI core controller platform driver. + * + * Copyright (c) 2023-2024, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "spi-phytium.h" + +#define DRIVER_NAME_PHYT "phytium_spi_2.0" +#define DRIVER_VERSION "1.0.7" + +#define PHYTIUM_CPU_PART_FTC872 0x872 + +#define MIDR_PHYTIUM_FTC872 MIDR_CPU_MODEL(ARM_CPU_IMP_PHYTIUM, PHYTIUM_CPU_PART_FTC872) + +static ssize_t debug_show(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct phytium_spi *fts = dev_get_drvdata(dev); + ssize_t ret; + u32 reg; + + reg = phytium_read_regfile(fts, SPI_REGFILE_DEBUG); + ret = sprintf(buf, "%x\n", reg); + + return ret; +} + +static ssize_t debug_store(struct device *dev, + struct device_attribute *da, const char *buf, + size_t size) +{ + u8 loc, dis_en, status = 0; + char *p; + char *token; + long value; + u32 reg; + struct phytium_spi *fts = dev_get_drvdata(dev); + + dev_info(dev, "echo alive(1)/debug(0) enable(1)/disable(0) > debug\n"); + dev_info(dev, "Example:echo 0 1 > debug; Enable Debug Function\n"); + + p = kmalloc(size, GFP_KERNEL); + if (!p) + return -ENOMEM; + strscpy(p, buf, size); + + token = strsep(&p, " "); + if (!token) + return -EINVAL; + + status = kstrtol(token, 0, &value); + if (status) + return status; + loc = (u8)value; + + token = strsep(&p, " "); + if (!token) + return -EINVAL; + + status = kstrtol(token, 0, &value); + if (status) + return status; + dis_en = value; + + reg = phytium_read_regfile(fts, SPI_REGFILE_DEBUG); + + if (loc == 1) { + if (dis_en == 1) { + fts->alive_enabled = true; + reg |= BIT(loc); + } else if (dis_en == 0) { + fts->alive_enabled = false; + reg &= ~BIT(loc); + } + fts->runtimes = 0; + } else if (loc == 0) { + if (dis_en == 1) { + fts->debug_enabled = true; + reg |= BIT(loc); + } else if (dis_en == 0) { + fts->debug_enabled = false; + reg &= ~BIT(loc); + } + } + + phytium_write_regfile(fts, SPI_REGFILE_DEBUG, reg); + + kfree(p); + + return size; +} + +static DEVICE_ATTR_RW(debug); + +static struct attribute *spi_phyt_device_attrs[] = { + &dev_attr_debug.attr, + NULL, +}; + +static const struct attribute_group spi_phyt_device_group = { + .attrs = spi_phyt_device_attrs, +}; + +static int spi_phyt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phytium_spi *fts; + struct resource *regfile_mem, *share_mem; + int ret; + int num_cs; + int cs_gpio; + int global_cs = 0; + int i; + u32 clk_rate = SPI_DEFAULT_CLK; + + fts = devm_kzalloc(&pdev->dev, sizeof(struct phytium_spi), + GFP_KERNEL); + if (!fts) + return -ENOMEM; + + regfile_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regfile_mem) { + dev_err(&pdev->dev, "no regfile_mem resource?\n"); + return -EINVAL; + } + + fts->regfile = devm_ioremap_resource(&pdev->dev, regfile_mem); + if (IS_ERR(fts->regfile)) { + dev_err(&pdev->dev, "fts->regfile map failed\n"); + return PTR_ERR(fts->regfile); + } + + share_mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!share_mem) { + dev_err(&pdev->dev, "no share_mem resource?\n"); + return -EINVAL; + } + + fts->mem_tx_physic = (u32)share_mem->start + sizeof(struct msg); + fts->mem_rx_physic = (u32)share_mem->start + sizeof(struct msg); + + fts->tx_shmem_addr = devm_ioremap_resource(&pdev->dev, share_mem); + if (IS_ERR(fts->tx_shmem_addr)) { + dev_err(&pdev->dev, "fts->tx_shmem_addr map failed\n"); + return PTR_ERR(fts->tx_shmem_addr); + } + + fts->msg = (struct msg *)fts->tx_shmem_addr; + + fts->mem_tx = (u64)fts->msg + sizeof(struct msg); + fts->mem_rx = (u64)fts->msg + sizeof(struct msg); + + fts->irq = platform_get_irq(pdev, 0); + if (fts->irq < 0) { + dev_err(&pdev->dev, "no irq resource?\n"); + return fts->irq; /* -ENXIO */ + } + + if (pdev->dev.of_node) { + fts->clk = devm_clk_get(&pdev->dev, NULL); + + if (IS_ERR(fts->clk)) + return PTR_ERR(fts->clk); + ret = clk_prepare_enable(fts->clk); + if (ret) + return ret; + + fts->max_freq = clk_get_rate(fts->clk); + } else if (has_acpi_companion(&pdev->dev)) { + fwnode_property_read_u32(dev->fwnode, "spi-clock", &clk_rate); + fts->max_freq = clk_rate; + } + + fts->bus_num = pdev->id; + device_property_read_u32(&pdev->dev, "reg-io-width", &fts->reg_io_width); + + num_cs = 4; + + device_property_read_u32(&pdev->dev, "num-cs", &num_cs); + + fts->num_cs = num_cs; + + if (pdev->dev.of_node) { + int i; + + for (i = 0; i < fts->num_cs; i++) { + cs_gpio = of_get_named_gpio(pdev->dev.of_node, + "cs-gpios", i); + + if (cs_gpio == -EPROBE_DEFER) { + ret = cs_gpio; + goto out; + } + + if (gpio_is_valid(cs_gpio)) { + ret = devm_gpio_request(&pdev->dev, cs_gpio, + dev_name(&pdev->dev)); + if (ret) + goto out; + } + } + } else if (has_acpi_companion(&pdev->dev)) { + int n; + int *cs; + struct gpio_desc *gpiod; + + n = gpiod_count(&pdev->dev, "cs"); + + cs = devm_kcalloc(&pdev->dev, n, sizeof(int), GFP_KERNEL); + fts->cs = cs; + + for (i = 0; i < n; i++) { + gpiod = devm_gpiod_get_index_optional(&pdev->dev, "cs", i, + GPIOD_OUT_LOW); + + if (IS_ERR(gpiod)) { + ret = PTR_ERR(gpiod); + goto out; + } + + cs_gpio = desc_to_gpio(gpiod); + cs[i] = cs_gpio; + } + } + + device_property_read_u32(&pdev->dev, "global-cs", &global_cs); + fts->global_cs = global_cs; + + fts->dma_get_ddrdata = false; + if ((read_cpuid_id() & MIDR_CPU_MODEL_MASK) == MIDR_PHYTIUM_FTC872) + fts->dma_get_ddrdata = true; + + ret = spi_phyt_add_host(&pdev->dev, fts); + if (ret) + goto out; + + platform_set_drvdata(pdev, fts); + + if (sysfs_create_group(&pdev->dev.kobj, &spi_phyt_device_group)) + dev_warn(&pdev->dev, "failed create sysfs\n"); + + return 0; + +out: + clk_disable_unprepare(fts->clk); + return ret; +} + +static int spi_phyt_remove(struct platform_device *pdev) +{ + struct phytium_spi *fts = platform_get_drvdata(pdev); + + spi_phyt_remove_host(fts); + sysfs_remove_group(&pdev->dev.kobj, &spi_phyt_device_group); + clk_disable_unprepare(fts->clk); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int spi_phyt_suspend(struct device *dev) +{ + struct phytium_spi *fts = dev_get_drvdata(dev); + + return spi_phyt_suspend_host(fts); +} + +static int spi_phyt_resume(struct device *dev) +{ + struct phytium_spi *fts = dev_get_drvdata(dev); + + return spi_phyt_resume_host(fts); +} +#endif + +static SIMPLE_DEV_PM_OPS(spi_phyt_pm_ops, spi_phyt_suspend, spi_phyt_resume); + +static const struct of_device_id spi_phyt_of_match[] = { + { .compatible = "phytium,spi-2.0", .data = (void *)0 }, + { /* end of table */} +}; +MODULE_DEVICE_TABLE(of, spi_phyt_of_match); + +static const struct acpi_device_id spi_phyt_acpi_match[] = { + {"PHYT0060", 0}, + {} +}; +MODULE_DEVICE_TABLE(acpi, spi_phyt_acpi_match); + +static struct platform_driver spi_phyt_driver = { + .probe = spi_phyt_probe, + .remove = spi_phyt_remove, + .driver = { + .name = DRIVER_NAME_PHYT, + .of_match_table = of_match_ptr(spi_phyt_of_match), + .acpi_match_table = ACPI_PTR(spi_phyt_acpi_match), + .pm = &spi_phyt_pm_ops, + }, +}; +module_platform_driver(spi_phyt_driver); + +MODULE_AUTHOR("Peng Min "); +MODULE_DESCRIPTION("Platform Driver for Phytium SPI controller core"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); diff --git a/drivers/spi/spi-phytium-v2.c b/drivers/spi/spi-phytium-v2.c new file mode 100644 index 0000000000000..53d7f38e8708e --- /dev/null +++ b/drivers/spi/spi-phytium-v2.c @@ -0,0 +1,559 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SPI core controller driver. + * + * Copyright (c) 2023-2024, Phytium Technology Co., Ltd.. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "spi-phytium.h" + +static inline void spi_phyt_enable_chip(struct phytium_spi *fts, u8 enable) +{ + u8 val = enable ? 1 : 2; + + spi_phytium_set_cmd8(fts, PHYTSPI_MSG_CMD_SET_MODULE_EN, val); +} + +static inline void spi_phyt_set_clk(struct phytium_spi *fts, u16 div) +{ + u32 new_div = div; + + spi_phytium_set_cmd32(fts, PHYTSPI_MSG_CMD_SET_BAUDR, new_div); +} + +static inline void spi_phyt_dma_reset(struct phytium_spi *fts, u8 enable) +{ + spi_phytium_set_cmd8(fts, PHYTSPI_MSG_CMD_SET_DMA_RESET, enable); +} + +static inline void spi_phyt_global_cs(struct phytium_spi *fts) +{ + u32 global_cs_en; + u16 cs; + + global_cs_en = GENMASK(fts->num_cs-1, 0) << fts->num_cs; + + cs = (u16)((0x1 << 8) | global_cs_en); + spi_phytium_set_cmd16(fts, PHYTSPI_MSG_CMD_SET_CS, cs); +} + +static inline void spi_phyt_reset_chip(struct phytium_spi *fts) +{ + spi_phyt_dma_reset(fts, 1); + spi_phyt_enable_chip(fts, 0); + if (fts->global_cs) + spi_phyt_global_cs(fts); + spi_phyt_enable_chip(fts, 1); +} + +static inline void spi_phyt_shutdown_chip(struct phytium_spi *fts) +{ + spi_phyt_enable_chip(fts, 0); + spi_phyt_set_clk(fts, 0); +} + +struct phytium_spi_chip { + u8 poll_mode; + u8 type; + void (*cs_control)(u32 command); +}; + +struct chip_data { + u8 cs; + u8 tmode; + u8 type; + + u8 poll_mode; + + u16 clk_div; + u32 speed_hz; + void (*cs_control)(u32 command); +}; + +static void spi_phyt_set_cs(struct spi_device *spi, bool enable) +{ + struct phytium_spi *fts = spi_master_get_devdata(spi->master); + struct chip_data *chip = spi_get_ctldata(spi); + u32 origin; + u16 cs; + + if (fts->tx || fts->rx) + return; + + if (fts->msg->cmd_id == PHYTSPI_MSG_CMD_DATA && + fts->msg->cmd_subid == PHYTSPI_MSG_CMD_DATA_TX) + return; + + if (chip && chip->cs_control) + chip->cs_control(!enable); + + if (!enable) { + cs = BIT(spi->chip_select); + spi_phytium_set_cmd16(fts, PHYTSPI_MSG_CMD_SET_CS, cs); + if (fts->global_cs) { + origin = (GENMASK(fts->num_cs-1, 0) << fts->num_cs) + | (1 << spi->chip_select); + cs = (0x1 << 8) | origin; + spi_phytium_set_cmd16(fts, PHYTSPI_MSG_CMD_SET_CS, cs); + } + } else { + if (fts->global_cs) { + origin = (GENMASK(fts->num_cs-1, 0) << fts->num_cs) + & ~(1 << spi->chip_select); + cs = (0x1 << 8) | origin; + spi_phytium_set_cmd16(fts, PHYTSPI_MSG_CMD_SET_CS, cs); + } + } +} + +static irqreturn_t spi_phyt_irq(int irq, void *dev_id) +{ + struct spi_master *master = dev_id; + struct phytium_spi *fts = spi_master_get_devdata(master); + + complete(&fts->cmd_completion); + writel_relaxed(0, fts->regfile + SPI_REGFILE_RV2AP_INTR_STATE); + writel_relaxed(0x10, fts->regfile + SPI_REGFILE_RV2AP_INT_CLEAN); + + return IRQ_HANDLED; +} + +static int spi_phyt_transfer_one(struct spi_master *master, + struct spi_device *spi, struct spi_transfer *transfer) +{ + struct phytium_spi *fts = spi_master_get_devdata(master); + struct chip_data *chip = spi_get_ctldata(spi); + int ret; + + fts->tx = (void *)transfer->tx_buf; + fts->tx_end = fts->tx + transfer->len; + fts->rx = transfer->rx_buf; + fts->rx_end = fts->rx + transfer->len; + fts->len = transfer->len; + + if (chip->cs_control) { + if (fts->rx && fts->tx) + chip->tmode = TMOD_TR; + else if (fts->rx) + chip->tmode = TMOD_RO; + else + chip->tmode = TMOD_TO; + } + + if (fts->tx) { + if ((*(u8 *)fts->tx == SPINOR_OP_WREN) && fts->spi_write_flag == 0) { + spi_phytium_write_pre(fts, spi->chip_select, + transfer->bits_per_word, spi->mode, + chip->tmode, 3, fts->spi_write_flag); + fts->spi_write_flag++; + return 0; + } + + if ((*(u8 *)fts->tx == SPINOR_OP_BE_4K) && (fts->spi_write_flag == 1) && + fts->flash_read == 0 && fts->flash_erase != 1) { + fts->spi_write_flag++; + fts->flash_erase = 1; + return 0; + } + + if ((*(u8 *)fts->tx == SPINOR_OP_CHIP_ERASE) && (fts->spi_write_flag == 1) && + fts->flash_read == 0 && fts->flash_erase == 0) { + ret = spi_phytium_flash_erase(fts, spi->chip_select, + transfer->bits_per_word, + spi->mode, chip->tmode, 3, SPINOR_OP_CHIP_ERASE); + fts->spi_write_flag = 0; + fts->flash_erase = 2; + } + + if ((*(u8 *)fts->tx == SPINOR_OP_READ || *(u8 *)fts->tx == SPINOR_OP_READ_FAST || + *(u8 *)fts->tx == SPINOR_OP_READ_4B || + *(u8 *)fts->tx == SPINOR_OP_READ_FAST_4B) && + fts->spi_write_flag == 0 && fts->flash_read == 0 && + fts->flash_erase == 0) { + fts->flash_cmd = *(u8 *)fts->tx; + fts->spi_write_flag++; + fts->flash_read = 1; + return 0; + } + + if ((fts->spi_write_flag == 1) && fts->flash_read == 0 && + fts->flash_write == 0 && ((*(u8 *)fts->tx == SPINOR_OP_PP) || + (*(u8 *)fts->tx == SPINOR_OP_PP_4B))) { + fts->flash_cmd = *(u8 *)fts->tx; + fts->spi_write_flag++; + fts->flash_write = 1; + return 0; + } + + if ((*(u8 *)fts->tx == SPINOR_OP_RDSR) && fts->flash_write == 3) { + fts->read_sr = 1; + fts->flash_write = 0; + return 0; + } + + if ((*(u8 *)fts->tx == SPINOR_OP_RDSR) && fts->flash_erase == 2) { + fts->read_sr = 1; + return 0; + } + } + + if (fts->read_sr) { + *(u8 *)(fts->rx) = 0; + fts->read_sr = 0; + return 0; + } + + if (fts->tx) { + if (fts->flash_erase == 1) { + ret = spi_phytium_flash_erase(fts, spi->chip_select, + transfer->bits_per_word, + spi->mode, chip->tmode, 3, SPINOR_OP_BE_4K); + if (ret) { + dev_err(&master->dev, "flash erase failed\n"); + return ret; + } + fts->spi_write_flag = 0; + fts->flash_erase++; + } else if (fts->flash_read) { + ret = spi_phytium_flash_erase(fts, spi->chip_select, + transfer->bits_per_word, + spi->mode, chip->tmode, 1, fts->flash_cmd); + if (ret) { + dev_err(&master->dev, "transfer read-command failed\n"); + return ret; + } + fts->spi_write_flag = 0; + fts->flash_read = 0; + } else if (fts->flash_write == 1) { + fts->flash_write++; + ret = spi_phytium_flash_write(fts, fts->flash_cmd); + if (ret) { + dev_err(&master->dev, "flash write failed\n"); + return ret; + } + } else if (fts->flash_erase == 2 && (*(u8 *)fts->tx == SPINOR_OP_WRDI)) { + ret = spi_phytium_write(fts, spi->chip_select, transfer->bits_per_word, + spi->mode, chip->tmode, 3, fts->spi_write_flag); + if (ret) { + dev_err(&master->dev, "transfer disable-command failed\n"); + return ret; + } + fts->flash_erase = 0; + } else { + ret = spi_phytium_write(fts, spi->chip_select, transfer->bits_per_word, + spi->mode, chip->tmode, 1, fts->spi_write_flag); + if (ret) { + dev_err(&master->dev, "write command failed\n"); + return ret; + } + if (fts->flash_write == 2) + fts->flash_write++; + fts->spi_write_flag = 0; + } + } + + if (fts->rx) { + ret = spi_phytium_read(fts, spi->chip_select, transfer->bits_per_word, + spi->mode, chip->tmode, 2); + if (ret) { + dev_err(&master->dev, "read data failed\n"); + return ret; + } + } + + return ret; +} + +static void spi_phyt_handle_err(struct spi_master *master, + struct spi_message *msg) +{ + struct phytium_spi *fts = spi_master_get_devdata(master); + + spi_phyt_reset_chip(fts); +} + +static int spi_phyt_setup(struct spi_device *spi) +{ + struct phytium_spi_chip *chip_info = NULL; + struct chip_data *chip; + struct spi_master *master = spi->master; + struct phytium_spi *fts = spi_master_get_devdata(master); + u8 data_width, scph, scpol, tmode; + u16 mode; + u16 clk_div; + + spi_phyt_enable_chip(fts, 0); + + clk_div = (fts->max_freq / spi->max_speed_hz + 1) & 0xfffe; + spi_phyt_set_clk(fts, clk_div); + fts->clk_div = clk_div; + + chip = spi_get_ctldata(spi); + if (!chip) { + chip = kzalloc(sizeof(struct chip_data), GFP_KERNEL); + if (!chip) + return -ENOMEM; + spi_set_ctldata(spi, chip); + } + + chip_info = spi->controller_data; + + if (chip_info) { + if (chip_info->cs_control) + chip->cs_control = chip_info->cs_control; + + chip->poll_mode = chip_info->poll_mode; + chip->type = chip_info->type; + } + + chip->tmode = 0; + + data_width = spi->bits_per_word; + spi_phytium_set_cmd8(fts, PHYTSPI_MSG_CMD_SET_DATA_WIDTH, data_width); + + scph = spi->mode & (0x1); + scpol = spi->mode >> 1; + mode = (scph << 8) | scpol; + spi_phytium_set_cmd16(fts, PHYTSPI_MSG_CMD_SET_MODE, mode); + + tmode = chip->tmode; + spi_phytium_set_cmd8(fts, PHYTSPI_MSG_CMD_SET_TMOD, tmode); + + spi_phyt_enable_chip(fts, 1); + + return 0; +} + +static void spi_phyt_cleanup(struct spi_device *spi) +{ + struct chip_data *chip = spi_get_ctldata(spi); + + kfree(chip); + spi_set_ctldata(spi, NULL); +} + +void spi_phyt_enable_debug(struct phytium_spi *fts) +{ + u32 reg; + + reg = phytium_read_regfile(fts, SPI_REGFILE_DEBUG); + + phytium_write_regfile(fts, SPI_REGFILE_DEBUG, + reg | SPI_REGFILE_DEBUG_VAL); +} + +void spi_phyt_disable_debug(struct phytium_spi *fts) +{ + u32 reg; + + reg = phytium_read_regfile(fts, SPI_REGFILE_DEBUG); + reg &= ~SPI_REGFILE_DEBUG_VAL; + + phytium_write_regfile(fts, SPI_REGFILE_DEBUG, reg); +} + +void spi_phyt_disable_alive(struct phytium_spi *fts) +{ + u32 reg; + + reg = phytium_read_regfile(fts, SPI_REGFILE_DEBUG); + reg &= ~SPI_REGFILE_ALIVE_VAL; + + phytium_write_regfile(fts, SPI_REGFILE_DEBUG, reg); +} + +void spi_watchdog(struct phytium_spi *fts) +{ + u32 reg; + + reg = phytium_read_regfile(fts, SPI_REGFILE_DEBUG); + phytium_write_regfile(fts, SPI_REGFILE_DEBUG, + reg | SPI_REGFILE_HEARTBIT_VAL); +} + +static void spi_phyt_timer_handle(struct timer_list *t) +{ + struct phytium_spi *fts = from_timer(fts, t, timer); + + if (fts->alive_enabled && fts->watchdog) { + if (fts->runtimes < 20) + fts->watchdog(fts); + + fts->runtimes++; + } + + mod_timer(&fts->timer, jiffies + msecs_to_jiffies(10)); +} + +void spi_handle_debug_err(struct phytium_spi *fts) +{ + struct device *dev = &fts->master->dev; + u32 reg, len, i; + + reg = phytium_read_regfile(fts, SPI_REGFILE_DEBUG); + + if (reg & SPI_REGFILE_HAVE_LOG) { + len = strnlen(fts->log, fts->log_size); + dev_info(dev, "log len :%d,addr: 0x%llx,size:%d\n", + len, (u64)fts->log, fts->log_size); + if (len > SPI_LOG_LINE_MAX_LEN) { + for (i = 0; i + SPI_LOG_LINE_MAX_LEN < len; i += SPI_LOG_LINE_MAX_LEN) + dev_info(dev, "(log)%.*s\n", SPI_LOG_LINE_MAX_LEN, &fts->log[i]); + } else { + dev_info(dev, "(log)%.*s\n", SPI_LOG_LINE_MAX_LEN, &fts->log[0]); + } + + for (i = 0; i < fts->log_size; i++) + fts->log[i] = 0; + } + + reg &= ~SPI_REGFILE_HAVE_LOG; + phytium_write_regfile(fts, SPI_REGFILE_DEBUG, reg); +} + +static void spi_phyt_hw_init(struct device *dev, struct phytium_spi *fts) +{ + u32 reg, i; + + spi_phytium_default(fts); + + reg = phytium_read_regfile(fts, SPI_REGFILE_DEBUG); + fts->ddr_paddr = ((reg & SPI_REGFILE_ADDR_MASK) >> 8) << SPI_DDR_ADDR_HIGH; + fts->log_size = ((reg & SPI_REGFILE_SIZE_MASK) >> 4) * SPI_DEBUG_LOG_SIZE; + fts->log = devm_ioremap(dev, fts->ddr_paddr, fts->log_size); + + if (IS_ERR(fts->log)) { + dev_err(dev, "log_addr is err\n"); + return; + } + + for (i = 0; i < fts->log_size; i++) + fts->log[i] = 0; +} + +int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts) +{ + struct spi_master *master; + int ret; + + WARN_ON(fts == NULL); + + master = spi_alloc_master(dev, 0); + if (!master) + return -ENOMEM; + + fts->master = master; + snprintf(fts->name, sizeof(fts->name), "phytium_spi%d", fts->bus_num); + + init_completion(&fts->cmd_completion); + ret = devm_request_irq(dev, fts->irq, spi_phyt_irq, IRQF_SHARED, fts->name, master); + if (ret < 0) { + dev_err(dev, "can not get IRQ\n"); + goto err_free_master; + } + + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP; + master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16); + master->bus_num = fts->bus_num; + master->num_chipselect = fts->num_cs; + master->setup = spi_phyt_setup; + master->cleanup = spi_phyt_cleanup; + master->set_cs = spi_phyt_set_cs; + master->transfer_one = spi_phyt_transfer_one; + master->handle_err = spi_phyt_handle_err; + master->max_speed_hz = fts->max_freq; + master->dev.of_node = dev->of_node; + master->dev.fwnode = dev->fwnode; + master->flags = SPI_CONTROLLER_GPIO_SS; + + spi_master_set_devdata(master, fts); + + spi_phyt_disable_debug(fts); + spi_phyt_disable_alive(fts); + fts->runtimes = 0; + fts->debug_enabled = false; + fts->alive_enabled = false; + + fts->watchdog = spi_watchdog; + fts->handle_debug_err = spi_handle_debug_err; + + fts->timer.expires = jiffies + msecs_to_jiffies(50); + timer_setup(&fts->timer, spi_phyt_timer_handle, 0); + add_timer(&fts->timer); + + spi_phyt_hw_init(dev, fts); + + ret = devm_spi_register_master(dev, master); + if (ret) { + dev_err(&master->dev, "problem registering spi master\n"); + goto err_exit; + } + + return 0; + +err_exit: + spi_phyt_enable_chip(fts, 0); +err_free_master: + spi_master_put(master); + return ret; +} +EXPORT_SYMBOL_GPL(spi_phyt_add_host); + +void spi_phyt_remove_host(struct phytium_spi *fts) +{ + del_timer(&fts->timer); + spi_phyt_shutdown_chip(fts); +} +EXPORT_SYMBOL_GPL(spi_phyt_remove_host); + +int spi_phyt_suspend_host(struct phytium_spi *fts) +{ + int ret; + + ret = spi_controller_suspend(fts->master); + if (ret) + return ret; + + spi_phyt_shutdown_chip(fts); + return 0; +} +EXPORT_SYMBOL_GPL(spi_phyt_suspend_host); + +int spi_phyt_resume_host(struct phytium_spi *fts) +{ + int ret; + + spi_phyt_hw_init(&fts->master->dev, fts); + + spi_phyt_enable_chip(fts, 0); + spi_phyt_set_clk(fts, fts->clk_div); + spi_phyt_enable_chip(fts, 1); + + ret = spi_controller_resume(fts->master); + if (ret) + dev_err(&fts->master->dev, "fail to start queue (%d)\n", ret); + return ret; +} +EXPORT_SYMBOL_GPL(spi_phyt_resume_host); + +MODULE_AUTHOR("Peng Min "); +MODULE_DESCRIPTION("Driver for Phytium SPI controller core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-phytium.h b/drivers/spi/spi-phytium.h index f9530e4c99922..c7e25383a11a5 100644 --- a/drivers/spi/spi-phytium.h +++ b/drivers/spi/spi-phytium.h @@ -55,6 +55,90 @@ #define SPI_DMA_RDMAE (1 << 0) #define SPI_DMA_TDMAE (1 << 1) #define SPI_WAIT_RETRIES 5 + +#define SPI_REGFILE_SIZE (0x48) +#define SPI_REGFILE_AP2RV_INTR_STATE (0x24) +#define SPI_REGFILE_RV2AP_INTR_STATE (0x2c) +#define SPI_REGFILE_RV2AP_INT_CLEAN (0x74) +#define SPI_REGFILE_DEBUG (0x58) + +#define SPI_REGFILE_DEBUG_VAL BIT(0) +#define SPI_REGFILE_ALIVE_VAL BIT(1) +#define SPI_REGFILE_HEARTBIT_VAL BIT(2) +#define SPI_REGFILE_HAVE_LOG BIT(3) +#define SPI_REGFILE_SIZE_MASK GENMASK(7, 4) +#define SPI_REGFILE_ADDR_MASK GENMASK(27, 8) + +#define SPI_DDR_ADDR_HIGH 12 +#define SPI_DEBUG_LOG_SIZE 4096 + +#define SPI_LOG_LINE_MAX_LEN 400 + +#define SPI_MODULE_OPT_CMD 0x20 + +#define SPI_TRANS_DATA_SIZE 1024 +#define FLASH_PAGE_SIZE 256 + +#define SPI_MSG_COMPLETE_OK 1 +#define SPI_MSG_COMPLETE_KO 0 + +#define SPI_RESULT_IGNORE_LVL (0) +#define SPI_RESULT_WRITE_ISR_LVL (1) +#define SPI_RESULT_READ_ISR_LVL (2) + +#define SPI_SHMEM_TX_MSG_MAX_CNT 1 + +#define SPI_MASTER_TIMEOUT 8 + +#define SPI_DEFAULT_CLK 50000000 + +enum phytspi_msg_cmd_id { + PHYTSPI_MSG_CMD_DEFAULT = 0, + PHYTSPI_MSG_CMD_SET, + PHYTSPI_MSG_CMD_GET, + PHYTSPI_MSG_CMD_DATA, + PHYTSPI_MSG_CMD_REPORT, +}; + +enum phytspi_set_subid { + PHYTSPI_MSG_CMD_SET_MODULE_EN = 0, + PHYTSPI_MSG_CMD_SET_DATA_WIDTH, + PHYTSPI_MSG_CMD_SET_MODE, + PHYTSPI_MSG_CMD_SET_TMOD, + PHYTSPI_MSG_CMD_SET_BAUDR, + PHYTSPI_MSG_CMD_SET_INT_TI, + PHYTSPI_MSG_CMD_SET_NDF, + PHYTSPI_MSG_CMD_SET_CS, + PHYTSPI_MSG_CMD_SET_DMA_RESET, +}; + +enum phytspi_data_subid { + PHYTSPI_MSG_CMD_DATA_TX = 0, + PHYTSPI_MSG_CMD_DATA_RX, + PHYTSPI_MSG_CMD_DATA_FLASH_TX, + PHYTSPI_MSG_CMD_FLASH_ERASE, + PHYTSPI_MSG_CMD_DATA_DMA_TX, + PHYTSPI_MSG_CMD_DATA_DMA_RX, + PHYTSPI_MSG_CMD_DATA_FLASH_DMA_TX, +}; + +struct msg { + u8 reserved; + u8 seq; + u8 cmd_id; + u8 cmd_subid; + u16 len; + u8 status1; + u8 status0; + u8 data[56]; +}; + +struct spi_trans_msg_info { + u32 msg_total_num; + u32 shmem_data_addr; + int result; +}; + struct phytium_spi; struct phytium_spi_dma_ops { int (*dma_init)(struct device *dev, struct phytium_spi *fts); @@ -71,8 +155,22 @@ struct phytium_spi { char name[16]; void __iomem *regs; + void __iomem *regfile; + void __iomem *tx_shmem_addr; + void *rx_shmem_addr; + + struct msg *msg; + u32 mem_tx_physic; + u32 mem_rx_physic; + u64 mem_tx; + u64 mem_rx; + + u16 clk_div; + int module; + bool global_cs; bool dma_en; + bool half_duplex; unsigned long paddr; int irq; u32 fifo_len; @@ -90,7 +188,31 @@ struct phytium_spi { void *rx_end; u8 n_bytes; int dma_mapped; + struct clk *clk; irqreturn_t (*transfer_handler)(struct phytium_spi *fts); + + int cmd_err; + u32 cur_tx_tail; + struct completion cmd_completion; + + u8 flags; + u8 spi_write_flag; + u8 flash_erase; + u8 flash_read; + u8 flash_write; + u8 read_sr; + u8 flash_cmd; + + bool debug_enabled; + bool alive_enabled; + struct timer_list timer; + u32 runtimes; // for debug + u64 ddr_paddr; + char *log; + u32 log_size; + void (*watchdog)(struct phytium_spi *fts); + void (*handle_debug_err)(struct phytium_spi *fts); + /* DMA info */ u32 current_freq; /* frequency in hz */ struct dma_chan *txchan; @@ -102,6 +224,8 @@ struct phytium_spi { dma_addr_t dma_addr; /* phy address of the Data register */ const struct phytium_spi_dma_ops *dma_ops; struct completion dma_completion; + + bool dma_get_ddrdata; }; static inline u32 phytium_readl(struct phytium_spi *fts, u32 offset) From b127bad4e875760f9be77021b16db8cd1388efde Mon Sep 17 00:00:00 2001 From: Cui Fulong Date: Tue, 10 Mar 2026 17:17:34 +0800 Subject: [PATCH 023/101] spi-v2: phytium: Adapt the mcp251x device The spi-v2 driver is adapted to the mcp251x device. Furthermore, spi-v2 currently only supports half-duplex. Mainline: Open-Source Signed-off-by: Cui Fulong Signed-off-by: Peng Min Signed-off-by: Wang Yinfeng --- drivers/spi/spi-phytium-v2.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-phytium-v2.c b/drivers/spi/spi-phytium-v2.c index 53d7f38e8708e..dbf834f0e13c4 100644 --- a/drivers/spi/spi-phytium-v2.c +++ b/drivers/spi/spi-phytium-v2.c @@ -25,6 +25,10 @@ #include #include "spi-phytium.h" +#define MCP251x_READ 0x03 +#define MCP251x_READ_RXB0 0x90 +#define MCP251x_READ_RXB1 0x94 + static inline void spi_phyt_enable_chip(struct phytium_spi *fts, u8 enable) { u8 val = enable ? 1 : 2; @@ -158,7 +162,7 @@ static int spi_phyt_transfer_one(struct spi_master *master, chip->tmode = TMOD_TO; } - if (fts->tx) { + if (fts->tx && fts->len == 1) { if ((*(u8 *)fts->tx == SPINOR_OP_WREN) && fts->spi_write_flag == 0) { spi_phytium_write_pre(fts, spi->chip_select, transfer->bits_per_word, spi->mode, @@ -258,8 +262,14 @@ static int spi_phyt_transfer_one(struct spi_master *master, } fts->flash_erase = 0; } else { + fts->flags = 1; + if (fts->spi_write_flag == 0 && *(u8 *)(fts->tx) != MCP251x_READ + && *(u8 *)(fts->tx) != MCP251x_READ_RXB0 + && *(u8 *)(fts->tx) != MCP251x_READ_RXB1) + fts->flags = 3; + ret = spi_phytium_write(fts, spi->chip_select, transfer->bits_per_word, - spi->mode, chip->tmode, 1, fts->spi_write_flag); + spi->mode, chip->tmode, fts->flags, fts->spi_write_flag); if (ret) { dev_err(&master->dev, "write command failed\n"); return ret; @@ -483,6 +493,7 @@ int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts) master->dev.of_node = dev->of_node; master->dev.fwnode = dev->fwnode; master->flags = SPI_CONTROLLER_GPIO_SS; + master->flags |= SPI_CONTROLLER_HALF_DUPLEX; spi_master_set_devdata(master, fts); From 839098a4be05c61d0c23babcff17a6eb86464e1c Mon Sep 17 00:00:00 2001 From: Xia Qian Date: Mon, 8 Jun 2026 13:57:16 +0800 Subject: [PATCH 024/101] qspi: phytium: Use the corresponding configurations for each chip Solve the problem of abnormal reading and writing of data when simultaneously accessing flash chips of different capacities. Mainline: Open-Source Signed-off-by: Peng Min Signed-off-by: Wang Yinfeng Signed-off-by: Xia Qian --- drivers/spi/spi-phytium-qspi.c | 37 ++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index 2296078a943cc..746c0d6706df7 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -32,6 +32,7 @@ #define QSPI_FLASH_CAP_NUM_MASK (0x3 << QSPI_FLASH_CAP_NUM_SHIFT) #define QSPI_FLASH_CAP_CAP_SHIFT 0 #define QSPI_FLASH_CAP_CAP_MASK (0x7 << QSPI_FLASH_CAP_CAP_SHIFT) +#define QSPI_FLASH_CAP_NUM_SHIFT_NEW 16 #define QSPI_RD_CFG_REG 0x04 #define QSPI_RD_CFG_RD_CMD_SHIFT 24 @@ -70,6 +71,7 @@ #define QSPI_WR_CFG_WR_MODE_MASK (0x1 << QSPI_WR_CFG_WR_MODE_SHIFT) #define QSPI_WR_CFG_WR_SCK_SEL_SHIFT 0 #define QSPI_WR_CFG_WR_SCK_SEL_MASK (0x7 << QSPI_WR_CFG_WR_SCK_SEL_SHIFT) +#define WR_CFG_NODIR_VALUE 0x5000000 #define QSPI_FLUSH_REG 0x0c #define QSPI_FLUSH_EN (0x1 << 0) @@ -175,6 +177,10 @@ struct phytium_qspi { struct phytium_qspi_flash flash[PHYTIUM_QSPI_MAX_NORCHIP]; u8 fnum; bool nodirmap; + + u32 wr_cfg_reg[PHYTIUM_QSPI_MAX_NORCHIP]; + u32 rd_cfg_reg[PHYTIUM_QSPI_MAX_NORCHIP]; + u32 flash_cap; }; static bool phytium_qspi_check_buswidth(u8 width) @@ -513,6 +519,7 @@ static int phytium_qspi_dirmap_create(struct spi_mem_dirmap_desc *desc) cmd |= flash->clk_div & QSPI_RD_CFG_RD_SCK_SEL_MASK; writel_relaxed(cmd, qspi->io_base + QSPI_RD_CFG_REG); + qspi->rd_cfg_reg[spi_get_chipselect(spi, 0)] = cmd; dev_dbg(qspi->dev, "Create read dirmap and setup RD_CFG_REG [%#x].\n", cmd); } else if (desc->info.op_tmpl->data.dir == SPI_MEM_DATA_OUT) { @@ -529,10 +536,7 @@ static int phytium_qspi_dirmap_create(struct spi_mem_dirmap_desc *desc) cmd |= QSPI_WR_CFG_WR_MODE_MASK; cmd |= flash->clk_div & QSPI_WR_CFG_WR_SCK_SEL_MASK; - - writel_relaxed(cmd, qspi->io_base + QSPI_WR_CFG_REG); - - dev_dbg(qspi->dev, "Create write dirmap and setup WR_CFG_REG [%#x].\n", cmd); + qspi->wr_cfg_reg[spi_get_chipselect(spi, 0)] = cmd; } else { ret = -EINVAL; } @@ -551,6 +555,7 @@ static ssize_t phytium_qspi_dirmap_read(struct spi_mem_dirmap_desc *desc, void __iomem *src = flash->base + offs; u8 *buf_rx = buf; + writel_relaxed(qspi->rd_cfg_reg[spi_get_chipselect(spi, 0)], qspi->io_base + QSPI_RD_CFG_REG); memcpy_fromio(buf_rx, src, len); return len; @@ -569,6 +574,9 @@ static ssize_t phytium_qspi_dirmap_write(struct spi_mem_dirmap_desc *desc, size_t mask = 0x03; u_char tmp[4] = {0}; + /* set wr_cfg for drimap write */ + writel_relaxed(qspi->wr_cfg_reg[spi_get_chipselect(spi, 0)], qspi->io_base + QSPI_WR_CFG_REG); + if (offs & 0x03) { dev_err(qspi->dev, "Addr not four-byte aligned!\n"); return -EINVAL; @@ -794,6 +802,18 @@ static int phytium_qspi_probe(struct platform_device *pdev) flash_cap |= qspi->fnum << QSPI_FLASH_CAP_NUM_SHIFT; writel_relaxed(flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); + } else { + for (i = 0; qspi->fnum > i && i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { + ret = phytium_qspi_flash_capacity_encode_new(qspi->flash[i].size, + &qspi->flash_cap, i); + if (ret) { + dev_err(dev, "Flash size is invalid.\n"); + goto probe_setup_failed; + } + } + qspi->flash_cap |= (qspi->fnum - 1) << QSPI_FLASH_CAP_NUM_SHIFT_NEW; + + writel_relaxed(qspi->flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); } return 0; @@ -835,6 +855,15 @@ static int __maybe_unused phytium_qspi_suspend(struct device *dev) static int __maybe_unused phytium_qspi_resume(struct device *dev) { + struct phytium_qspi *qspi = dev_get_drvdata(dev); + + if (!qspi->nodirmap) { + /* set rd_cfg reg and flash_capacity reg after resume */ + writel_relaxed(qspi->flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); + } else { + writel_relaxed(WR_CFG_NODIR_VALUE, qspi->io_base + QSPI_WR_CFG_REG); + } + return pm_runtime_force_resume(dev); } From 67bb3cf77b399d86401e47dc6c7d7bf3c176d8f3 Mon Sep 17 00:00:00 2001 From: WangYuli Date: Thu, 6 Aug 2026 22:30:26 +0800 Subject: [PATCH 025/101] spi: phytium-qspi: Fix uninitialized 'res' in probe Clang warns that 'res' may be used uninitialized in phytium_qspi_probe(): drivers/spi/spi-phytium-qspi.c:717:11: error: variable 'res' is used uninitialized whenever 'if' condition is false [-Werror,-Wsometimes-uninitialized] 717 | else if (has_acpi_companion(dev)) { | ^~~~~~~~~~~~~~~~~~~~~~~ drivers/spi/spi-phytium-qspi.c:723:45: note: uninitialized use occurs here 723 | qspi->io_base = devm_ioremap_resource(dev, res); | ^~~ drivers/spi/spi-phytium-qspi.c:717:7: note: remove the 'if' if its condition is always true 717 | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/spi/spi-phytium-qspi.c:688:22: note: initialize the variable 'res' to silence this warning 688 | struct resource *res; | ^ | = NULL 1 error generated. 'res' is only assigned inside the 'if (dev->of_node)' / 'else if (has_acpi_companion(dev))' branches. When a device has neither a DT node nor an ACPI companion, neither branch is taken and 'res' is passed uninitialized to devm_ioremap_resource(). GCC does not report this, but with CONFIG_WERROR=y a clang build fails. Initialize 'res' to NULL so that devm_ioremap_resource() handles the missing-resource case gracefully (it returns -EINVAL for a NULL resource) instead of consuming an uninitialized pointer. Fixes: 7f4fb2e5cdaf ("arm64: phytium: UEFI mode acpi table support for qspi/spi driver") Assisted-by: GitHub Copilot:deepseek-v4-flash Signed-off-by: WangYuli --- drivers/spi/spi-phytium-qspi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index 746c0d6706df7..fba79be38a4fc 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -664,7 +664,7 @@ static int phytium_qspi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct spi_controller *ctrl; - struct resource *res; + struct resource *res = NULL; struct phytium_qspi *qspi; int i, ret; u32 flash_cap; From f61cb55a1a1691e9b1aa20355679ced5cf073765 Mon Sep 17 00:00:00 2001 From: Peng Min Date: Mon, 10 Nov 2025 16:00:43 +0800 Subject: [PATCH 026/101] spi-v2: phytium: Restricted adaptation platform for SPI driver This driver is exclusively for the PHYTIUM platform and is not compatible with other SoCs. This restriction prevents errors on unsupported platform. Mainline: Open-Source Signed-off-by: Peng Min Signed-off-by: Wang Yinfeng Change-Id: Ic613fa79fd85a1a7bc8cf87e9b5e1c68eaf124dd --- drivers/spi/Kconfig | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 62e29d4126697..1d86a4de67fef 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -892,6 +892,19 @@ config SPI_PHYTIUM_QSPI If unsure, say N. +config SPI_PHYTIUM_V2 + tristate "spi phytium v2" + depends on ARCH_PHYTIUM + help + This config is similar to the "SPI_PHYTIUM" config. + +config SPI_PHYTIUM_PLAT_V2 + tristate "Phytium SPI-v2 controller platform support" + select SPI_PHYTIUM_V2 + depends on ARCH_PHYTIUM + help + This config is similar to the "SPI_PHYTIUM_PLAT" config. + config SPI_PIC32 tristate "Microchip PIC32 series SPI" depends on MACH_PIC32 || COMPILE_TEST From d3d422e31833411f7da221b2b1af3feaf8dea790 Mon Sep 17 00:00:00 2001 From: Peng Min Date: Mon, 18 Aug 2025 16:01:44 +0800 Subject: [PATCH 027/101] spi: phytium: Use global-cs register to select chip by default The driver uses global-cs register(0x100) for chip selection by default. Slove the problem of not being able to read the device ID when using the internal chip selection register(0x10). Mainline: Open-Source Signed-off-by: Peng Min Signed-off-by: Wang Yinfeng Change-Id: I6deab9ce5a572d1e7e1f67ada9983cad550b9411 --- drivers/spi/spi-phytium-plat.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-phytium-plat.c b/drivers/spi/spi-phytium-plat.c index eb2b3d837693b..fd24684bd4d91 100644 --- a/drivers/spi/spi-phytium-plat.c +++ b/drivers/spi/spi-phytium-plat.c @@ -28,6 +28,9 @@ #include "spi-phytium.h" #define DRIVER_NAME "phytium_spi" +#define DRIVER_VERSION "1.0.1" + +#define SPI_PHYTIUM_DEFAULT_CLK_RATE 50000000 struct phytium_spi_clk { struct phytium_spi fts; @@ -41,7 +44,8 @@ static int phytium_spi_probe(struct platform_device *pdev) struct resource *mem; int ret; int num_cs; - int global_cs; + int global_cs = 1; + u32 clk_rate = SPI_PHYTIUM_DEFAULT_CLK_RATE; ftsc = devm_kzalloc(&pdev->dev, sizeof(struct phytium_spi_clk), GFP_KERNEL); From 58df49af004472a70f654bde5d732f85429b7793 Mon Sep 17 00:00:00 2001 From: Peng Min Date: Tue, 18 Nov 2025 09:25:15 +0800 Subject: [PATCH 028/101] spi: phytium: Restricted adaptation platform for SPI driver This driver is exclusively for the PHYTIUM platform and is not compatible with other SoCs. This restriction prevents errors on unsupported platform. Mainline: Open-Source Signed-off-by: Peng Min Signed-off-by: Wang Yinfeng Change-Id: Ib6ce4ada8ac4ff88c96289a00e8482010a4ceb9a --- drivers/spi/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 1d86a4de67fef..6e478567ab11f 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -854,7 +854,7 @@ config SPI_PCI1XXXX config SPI_PHYTIUM tristate - depends on ARCH_PHYTIUM || COMPILE_TEST + depends on ARCH_PHYTIUM config SPI_PHYTIUM_PLAT tristate "Phytium SPI controller platform support" From 5e49cfbb5912bc5c97c2993901b26d5b7603f8ff Mon Sep 17 00:00:00 2001 From: Peng Min Date: Thu, 4 Dec 2025 09:18:43 +0800 Subject: [PATCH 029/101] spi-v2: phytium: Add support for full-duplex transmission mode In order to accommodate devices such as spidev and tpm that support full-duplex transmission, full-duplex support has been added to the spi-v2 driver. Mainline: NA Signed-off-by: Peng Min Signed-off-by: Wang Yinfeng Change-Id: I6b5a96337b8713ed5300d4c1651ea2d14c74bfdb --- drivers/spi/spi-phytium-common.c | 70 +++++++++++++++++++++++++++++++ drivers/spi/spi-phytium-plat-v2.c | 2 +- drivers/spi/spi-phytium-v2.c | 46 ++++++++++++++------ drivers/spi/spi-phytium.h | 56 +++++++++++++++++++++---- 4 files changed, 153 insertions(+), 21 deletions(-) diff --git a/drivers/spi/spi-phytium-common.c b/drivers/spi/spi-phytium-common.c index 96a30a01ebe50..8ff76bb1565aa 100644 --- a/drivers/spi/spi-phytium-common.c +++ b/drivers/spi/spi-phytium-common.c @@ -452,6 +452,76 @@ int spi_phytium_read(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, } EXPORT_SYMBOL_GPL(spi_phytium_read); +int spi_phytium_xfer(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, + u8 tmode, u8 flags) +{ + int ret; + u32 len; + u64 smem_tx, smem_rx; + u8 first = 1; + u64 tx_addr, rx_addr; + u64 *data = (u64 *)fts->tx; + + do { + if (fts->dma_get_ddrdata) + len = min_t(u32, (u32)(fts->rx_end - fts->rx), + (u32)(fts->rx_end - fts->rx)); + else + len = min_t(u32, (u32)(fts->rx_end - fts->rx), 128); + + fts->msg->cmd_id = PHYTSPI_MSG_CMD_DATA; + + smem_tx = (u64)fts->msg + sizeof(struct msg); + smem_rx = (u64)fts->msg + sizeof(struct msg) + 128; + + if (len > 16 && fts->dma_get_ddrdata) { + fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_DMA_XFER; + tx_addr = __virt_to_phys((u64)fts->tx); + if (!tx_addr) { + dev_err(&fts->master->dev, "tx address translation failed\n"); + return -1; + } + rx_addr = __virt_to_phys((u64)fts->rx); + if (!rx_addr) { + dev_err(&fts->master->dev, "rx address translation failed\n"); + return -1; + } + + *(u64 *)&fts->msg->data[0] = tx_addr; + *(u64 *)&fts->msg->data[8] = rx_addr; + } else { + fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_XFER; + memcpy_byte((void *)smem_tx, fts->tx, len); + *(u64 *)&fts->msg->data[0] = sizeof(struct msg); + *(u64 *)&fts->msg->data[8] = sizeof(struct msg) + 128; + } + + *(u32 *)&fts->msg->data[16] = len; + fts->msg->data[20] = cs; + fts->msg->data[21] = dfs; + fts->msg->data[22] = mode; + fts->msg->data[23] = tmode; + if (first == 1) + fts->msg->data[24] = 1; + else + fts->msg->data[24] = flags; + fts->msg->data[24] = first; + ret = spi_phytium_set(fts); + if (ret) { + dev_err(&fts->master->dev, "AP <-> RV interaction failed\n"); + return ret; + } + if (len <= 16 || !fts->dma_get_ddrdata) + memcpy_byte(fts->rx, (void *)smem_rx, len); + + fts->rx += len; + first = 0; + } while (fts->rx_end > fts->rx); + + return ret; +} +EXPORT_SYMBOL_GPL(spi_phytium_xfer); + MODULE_AUTHOR("Peng Min "); MODULE_DESCRIPTION("Phytium SPI adapter core"); MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-phytium-plat-v2.c b/drivers/spi/spi-phytium-plat-v2.c index 7dff26be48df3..45664dc2b2db3 100644 --- a/drivers/spi/spi-phytium-plat-v2.c +++ b/drivers/spi/spi-phytium-plat-v2.c @@ -26,7 +26,7 @@ #include "spi-phytium.h" #define DRIVER_NAME_PHYT "phytium_spi_2.0" -#define DRIVER_VERSION "1.0.7" +#define DRIVER_VERSION "1.0.14" #define PHYTIUM_CPU_PART_FTC872 0x872 diff --git a/drivers/spi/spi-phytium-v2.c b/drivers/spi/spi-phytium-v2.c index dbf834f0e13c4..2ff3fd65b80ce 100644 --- a/drivers/spi/spi-phytium-v2.c +++ b/drivers/spi/spi-phytium-v2.c @@ -110,18 +110,18 @@ static void spi_phyt_set_cs(struct spi_device *spi, bool enable) chip->cs_control(!enable); if (!enable) { - cs = BIT(spi->chip_select); + cs = BIT(spi_get_chipselect(spi, 0)); spi_phytium_set_cmd16(fts, PHYTSPI_MSG_CMD_SET_CS, cs); if (fts->global_cs) { origin = (GENMASK(fts->num_cs-1, 0) << fts->num_cs) - | (1 << spi->chip_select); + | (1 << spi_get_chipselect(spi, 0)); cs = (0x1 << 8) | origin; spi_phytium_set_cmd16(fts, PHYTSPI_MSG_CMD_SET_CS, cs); } } else { if (fts->global_cs) { origin = (GENMASK(fts->num_cs-1, 0) << fts->num_cs) - & ~(1 << spi->chip_select); + & ~(1 << spi_get_chipselect(spi, 0)); cs = (0x1 << 8) | origin; spi_phytium_set_cmd16(fts, PHYTSPI_MSG_CMD_SET_CS, cs); } @@ -145,8 +145,13 @@ static int spi_phyt_transfer_one(struct spi_master *master, { struct phytium_spi *fts = spi_master_get_devdata(master); struct chip_data *chip = spi_get_ctldata(spi); + struct spi_mem *mem = spi_get_drvdata(spi); + struct spi_nor *nor = NULL; int ret; + if (mem) + nor = spi_mem_get_drvdata(mem); + fts->tx = (void *)transfer->tx_buf; fts->tx_end = fts->tx + transfer->len; fts->rx = transfer->rx_buf; @@ -162,9 +167,19 @@ static int spi_phyt_transfer_one(struct spi_master *master, chip->tmode = TMOD_TO; } - if (fts->tx && fts->len == 1) { + if (fts->tx && fts->rx) { + if (fts->half_duplex) { + dev_err(&master->dev, "SPI-V2 not support full duplex\n"); + return -EPERM; + } + ret = spi_phytium_xfer(fts, spi_get_chipselect(spi, 0), transfer->bits_per_word, + spi->mode, chip->tmode, 0); + return ret; + } + + if (mem != NULL && nor != NULL && mem == nor->spimem && fts->tx && fts->len == 1) { if ((*(u8 *)fts->tx == SPINOR_OP_WREN) && fts->spi_write_flag == 0) { - spi_phytium_write_pre(fts, spi->chip_select, + spi_phytium_write_pre(fts, spi_get_chipselect(spi, 0), transfer->bits_per_word, spi->mode, chip->tmode, 3, fts->spi_write_flag); fts->spi_write_flag++; @@ -180,7 +195,7 @@ static int spi_phyt_transfer_one(struct spi_master *master, if ((*(u8 *)fts->tx == SPINOR_OP_CHIP_ERASE) && (fts->spi_write_flag == 1) && fts->flash_read == 0 && fts->flash_erase == 0) { - ret = spi_phytium_flash_erase(fts, spi->chip_select, + ret = spi_phytium_flash_erase(fts, spi_get_chipselect(spi, 0), transfer->bits_per_word, spi->mode, chip->tmode, 3, SPINOR_OP_CHIP_ERASE); fts->spi_write_flag = 0; @@ -227,7 +242,7 @@ static int spi_phyt_transfer_one(struct spi_master *master, if (fts->tx) { if (fts->flash_erase == 1) { - ret = spi_phytium_flash_erase(fts, spi->chip_select, + ret = spi_phytium_flash_erase(fts, spi_get_chipselect(spi, 0), transfer->bits_per_word, spi->mode, chip->tmode, 3, SPINOR_OP_BE_4K); if (ret) { @@ -237,7 +252,7 @@ static int spi_phyt_transfer_one(struct spi_master *master, fts->spi_write_flag = 0; fts->flash_erase++; } else if (fts->flash_read) { - ret = spi_phytium_flash_erase(fts, spi->chip_select, + ret = spi_phytium_flash_erase(fts, spi_get_chipselect(spi, 0), transfer->bits_per_word, spi->mode, chip->tmode, 1, fts->flash_cmd); if (ret) { @@ -254,7 +269,7 @@ static int spi_phyt_transfer_one(struct spi_master *master, return ret; } } else if (fts->flash_erase == 2 && (*(u8 *)fts->tx == SPINOR_OP_WRDI)) { - ret = spi_phytium_write(fts, spi->chip_select, transfer->bits_per_word, + ret = spi_phytium_write(fts, spi_get_chipselect(spi, 0), transfer->bits_per_word, spi->mode, chip->tmode, 3, fts->spi_write_flag); if (ret) { dev_err(&master->dev, "transfer disable-command failed\n"); @@ -268,7 +283,7 @@ static int spi_phyt_transfer_one(struct spi_master *master, && *(u8 *)(fts->tx) != MCP251x_READ_RXB1) fts->flags = 3; - ret = spi_phytium_write(fts, spi->chip_select, transfer->bits_per_word, + ret = spi_phytium_write(fts, spi_get_chipselect(spi, 0), transfer->bits_per_word, spi->mode, chip->tmode, fts->flags, fts->spi_write_flag); if (ret) { dev_err(&master->dev, "write command failed\n"); @@ -281,7 +296,7 @@ static int spi_phyt_transfer_one(struct spi_master *master, } if (fts->rx) { - ret = spi_phytium_read(fts, spi->chip_select, transfer->bits_per_word, + ret = spi_phytium_read(fts, spi_get_chipselect(spi, 0), transfer->bits_per_word, spi->mode, chip->tmode, 2); if (ret) { dev_err(&master->dev, "read data failed\n"); @@ -493,7 +508,14 @@ int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts) master->dev.of_node = dev->of_node; master->dev.fwnode = dev->fwnode; master->flags = SPI_CONTROLLER_GPIO_SS; - master->flags |= SPI_CONTROLLER_HALF_DUPLEX; + + fts->half_duplex = false; + if (!(phytium_read_regfile(fts, SPI_REGFILE_SOFTWARE2) + & SPI_REGFILE_FULL_DUPLEX)) { + dev_warn(dev, "SPI-V2 only support half duplex\n"); + fts->half_duplex = true; + master->flags |= SPI_CONTROLLER_HALF_DUPLEX; + } spi_master_set_devdata(master, fts); diff --git a/drivers/spi/spi-phytium.h b/drivers/spi/spi-phytium.h index c7e25383a11a5..500e08d34119f 100644 --- a/drivers/spi/spi-phytium.h +++ b/drivers/spi/spi-phytium.h @@ -56,18 +56,23 @@ #define SPI_DMA_TDMAE (1 << 1) #define SPI_WAIT_RETRIES 5 -#define SPI_REGFILE_SIZE (0x48) #define SPI_REGFILE_AP2RV_INTR_STATE (0x24) #define SPI_REGFILE_RV2AP_INTR_STATE (0x2c) -#define SPI_REGFILE_RV2AP_INT_CLEAN (0x74) +#define SPI_REGFILE_SIZE (0x48) +#define SPI_REGFILE_DDR_HIGH_REG (0x4c) + +#define SPI_REGFILE_SOFTWARE2 (0x54) +#define SPI_REGFILE_FULL_DUPLEX BIT(9) + #define SPI_REGFILE_DEBUG (0x58) +#define SPI_REGFILE_DEBUG_VAL BIT(0) +#define SPI_REGFILE_ALIVE_VAL BIT(1) +#define SPI_REGFILE_HEARTBIT_VAL BIT(2) +#define SPI_REGFILE_HAVE_LOG BIT(3) +#define SPI_REGFILE_SIZE_MASK GENMASK(7, 4) +#define SPI_REGFILE_ADDR_MASK GENMASK(27, 8) -#define SPI_REGFILE_DEBUG_VAL BIT(0) -#define SPI_REGFILE_ALIVE_VAL BIT(1) -#define SPI_REGFILE_HEARTBIT_VAL BIT(2) -#define SPI_REGFILE_HAVE_LOG BIT(3) -#define SPI_REGFILE_SIZE_MASK GENMASK(7, 4) -#define SPI_REGFILE_ADDR_MASK GENMASK(27, 8) +#define SPI_REGFILE_RV2AP_INT_CLEAN (0x74) #define SPI_DDR_ADDR_HIGH 12 #define SPI_DEBUG_LOG_SIZE 4096 @@ -120,6 +125,8 @@ enum phytspi_data_subid { PHYTSPI_MSG_CMD_DATA_DMA_TX, PHYTSPI_MSG_CMD_DATA_DMA_RX, PHYTSPI_MSG_CMD_DATA_FLASH_DMA_TX, + PHYTSPI_MSG_CMD_DATA_XFER, + PHYTSPI_MSG_CMD_DATA_DMA_XFER, }; struct msg { @@ -312,6 +319,39 @@ static inline void spi_shutdown_chip(struct phytium_spi *fts) spi_set_clk(fts, 0); fts->current_freq = 0; } + +static inline u32 phytium_read_regfile(struct phytium_spi *fts, u32 reg_off) +{ + return readl_relaxed(fts->regfile + reg_off); +} + +static inline void phytium_write_regfile(struct phytium_spi *fts, u32 reg_off, u32 val) +{ + writel_relaxed(val, fts->regfile + reg_off); +} + +extern void spi_phytium_default(struct phytium_spi *fts); +extern void spi_phytium_set_cmd8(struct phytium_spi *fts, u16 sub_cmd, u8 data); +extern void spi_phytium_set_cmd16(struct phytium_spi *fts, u16 sub_cmd, u16 data); +extern void spi_phytium_set_cmd32(struct phytium_spi *fts, u16 sub_cmd, u32 data); +extern void spi_phytium_data_cmd_write(struct phytium_spi *fts, u16 sub_cmd); +extern void spi_phytium_data_cmd_read(struct phytium_spi *fts, u16 sub_cmd); +extern void spi_phytium_write_pre(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, u8 tmode, + u8 flags, u8 spi_write_flag); +extern int spi_phytium_flash_erase(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, + u8 tmode, u8 flags, u8 cmd); +extern int spi_phytium_flash_write(struct phytium_spi *fts, u8 cmd); +extern int spi_phytium_write(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, + u8 tmode, u8 flags, u8 spi_write_flag); +extern int spi_phytium_read(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, + u8 tmode, u8 flags); +extern int spi_phytium_xfer(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, + u8 tmode, u8 flags); +extern int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts); +extern void spi_phyt_remove_host(struct phytium_spi *fts); +extern int spi_phyt_suspend_host(struct phytium_spi *fts); +extern int spi_phyt_resume_host(struct phytium_spi *fts); + extern int phytium_spi_add_host(struct device *dev, struct phytium_spi *fts); extern void phytium_spi_remove_host(struct phytium_spi *fts); extern int phytium_spi_suspend_host(struct phytium_spi *fts); From e3e99fac6a8e3b9cb653602f538388a7faa977b9 Mon Sep 17 00:00:00 2001 From: Peng Yao Date: Thu, 18 Dec 2025 17:31:52 +0800 Subject: [PATCH 030/101] spi-v2: phytium: Add support ddr addr 0-45bit. Determine whether to enable DMA andwhether it is compatible with 32-bit and 45-bit physical memory addresses by reading the regfile version register added to the spi-v2 driver. Mainline: NA Signed-off-by: Peng Yao Signed-off-by: Wang Yinfeng Change-Id: I4402a96de7f60f31f2a245d33832f45839c39403 --- drivers/spi/spi-phytium-plat-v2.c | 8 ++++---- drivers/spi/spi-phytium-v2.c | 12 ++++++++++-- drivers/spi/spi-phytium.h | 8 ++++++++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-phytium-plat-v2.c b/drivers/spi/spi-phytium-plat-v2.c index 45664dc2b2db3..88ea8bc324447 100644 --- a/drivers/spi/spi-phytium-plat-v2.c +++ b/drivers/spi/spi-phytium-plat-v2.c @@ -28,9 +28,7 @@ #define DRIVER_NAME_PHYT "phytium_spi_2.0" #define DRIVER_VERSION "1.0.14" -#define PHYTIUM_CPU_PART_FTC872 0x872 -#define MIDR_PHYTIUM_FTC872 MIDR_CPU_MODEL(ARM_CPU_IMP_PHYTIUM, PHYTIUM_CPU_PART_FTC872) static ssize_t debug_show(struct device *dev, struct device_attribute *da, @@ -247,9 +245,11 @@ static int spi_phyt_probe(struct platform_device *pdev) device_property_read_u32(&pdev->dev, "global-cs", &global_cs); fts->global_cs = global_cs; - fts->dma_get_ddrdata = false; - if ((read_cpuid_id() & MIDR_CPU_MODEL_MASK) == MIDR_PHYTIUM_FTC872) + fts->regfile_version = phytium_read_regfile(fts, SPI_REGFILE_VERSION_REG); + if (fts->regfile_version & SPI_REGFILE_VERSION_DMA) fts->dma_get_ddrdata = true; + else + fts->dma_get_ddrdata = false; ret = spi_phyt_add_host(&pdev->dev, fts); if (ret) diff --git a/drivers/spi/spi-phytium-v2.c b/drivers/spi/spi-phytium-v2.c index 2ff3fd65b80ce..1cd633f0a3a67 100644 --- a/drivers/spi/spi-phytium-v2.c +++ b/drivers/spi/spi-phytium-v2.c @@ -456,12 +456,20 @@ void spi_handle_debug_err(struct phytium_spi *fts) static void spi_phyt_hw_init(struct device *dev, struct phytium_spi *fts) { - u32 reg, i; + u32 reg, i, reg_ddr_high; spi_phytium_default(fts); reg = phytium_read_regfile(fts, SPI_REGFILE_DEBUG); - fts->ddr_paddr = ((reg & SPI_REGFILE_ADDR_MASK) >> 8) << SPI_DDR_ADDR_HIGH; + + if (fts->regfile_version & SPI_REGFILE_VERSION_DDR) { + fts->ddr_paddr = ((reg & SPI_REGFILE_ADDR_MASK) >> 8); + reg_ddr_high = phytium_read_regfile(fts, SPI_REGFILE_DDR_HIGH_REG); + fts->ddr_paddr |= ((u64)reg_ddr_high << 20); + } else { + fts->ddr_paddr = ((reg & SPI_REGFILE_ADDR_MASK) >> 8) << SPI_DDR_ADDR_HIGH; + } + fts->log_size = ((reg & SPI_REGFILE_SIZE_MASK) >> 4) * SPI_DEBUG_LOG_SIZE; fts->log = devm_ioremap(dev, fts->ddr_paddr, fts->log_size); diff --git a/drivers/spi/spi-phytium.h b/drivers/spi/spi-phytium.h index 500e08d34119f..758333c0cd3af 100644 --- a/drivers/spi/spi-phytium.h +++ b/drivers/spi/spi-phytium.h @@ -72,6 +72,13 @@ #define SPI_REGFILE_SIZE_MASK GENMASK(7, 4) #define SPI_REGFILE_ADDR_MASK GENMASK(27, 8) +#define SPI_REGFILE_DDR_HIGH_REG (0x4c) +#define SPI_REGFILE_VERSION_REG (0x700) +#define SPI_REGFILE_VERSION_SUB GENMASK(7, 0) +#define SPI_REGFILE_VERSION GENMASK(15, 8) +#define SPI_REGFILE_VERSION_DMA BIT(16) +#define SPI_REGFILE_VERSION_DDR BIT(17) + #define SPI_REGFILE_RV2AP_INT_CLEAN (0x74) #define SPI_DDR_ADDR_HIGH 12 @@ -219,6 +226,7 @@ struct phytium_spi { u32 log_size; void (*watchdog)(struct phytium_spi *fts); void (*handle_debug_err)(struct phytium_spi *fts); + u32 regfile_version; /* DMA info */ u32 current_freq; /* frequency in hz */ From e2997876216ce1cc556afa1fa2667867d9149d26 Mon Sep 17 00:00:00 2001 From: zhuling Date: Fri, 21 Nov 2025 16:53:09 +0800 Subject: [PATCH 031/101] spi: phytium: Add ACPI FixedDMA support for DMA channels Enable DMA when the SPI controller is described by ACPI using FixedDMA. The driver now detects the ACPI firmware path, acquires RX/TX channels by index, and arms the DMA path accordingly, while preserving the existing Device Tree behavior. This prevents unintended fallback to PIO on ACPI platforms. Mainline: Open-Source Signed-off-by: zhuling Signed-off-by: Wang Yinfeng Change-Id: I5229227fb377251d75d3ec9fd8f345b77fd4ab8c --- drivers/spi/spi-phytium-dma.c | 13 ++++++++-- drivers/spi/spi-phytium-plat.c | 44 ++++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-phytium-dma.c b/drivers/spi/spi-phytium-dma.c index 4387dadcd8505..ca2f7018b97e2 100644 --- a/drivers/spi/spi-phytium-dma.c +++ b/drivers/spi/spi-phytium-dma.c @@ -14,6 +14,8 @@ #include #include #include "spi-phytium.h" +#include +#include #define RX_BUSY 0 #define RX_BURST_LEVEL 16 @@ -64,11 +66,18 @@ static void phytium_spi_dma_maxburst_init(struct phytium_spi *fts) static int phytium_spi_dma_init(struct device *dev, struct phytium_spi *fts) { - fts->rxchan = dma_request_chan(dev, "rx"); + /* Support ACPI (FixedDMA) and DT (dmas/dma-names) */ + if (has_acpi_companion(dev)) + fts->rxchan = acpi_dma_request_slave_chan_by_index(dev, 0); + else + fts->rxchan = dma_request_chan(dev, "rx"); if (IS_ERR_OR_NULL(fts->rxchan)) return -ENODEV; - fts->txchan = dma_request_chan(dev, "tx"); + if (has_acpi_companion(dev)) + fts->txchan = acpi_dma_request_slave_chan_by_index(dev, 1); + else + fts->txchan = dma_request_chan(dev, "tx"); if (IS_ERR_OR_NULL(fts->txchan)) { dev_err(dev, "can't request chan\n"); dma_release_channel(fts->rxchan); diff --git a/drivers/spi/spi-phytium-plat.c b/drivers/spi/spi-phytium-plat.c index fd24684bd4d91..cb8427fb452e4 100644 --- a/drivers/spi/spi-phytium-plat.c +++ b/drivers/spi/spi-phytium-plat.c @@ -28,7 +28,7 @@ #include "spi-phytium.h" #define DRIVER_NAME "phytium_spi" -#define DRIVER_VERSION "1.0.1" +#define DRIVER_VERSION "1.0.2" #define SPI_PHYTIUM_DEFAULT_CLK_RATE 50000000 @@ -37,6 +37,39 @@ struct phytium_spi_clk { struct clk *clk; }; +static bool phytium_acpi_has_FixedDMA(struct device *dev) +{ + struct acpi_device *adev; + struct acpi_resource *res; + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; + bool found = false; + acpi_status status; + + if (!dev) + return false; + + adev = ACPI_COMPANION(dev); + if (!adev || !adev->handle) + return false; + + /* Get _CRS resource block (kernel allocates buffer) */ + status = acpi_get_current_resources(adev->handle, &buf); + if (ACPI_FAILURE(status) || !buf.pointer) + return false; + + /* Traverse resource list, exit and return true if FixedDMA is found */ + for (res = buf.pointer; res && res->type != ACPI_RESOURCE_TYPE_END_TAG; + res = ACPI_NEXT_RESOURCE(res)) { + if (res->type == ACPI_RESOURCE_TYPE_FIXED_DMA) { + found = true; + break; + } + } + + kfree(buf.pointer); + return found; +} + static int phytium_spi_probe(struct platform_device *pdev) { struct phytium_spi_clk *ftsc; @@ -84,7 +117,8 @@ static int phytium_spi_probe(struct platform_device *pdev) fts->max_freq = clk_get_rate(ftsc->clk); } else if (has_acpi_companion(&pdev->dev)) { - fts->max_freq = 48000000; + fwnode_property_read_u32(pdev->dev.fwnode, "spi-clock", &clk_rate); + fts->max_freq = clk_rate; } fts->bus_num = pdev->id; @@ -99,8 +133,10 @@ static int phytium_spi_probe(struct platform_device *pdev) /* check is use dma transfer */ if ((device_property_read_string_array(&pdev->dev, "dma-names", - NULL, 0) > 0) && - device_property_present(&pdev->dev, "dmas")) { + NULL, 0) > 0 && + device_property_present(&pdev->dev, "dmas")) || + (has_acpi_companion(&pdev->dev) && + phytium_acpi_has_FixedDMA(&pdev->dev))) { fts->dma_en = true; phytium_spi_dmaops_set(fts); } From d38805f7799b76c41f86cb59b099c75f735f56ed Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 14:24:27 +0800 Subject: [PATCH 032/101] ALSA: hda: Remove the Phytium substream write from shared azx_pcm_prepare MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 359acbbe ("hda: phytium: Add Phytium hda driver support") added an unconditional container_of() cast to struct hda_ft followed by a write of hda->substream in the shared azx_pcm_prepare() callback. Since sound/hda/common/controller.c is compiled into snd-hda-codec.o and shared by every HDA controller driver (Intel, Tegra, CIX, etc.), the cast reinterprets e.g. an embedded struct hda_intel as struct hda_ft. The substream pointer write then lands on hda_intel's probe_wait completion, corrupting it and causing hangs or crashes in the codec probe path on non-Phytium hardware. The tracking existed only so azx_resume() could re-prepare a suspended stream through a stale pointer — itself broken: the single pointer was overwritten by every prepare, leaving all but the last stream stuck, and the resume path mutated runtime state directly, bypassing the ALSA state machine. Drop the write entirely; stream restoration across suspend is left to the ALSA core, as snd-hda-intel does. Also drop the now-unused include of ../controllers/phytium.h from controller.c, which was introduced along with the bug. Fixes: 8da93e4a61f7 ("hda: phytium: Add Phytium hda driver support") Signed-off-by: Wentao Guan --- sound/hda/common/controller.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sound/hda/common/controller.c b/sound/hda/common/controller.c index 85e13ff7011c5..e57e0de3e1b87 100644 --- a/sound/hda/common/controller.c +++ b/sound/hda/common/controller.c @@ -17,7 +17,6 @@ #include #include -#include "../controllers/phytium.h" #include "../controllers/intel.h" #include @@ -265,9 +264,6 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream) struct hda_spdif_out *spdif = snd_hda_spdif_out_of_nid(apcm->codec, hinfo->nid); unsigned short ctls = spdif ? spdif->ctls : 0; - struct hda_ft *hda = container_of(chip, struct hda_ft, chip); - - hda->substream = substream; trace_azx_pcm_prepare(chip, azx_dev); guard_dsp_lock(azx_dev); From 3442893fdca40bd7a782d05a74ae11e192764532 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 14:30:25 +0800 Subject: [PATCH 033/101] spi: phytium: Fix division by zero on zero transfer speed in DMA path The DMA transfer path estimates timeouts from xfer->speed_hz without validating it. phytium_spi_dma_wait() does do_div(ms, speed) and phytium_spi_dma_wait_tx_done() computes DIV_ROUND_UP(1000000000, xfer->speed_hz / 2), so a transfer with speed_hz == 0 hits a division by zero and panics the kernel. The second site is also broken for speed_hz == 1, where the / 2 truncates the divisor to zero. The SPI core already substitutes a zero speed with spi->max_speed_hz in __spi_validate(), so a zero reaching the driver means the controller clock was never configured (fts->max_freq == 0). Refuse such transfers with -EINVAL at the entry of phytium_spi_dma_transfer() before any division happens, and clamp the divisor in phytium_spi_dma_wait_tx_done() with max_t(u32, ..., 1) so the FIFO drain estimate stays well-defined even for a 1 Hz speed. Fixes: 3674931621b2 ("arm64: spi: Phytium: Adapt SPI driver to use DDMA interface") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-dma.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-phytium-dma.c b/drivers/spi/spi-phytium-dma.c index ca2f7018b97e2..de2d747594e0b 100644 --- a/drivers/spi/spi-phytium-dma.c +++ b/drivers/spi/spi-phytium-dma.c @@ -190,7 +190,7 @@ static int phytium_spi_dma_wait_tx_done(struct phytium_spi *fts, nents = phytium_readl(fts, TXFLR); ns = nents * fts->n_bytes * BITS_PER_BYTE; - ns *= DIV_ROUND_UP(1000000000, xfer->speed_hz / 2); + ns *= DIV_ROUND_UP(1000000000, max_t(u32, xfer->speed_hz / 2, 1)); while (phytium_spi_dma_tx_busy(fts) && retry--) spi_transfer_delay_ns(ns); @@ -507,6 +507,17 @@ static int phytium_spi_dma_transfer(struct phytium_spi *fts, unsigned int nents; int ret; + /* + * The SPI core substitutes a zero speed with spi->max_speed_hz, + * which can still be zero on a controller with an unconfigured + * clock. Refuse such transfers instead of dividing by zero in + * the timeout estimation below. + */ + if (!xfer->speed_hz) { + dev_err(&fts->master->dev, "DMA transfer speed is zero\n"); + return -EINVAL; + } + nents = max(xfer->tx_sg.nents, xfer->rx_sg.nents); /* From 28b307b979f3ef699594e2de499be5bf8e7f62d1 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 14:55:36 +0800 Subject: [PATCH 034/101] dt-bindings: spi: phytium-qspi: Fix unevaluatedProperties typo The binding used the misspelled key "unevaluateProperties", which the devicetree meta-schema does not recognize and silently ignores. As a result the schema never rejected unexpected properties in QSPI nodes. Rename it to the correct "unevaluatedProperties" so that validation of additional properties works as intended. Fixes: 9f6b1b5bb881 ("dt-bindings: spi: phytium-qspi: Document device tree binding") Signed-off-by: Wentao Guan --- Documentation/devicetree/bindings/spi/phytium,qspi-nor.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/spi/phytium,qspi-nor.yaml b/Documentation/devicetree/bindings/spi/phytium,qspi-nor.yaml index 8ff781a145313..99a842d0d48d7 100644 --- a/Documentation/devicetree/bindings/spi/phytium,qspi-nor.yaml +++ b/Documentation/devicetree/bindings/spi/phytium,qspi-nor.yaml @@ -40,7 +40,7 @@ required: - reg-names - clocks -unevaluateProperties: false +unevaluatedProperties: false examples: - | From 2c28fd5df29bae79284e74a3faab6e719aba2b38 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 14:55:36 +0800 Subject: [PATCH 035/101] firmware: arm_scmi: Fix device_node leak in mailbox channel setup mailbox_chan_setup() calls of_parse_phandle_with_args() to look up the mailbox controller node, which takes a reference on args.np, but the reference is never dropped after the of_device_is_compatible() check for the Phytium mailbox. Every SCMI channel setup therefore leaks one device_node reference. Add the missing of_node_put(args.np). Fixes: a8b77803d5d8 ("firmware: arm_scmi: Make mailbox transport poll on Phytium mailbox") Signed-off-by: Wentao Guan --- drivers/firmware/arm_scmi/transports/mailbox.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/firmware/arm_scmi/transports/mailbox.c b/drivers/firmware/arm_scmi/transports/mailbox.c index cc522e61d81f1..15905720300ca 100644 --- a/drivers/firmware/arm_scmi/transports/mailbox.c +++ b/drivers/firmware/arm_scmi/transports/mailbox.c @@ -252,6 +252,8 @@ static int mailbox_chan_setup(struct scmi_chan_info *cinfo, struct device *dev, if (of_device_is_compatible(args.np, "phytium,mbox")) cinfo->no_completion_irq = true; + of_node_put(args.np); + cinfo->transport_info = smbox; smbox->cinfo = cinfo; mutex_init(&smbox->chan_lock); From 53222ca39b27ead586f8f9e0b7e26b9be3aa619d Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 14:59:29 +0800 Subject: [PATCH 036/101] spi: phytium-qspi: Fix reg_name_array leak in probe reg_name_array is allocated with kcalloc() but never freed on any of the probe exit paths, including the success path, leaking 32 bytes on every probe. The array only holds string pointers borrowed from the fwnode property, so switch to devm_kcalloc() and let the device managed framework reclaim it on unbind. Fixes: 7f4fb2e5cdaf ("arm64: phytium: UEFI mode acpi table support for qspi/spi driver") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-qspi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index fba79be38a4fc..f6fc13073b2f6 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -689,7 +689,8 @@ static int phytium_qspi_probe(struct platform_device *pdev) qspi = spi_controller_get_devdata(ctrl); qspi->ctrl = ctrl; - reg_name_array = kcalloc(4, sizeof(*reg_name_array), GFP_KERNEL); + reg_name_array = devm_kcalloc(dev, 4, sizeof(*reg_name_array), + GFP_KERNEL); if (dev->of_node) res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi"); else if (has_acpi_companion(dev)) { From e745bb8206b7f1d9e0320c8966df3203c7dc6f59 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 15:00:10 +0800 Subject: [PATCH 037/101] spi: phytium-qspi: Fix out-of-bounds flash array access in setup phytium_qspi_setup() indexes qspi->flash[] with the chip select before validating it: the bounds check "flash->cs >= PHYTIUM_QSPI_MAX_NORCHIP" runs only after flash->cs and flash->spi have already been written through the out-of-range pointer. A chip select >= 4 (e.g. from a firmware or device tree misconfiguration) therefore corrupts the memory following the flash array before the check rejects it. Move the bounds check before the array access, rejecting the device with -EINVAL up front. Fixes: 9f2e21865eb1 ("arm64: spi: Phytium-qspi: Add support for Phytium QSPI controller") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-qspi.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index f6fc13073b2f6..4b089f76c6d2c 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -607,14 +607,15 @@ static int phytium_qspi_setup(struct spi_device *spi) if (ctrl->busy) return -EBUSY; + if (spi_get_chipselect(spi, 0) >= PHYTIUM_QSPI_MAX_NORCHIP) { + dev_err(&spi->dev, "Flash CS is out of range.\n"); + return -EINVAL; + } + flash = &qspi->flash[spi_get_chipselect(spi, 0)]; flash->cs = spi_get_chipselect(spi, 0); flash->spi = spi; - if (flash->cs >= PHYTIUM_QSPI_MAX_NORCHIP) { - dev_err(qspi->dev, "Flash CS is out of range.\n"); - return -EINVAL; - } qspi->fnum++; From bc13ca027fead2b3dfec6c89d6873eac021eeaa3 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 15:00:10 +0800 Subject: [PATCH 038/101] ALSA: hda: intel: Fix pci_dev leak in gf_init_pci gf_init_pci() takes a reference on the display device with pci_get_slot() to locate the frame buffer, but never drops it before returning. Every probe of a Glenfly (vendor 0x6766, device 0x3d40) HDA controller therefore leaks one pci_dev reference, which accumulates across bind/unbind cycles. Add the missing pci_dev_put() after the last use of diu_pci, matching the pattern used by the rest of this driver. Fixes: 86858d4871e3 ("add gf hdaudio 001 patch in deepin kernel 6.6") Signed-off-by: Wentao Guan --- sound/hda/controllers/intel.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c index 2865654753c61..c686599255043 100644 --- a/sound/hda/controllers/intel.c +++ b/sound/hda/controllers/intel.c @@ -432,6 +432,7 @@ static int gf_init_pci(struct azx *chip) gf_chip->diu_fb_bdl_vaddr[1] = ioremap_wc(diu_fb_bdl[1], BDL_SIZE); // size = 4K dev_info(chip->card->dev, "gf_hda diu fb base=0x%llx, size=%dM.\n", diu_fb_base, (unsigned int)(fb_size >> 20)); + pci_dev_put(diu_pci); } } return 0; From d2d85432eff496d20511ce559102317068064ef5 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 15:00:10 +0800 Subject: [PATCH 039/101] mtd: spi-nor: Drop unused adev variable in spi_nor_probe spi_nor_probe() declares struct acpi_device *adev and assigns it the result of ACPI_COMPANION(nor->dev), but never uses it. The name lookup below reads the "_HID" property directly instead. Remove the dead variable; with CONFIG_WERROR this would eventually break the build. Fixes: 7f4fb2e5cdaf ("arm64: phytium: UEFI mode acpi table support for qspi/spi driver") Signed-off-by: Wentao Guan --- drivers/mtd/spi-nor/core.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index fd41f8fb9fee6..19c4511271773 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -3778,7 +3778,6 @@ static int spi_nor_probe(struct spi_mem *spimem) struct device *dev = &spi->dev; struct flash_platform_data *data = dev_get_platdata(dev); struct spi_nor *nor; - struct acpi_device *adev; /* * Enable all caps by default. The core will mask them after * checking what's really supported using spi_mem_supports_op(). @@ -3798,7 +3797,6 @@ static int spi_nor_probe(struct spi_mem *spimem) nor->spimem = spimem; nor->dev = dev; spi_nor_set_flash_node(nor, dev->of_node); - adev = ACPI_COMPANION(nor->dev); nor->mtd.dev.fwnode = spi->dev.fwnode; device_property_read_string(&spi->dev, "_HID", &nor->mtd.name); From 2c53a19b409c60d6ba893c34d7742e1740de06f6 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:00:18 +0800 Subject: [PATCH 040/101] ALSA: hda: Bound-check the frame buffer memcpy in gf_pre_trigger gf_pre_trigger() copies the whole PCM DMA buffer into the 7 MB Glenfly frame-buffer window without any size check, unlike gf_update_stream() which validates dma_bytes against GF_HDA_FB_STREAM_SIZE first. A PCM buffer larger than the window overflows the ioremap_wc mapping and corrupts memory. Add the same bound to gf_pre_trigger() and skip the preload when the buffer does not fit. Fixes: 86858d4871e3 ("add gf hdaudio 001 patch in deepin kernel 6.6") Signed-off-by: Wentao Guan --- sound/hda/common/controller.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/hda/common/controller.c b/sound/hda/common/controller.c index e57e0de3e1b87..70c6c63be42d8 100644 --- a/sound/hda/common/controller.c +++ b/sound/hda/common/controller.c @@ -141,7 +141,9 @@ static int gf_pre_trigger(struct snd_pcm_substream *substream, int cmd) } stream_idx = apcm->codec->addr - 1; if ((cmd == SNDRV_PCM_TRIGGER_START) && - (stream_idx <= 1) && (gf_chip->diu_fb_stream_vaddr[stream_idx]) && (substream->runtime->dma_area)) { + (stream_idx <= 1) && (gf_chip->diu_fb_stream_vaddr[stream_idx]) && + (substream->runtime->dma_area) && + (substream->runtime->dma_bytes <= GF_HDA_FB_STREAM_SIZE)) { memcpy(gf_chip->diu_fb_stream_vaddr[stream_idx], substream->runtime->dma_area, substream->runtime->dma_bytes); gf_chip->diu_fb_stream_pos[stream_idx] = 0; } From 0c757cd8f9a385e27762ffaecb50041f6fec0195 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:00:18 +0800 Subject: [PATCH 041/101] ALSA: hda: Let RIRB IRQs run during CORB retries The cmd_resend loop held reg_lock with interrupts disabled while polling for a response and retransmitting the verb. Move CORB writes into a helper and release the lock between retry delays so RIRB processing can run normally. Keep one rirb.cmds reference for the logical command: retransmissions are recovery copies, and a lost copy cannot produce a response. Counting every copy makes a successful retry wait forever for responses that will never arrive. Fixes: 8da93e4a61f7 ("hda: phytium: Add Phytium hda driver support") Signed-off-by: Wentao Guan --- sound/hda/core/controller.c | 113 +++++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 47 deletions(-) diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c index 1975328bae9ef..9a2e112aa9093 100644 --- a/sound/hda/core/controller.c +++ b/sound/hda/core/controller.c @@ -209,24 +209,13 @@ static int snd_hdac_bus_get_response_pio(struct hdac_bus *bus, return 0; } -/** - * snd_hdac_bus_send_cmd_corb - send a command verb via CORB - * @bus: HD-audio core bus - * @val: encoded verb value to send - * - * Returns zero for success or a negative error code. - */ -static int snd_hdac_bus_send_cmd_corb(struct hdac_bus *bus, unsigned int val) +/* number of retransmissions for a verb that produced no response */ +#define CORB_SEND_RETRIES 6 + +/* caller holds bus->reg_lock */ +static int corb_write_cmd(struct hdac_bus *bus, unsigned int val) { - unsigned int addr = azx_command_addr(val); unsigned int wp, rp; - unsigned long timeout; - unsigned int rirb_wp; - int i = 0; - - guard(spinlock_irq)(&bus->reg_lock); - - bus->last_cmd[azx_command_addr(val)] = val; /* add command to corb */ wp = snd_hdac_chip_readw(bus, CORBWP); @@ -243,42 +232,60 @@ static int snd_hdac_bus_send_cmd_corb(struct hdac_bus *bus, unsigned int val) return -EAGAIN; } - bus->rirb.cmds[addr]++; bus->corb.buf[wp] = cpu_to_le32(val); snd_hdac_chip_writew(bus, CORBWP, wp); - if (bus->cmd_resend) { - timeout = jiffies + msecs_to_jiffies(1000); + return 0; +} + +/** + * snd_hdac_bus_send_cmd_corb - send a command verb via CORB + * @bus: HD-audio core bus + * @val: encoded verb value to send + * + * Returns zero for success or a negative error code. + */ +static int snd_hdac_bus_send_cmd_corb(struct hdac_bus *bus, unsigned int val) +{ + unsigned int addr = azx_command_addr(val); + int cmds_before, cmds_after; + int err, i; + + guard(spinlock_irq)(&bus->reg_lock); + + bus->last_cmd[addr] = val; + + err = corb_write_cmd(bus, val); + if (err) + return err; + /* Retries are copies of one logical verb, not new responses. */ + bus->rirb.cmds[addr]++; + + if (!bus->cmd_resend) + return 0; + + /* + * Controllers with cmd_resend occasionally lose a CORB write: the + * verb never reaches the codec and no response ever arrives. + * Retransmit the verb a few times, but keep one response reference + * for the logical command. A lost copy cannot produce a response, + * so counting every copy would make a successful retry time out. + * The wait runs with interrupts enabled; the per-codec counter is + * updated by snd_hdac_bus_update_rirb(). + */ + cmds_before = bus->rirb.cmds[addr]; + for (i = 0; i < CORB_SEND_RETRIES; i++) { + spin_unlock_irq(&bus->reg_lock); udelay(80); - rirb_wp = snd_hdac_chip_readw(bus, RIRBWP); - while (rirb_wp == bus->rirb.wp) { - udelay(80); - rirb_wp = snd_hdac_chip_readw(bus, RIRBWP); - if (rirb_wp != bus->rirb.wp) - break; - if (i > 5) - break; - if (time_after(jiffies, timeout)) - break; - - /* add command to corb */ - wp = snd_hdac_chip_readw(bus, CORBWP); - if (wp == 0xffff) { - /* something wrong, controller likely turned to D3 */ - return -EIO; - } - wp++; - wp %= AZX_MAX_CORB_ENTRIES; + spin_lock_irq(&bus->reg_lock); - rp = snd_hdac_chip_readw(bus, CORBRP); - if (wp == rp) { - /* oops, it's full */ - return -EAGAIN; - } - bus->corb.buf[wp] = cpu_to_le32(val); - snd_hdac_chip_writew(bus, CORBWP, wp); - i++; - } + cmds_after = bus->rirb.cmds[addr]; + if (cmds_after < cmds_before) + break; + + err = corb_write_cmd(bus, val); + if (err) + return err; } return 0; @@ -396,6 +403,18 @@ static int snd_hdac_bus_get_response_rirb(struct hdac_bus *bus, if (!bus->polling_mode) finish_wait(&bus->rirb_wq, &wait); + /* + * With command resend, the timed-out verb may still account for + * several retransmitted copies in rirb.cmds. Copies whose CORB + * write was lost never respond, so the wait would never complete + * and every following verb on this codec would inherit the stale + * references and time out in turn. Reset the counter for this + * codec: a response of a merely slow copy that lands afterwards + * is then dropped as spurious by snd_hdac_bus_update_rirb(). + */ + if (bus->cmd_resend) + bus->rirb.cmds[addr] = 0; + return -EIO; } From 08fc35e13c75de324f1f19cb2b6cebd6d5361e45 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:00:18 +0800 Subject: [PATCH 042/101] ALSA: hda: phytium: Cancel pending IRQ work in azx_free azx_free() clears the per-stream irq_pending flags but never waits for a possibly queued irq_pending_work, which keeps rescheduling itself while any flag is set. A work still running after snd_card_free() then uses the freed card. The reworked Intel driver cancels the work when clearing the flags; do the same here right after azx_clear_irq_pending(), when the flags are down so the work returns instead of rescheduling. Fixes: 8da93e4a61f7 ("hda: phytium: Add Phytium hda driver support") Signed-off-by: Wentao Guan --- sound/hda/controllers/phytium.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/hda/controllers/phytium.c b/sound/hda/controllers/phytium.c index 40c820c503b1c..e0431fa4a19da 100644 --- a/sound/hda/controllers/phytium.c +++ b/sound/hda/controllers/phytium.c @@ -440,6 +440,7 @@ static int azx_suspend(struct device *dev) bus = azx_bus(chip); snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); azx_clear_irq_pending(chip); + cancel_work_sync(&hda->irq_pending_work); azx_stop_chip(chip); if (bus->irq >= 0) { free_irq(bus->irq, (void *)chip); @@ -619,6 +620,7 @@ static int azx_free(struct azx *chip) if (bus->chip_init) { azx_clear_irq_pending(chip); + cancel_work_sync(&hda->irq_pending_work); azx_stop_all_streams(chip); azx_stop_chip(chip); } From cbe43f58ffa83721a30f406ca0b244f022dc063d Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:02:41 +0800 Subject: [PATCH 043/101] spi: phytium-pci: Fix drvdata type confusion in PM ops The PCI probe stores struct phytium_spi * via pci_set_drvdata(), but the suspend/resume callbacks read dev_get_drvdata() back as a struct spi_controller * and feed it to spi_controller_get_devdata(), turning the stored pointer into garbage and crashing on suspend/resume. This is the same bug that 5851343a5a5c5 fixed in the platform driver but was missed here. Read back the struct phytium_spi * directly. Fixes: caf2f8ff84f6 ("arm64: spi: add Phytium SPI controller support") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-pci.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-phytium-pci.c b/drivers/spi/spi-phytium-pci.c index 65797d6c0d2a4..3d5391edea4ce 100644 --- a/drivers/spi/spi-phytium-pci.c +++ b/drivers/spi/spi-phytium-pci.c @@ -88,16 +88,14 @@ static void phytium_spi_pci_remove(struct pci_dev *pdev) #ifdef CONFIG_PM_SLEEP static int spi_suspend(struct device *dev) { - struct spi_controller *master = dev_get_drvdata(dev); - struct phytium_spi *fts = spi_controller_get_devdata(master); + struct phytium_spi *fts = dev_get_drvdata(dev); return phytium_spi_suspend_host(fts); } static int spi_resume(struct device *dev) { - struct spi_controller *master = dev_get_drvdata(dev); - struct phytium_spi *fts = spi_controller_get_devdata(master); + struct phytium_spi *fts = dev_get_drvdata(dev); return phytium_spi_resume_host(fts); } From 961ea072f7662eed7930f01a2b848a47ae77ea93 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:02:42 +0800 Subject: [PATCH 044/101] mtd: partitions: Fix uninitialized compat in the ACPI parser path mtd_part_acpi_parse() declared const char *compat without an initializer. On the subpartition path the property read is skipped entirely, and on the master path a failed read leaves the variable untouched, so the parser dispatch branched on stack garbage. The surrounding device_for_each_child_node() loop also ran the parser lookup once per child node for no reason and leaked the child references. Initialize compat to NULL, evaluate the match once outside any loop and drop the loop entirely. Fixes: 7f4fb2e5cdaf ("arm64: phytium: UEFI mode acpi table support for qspi/spi driver") Signed-off-by: Wentao Guan --- drivers/mtd/mtdpart.c | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index a0064940cc682..1c1bfdbaf1a1f 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -602,35 +602,32 @@ static int mtd_part_acpi_parse(struct mtd_info *master, struct mtd_partitions *pparts) { struct mtd_part_parser *parser; - struct fwnode_handle *child; - const char *compat; + const char *compat = NULL; const char *fixed = "acpi-fixed-partitions"; int ret, err = 0; - int compare = 1; struct device *dev = &master->dev; - if (!mtd_is_partition(master)) { + /* + * Only the master carries the "fixed" marker property; on + * subpartitions it is absent and no ACPI parsing is done. + */ + if (!mtd_is_partition(master)) fwnode_property_read_string(dev->fwnode, "fixed", &compat); - if (compat) - compare = strcmp(compat, fixed); - } - //all child node - device_for_each_child_node(dev, child) { - if (compat && !compare) { + if (compat && !strcmp(compat, fixed)) { + parser = mtd_part_parser_get(fixed); + if (!parser && !request_module("%s", fixed)) parser = mtd_part_parser_get(fixed); - if (!parser && !request_module("%s", fixed)) - parser = mtd_part_parser_get(fixed); - if (parser) { - ret = mtd_part_do_parse(parser, master, pparts, NULL); - if (ret > 0) - return ret; - mtd_part_parser_put(parser); - if (ret < 0 && !err) - err = ret; - } + if (parser) { + ret = mtd_part_do_parse(parser, master, pparts, NULL); + if (ret > 0) + return ret; + mtd_part_parser_put(parser); + if (ret < 0 && !err) + err = ret; } } + return err; } From 354390792370a3216b1b2a43c870859e5e705539 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:02:42 +0800 Subject: [PATCH 045/101] firmware: arm_scmi: Rework the Phytium mailbox polling quirk The Phytium quirk in mailbox_chan_setup() was wired to a hard-coded of_parse_phandle_with_args() call on mboxes index 1 whose failure became fatal, which breaks the valid single-mailbox (num_mb == 1) layout on any platform. For the 2-mbox layout index 1 is the p2a doorbell channel, not the a2p reply channel whose completion IRQ is being disabled, and the quirk also ran during Rx channel setup where no_completion_irq is meaningless. Rework it as a non-fatal quirk: run it only on the Tx path, inspect the controller behind the a2p reply channel (a2p_rx_chan, already computed by mailbox_chan_validate()), resolve the phandle with of_parse_phandle() and simply skip the quirk when it cannot be resolved. Fixes: a8b77803d5d8 ("firmware: arm_scmi: Make mailbox transport poll on Phytium mailbox") Signed-off-by: Wentao Guan --- .../firmware/arm_scmi/transports/mailbox.c | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/drivers/firmware/arm_scmi/transports/mailbox.c b/drivers/firmware/arm_scmi/transports/mailbox.c index 15905720300ca..df50b34699b56 100644 --- a/drivers/firmware/arm_scmi/transports/mailbox.c +++ b/drivers/firmware/arm_scmi/transports/mailbox.c @@ -187,7 +187,6 @@ static int mailbox_chan_setup(struct scmi_chan_info *cinfo, struct device *dev, struct scmi_mailbox *smbox; int ret, a2p_rx_chan, p2a_chan, p2a_rx_chan; struct mbox_client *cl; - struct of_phandle_args args; ret = mailbox_chan_validate(cdev, &a2p_rx_chan, &p2a_chan, &p2a_rx_chan); if (ret) @@ -242,18 +241,30 @@ static int mailbox_chan_setup(struct scmi_chan_info *cinfo, struct device *dev, } } - ret = of_parse_phandle_with_args(cdev->of_node, "mboxes", - "#mbox-cells", 1, &args); - if (ret) { - dev_err(cdev, "failed to get SCMI %s mailbox\n", desc); - return ret; + /* + * The Phytium mailbox controller never raises the transfer-complete + * interrupt, so synchronous commands must be polled. Inspect the + * controller behind the a2p reply mailbox; a phandle that cannot be + * resolved is not fatal - just skip the quirk then. + */ + if (tx) { + struct of_phandle_args args; + + /* + * mboxes is a phandle-with-args list; of_parse_phandle() + * does not account for #mbox-cells, so it can land on an + * argument cell instead of the controller node when the + * specifier has arguments. Use the full parser. + */ + if (!of_parse_phandle_with_args(cdev->of_node, "mboxes", + "#mbox-cells", a2p_rx_chan, + &args)) { + if (of_device_is_compatible(args.np, "phytium,mbox")) + cinfo->no_completion_irq = true; + of_node_put(args.np); + } } - if (of_device_is_compatible(args.np, "phytium,mbox")) - cinfo->no_completion_irq = true; - - of_node_put(args.np); - cinfo->transport_info = smbox; smbox->cinfo = cinfo; mutex_init(&smbox->chan_lock); From 7e1cb27ff10353aafa360e2b6fb10772fadf8ef0 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:02:42 +0800 Subject: [PATCH 046/101] mtd: acpipart: Fix uninitialized values, dead gate and %pOF misuse parse_acpi_fixed_partitions() had a pile of issues: - the return of device_get_next_child_node() was discarded, so child_handle stayed NULL, dedicated stayed false, the acpi_match_device() gate was dead code and every fwnode child was treated as a partition (plus the reference of any returned node leaked); - offset/length were read with fwnode_property_read_u64() without checking the return values, and the !offset && !length test then branched on indeterminate stack values; - %pOF was used on struct fwnode_handle * pointers (three sites, one on a reachable error path), which vsnprintf interprets as struct device_node * and can dereference a bogus ->ops; - children with offset/length but no label left parts[].name NULL, failing the whole partition registration with a misleading -ENOMEM where ofpart falls back to the node name; - struct acpi_device *adev was assigned but never used. Fix all of the above: probe the first child and drop its reference, validate both property reads, fall back to the fwnode name when no label is present, switch the prints to %pfw and remove the dead variable. Also document in the match table why the ID was renamed from "acpi-fixed-partitions" to "acpi-partitions": the old string exceeds the 16-byte ACPI_ID_LEN, so the compiler truncated it and no firmware could ever have matched the full ID anyway. Fixes: 7f4fb2e5cdaf ("arm64: phytium: UEFI mode acpi table support for qspi/spi driver") Signed-off-by: Wentao Guan --- drivers/mtd/parsers/acpipart_core.c | 40 +++++++++++++++++++---------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/drivers/mtd/parsers/acpipart_core.c b/drivers/mtd/parsers/acpipart_core.c index 501d6f54b2c79..e272f63bc21e2 100644 --- a/drivers/mtd/parsers/acpipart_core.c +++ b/drivers/mtd/parsers/acpipart_core.c @@ -23,19 +23,24 @@ static int parse_acpi_fixed_partitions(struct mtd_info *master, const struct acpi_device_id *acpi_id; const char *partname; int nr_parts, i, ret = 0; - struct acpi_device *adev; struct fwnode_handle *child_handle = NULL; bool dedicated = true; struct device *dev; dev = &master->dev; - adev = ACPI_COMPANION(&master->dev); if (!master->parent) {/*master*/ - device_get_next_child_node(dev, child_handle); - if (!child_handle) { - pr_debug("%s: 'partitions' subnode not found on %pOF. Trying to parse direct subnodes as partitions.\n", - master->name, child_handle); + /* + * Probe whether the device has any child node; the + * returned reference is only needed for the check. + */ + child_handle = device_get_next_child_node(dev, NULL); + if (child_handle) { + fwnode_handle_put(child_handle); + child_handle = NULL; + } else { + pr_debug("%s: 'partitions' subnode not found on %pfw. Trying to parse direct subnodes as partitions.\n", + master->name, dev->fwnode); dedicated = false; } } @@ -57,14 +62,14 @@ static int parse_acpi_fixed_partitions(struct mtd_info *master, i = 0; device_for_each_child_node(dev, child_handle) { - u64 offset, length; + u64 offset = 0, length = 0; bool bool_match; - fwnode_property_read_u64(child_handle, "offset", &offset); - fwnode_property_read_u64(child_handle, "length", &length); - if (!offset && !length) { + if (fwnode_property_read_u64(child_handle, "offset", &offset) || + fwnode_property_read_u64(child_handle, "length", &length) || + (!offset && !length)) { if (dedicated) { - pr_debug("%s: acpipart partition %pOF (%pOF) missing reg property.\n", + pr_debug("%s: acpipart partition %pfw (%pfw) missing offset/length property.\n", master->name, child_handle, dev->fwnode); goto acpipart_fail; @@ -76,9 +81,12 @@ static int parse_acpi_fixed_partitions(struct mtd_info *master, parts[i].offset = offset; parts[i].size = length; - parts[i].fwnode = child_handle; + parts[i].fwnode = fwnode_handle_get(child_handle); if (!fwnode_property_read_string(child_handle, "label", &partname)) parts[i].name = partname; + else + /* fall back to the node name, as ofpart does */ + parts[i].name = fwnode_get_name(child_handle); bool_match = fwnode_property_read_bool(child_handle, "read-only"); if (bool_match) parts[i].mask_flags |= MTD_WRITEABLE; @@ -99,7 +107,7 @@ static int parse_acpi_fixed_partitions(struct mtd_info *master, return ret; acpipart_fail: - pr_err("%s: error parsing acpipart partition %pOF (%pOF)\n", + pr_err("%s: error parsing acpipart partition %pfw (%pfw)\n", master->name, child_handle, dev->fwnode); ret = -EINVAL; acpipart_none: @@ -108,6 +116,12 @@ static int parse_acpi_fixed_partitions(struct mtd_info *master, } static const struct acpi_device_id parse_acpipart_match_table[] = { + /* + * Note: the historical ID "acpi-fixed-partitions" exceeds the + * 16-byte ACPI_ID_LEN and was silently truncated by the compiler, + * which is why the ID was renamed to "acpi-partitions" in the + * -Werror fixes; firmware can never have matched the full old ID. + */ /* Generic */ { "acpi-partitions", 0 }, /* Customized */ From d6eadd7c5ec3b0429f24938222ccdae20e439ef2 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:17:51 +0800 Subject: [PATCH 047/101] spi: phytium: Release DMA channels when IRQ request fails Since the DMA channels are requested in phytium_spi_add_host() before request_irq(), an IRQ request failure jumps straight to err_free_master and skips dma_exit(), leaking both channels. Release them and disable the chip on this path like the register-failure path below does. Fixes: 3674931621b2 ("arm64: spi: Phytium: Adapt SPI driver to use DDMA interface") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/spi/spi-phytium.c b/drivers/spi/spi-phytium.c index b4df5cdf411a7..c706fe510a815 100644 --- a/drivers/spi/spi-phytium.c +++ b/drivers/spi/spi-phytium.c @@ -438,6 +438,9 @@ int phytium_spi_add_host(struct device *dev, struct phytium_spi *fts) fts->name, master); if (ret < 0) { dev_err(dev, "can not get IRQ\n"); + if (fts->dma_ops && fts->dma_ops->dma_exit) + fts->dma_ops->dma_exit(fts); + spi_enable_chip(fts, 0); goto err_free_master; } From fcba402bf6f2d4071c8be6c17b86541556cef064 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:17:51 +0800 Subject: [PATCH 048/101] spi: phytium: Unregister the controller before shutting the chip down 679d585f9e7c4 ("arm64: spi: Phytium: Fix controller unregister order") claimed to fix the unbind order, but still performed spi_shutdown_chip() before spi_unregister_controller(): the chip is dead while the still-registered controller unbinds its slave devices, whose drivers then cannot access the bus and may be left in an improper state. Move spi_unregister_controller() to the top of phytium_spi_remove_host(), before the DMA release and the chip shutdown, so the slaves unbind against a live bus. Fixes: 25449d0e072d ("arm64: spi: Phytium: Fix controller unregister order") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-phytium.c b/drivers/spi/spi-phytium.c index c706fe510a815..86bdb6ff8733b 100644 --- a/drivers/spi/spi-phytium.c +++ b/drivers/spi/spi-phytium.c @@ -465,12 +465,12 @@ EXPORT_SYMBOL_GPL(phytium_spi_add_host); void phytium_spi_remove_host(struct phytium_spi *fts) { + spi_unregister_controller(fts->master); + if (fts->dma_ops && fts->dma_ops->dma_exit) fts->dma_ops->dma_exit(fts); spi_shutdown_chip(fts); - spi_unregister_controller(fts->master); - free_irq(fts->irq, fts->master); } EXPORT_SYMBOL_GPL(phytium_spi_remove_host); From 8529907d96ee0da4903c3eabee4a42f3312e3635 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:17:51 +0800 Subject: [PATCH 049/101] spi: phytium-dma: Do not leave error pointers in the DMA channels When dma_request_chan() fails, phytium_spi_dma_init() returns with the ERR_PTR still stored in fts->rxchan or fts->txchan (only the tx-failure path cleared rxchan). DMA init failure is non-fatal - the host keeps running in PIO mode - so phytium_spi_dma_exit() can later hit the stale pointer and crash in dmaengine_terminate_sync() or dma_release_channel(). Clear both channel pointers on every failure path. Fixes: 3674931621b2 ("arm64: spi: Phytium: Adapt SPI driver to use DDMA interface") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-dma.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-phytium-dma.c b/drivers/spi/spi-phytium-dma.c index de2d747594e0b..c2beabf2f4a89 100644 --- a/drivers/spi/spi-phytium-dma.c +++ b/drivers/spi/spi-phytium-dma.c @@ -71,8 +71,10 @@ static int phytium_spi_dma_init(struct device *dev, fts->rxchan = acpi_dma_request_slave_chan_by_index(dev, 0); else fts->rxchan = dma_request_chan(dev, "rx"); - if (IS_ERR_OR_NULL(fts->rxchan)) + if (IS_ERR_OR_NULL(fts->rxchan)) { + fts->rxchan = NULL; return -ENODEV; + } if (has_acpi_companion(dev)) fts->txchan = acpi_dma_request_slave_chan_by_index(dev, 1); @@ -82,6 +84,7 @@ static int phytium_spi_dma_init(struct device *dev, dev_err(dev, "can't request chan\n"); dma_release_channel(fts->rxchan); fts->rxchan = NULL; + fts->txchan = NULL; return -ENODEV; } From 20b154838054c3c54f207d0a8e30d17cf1824a7f Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:17:51 +0800 Subject: [PATCH 050/101] spi: phytium-dma: Route TX-only large transfers through transfer_all phytium_spi_dma_transfer_one() unconditionally walks xfer->rx_sg.sgl, so a TX-only transfer larger than DMA_MAX_BUF_SIZE (rx_buf NULL, no rx scatterlist) NULL-dereferences right away. The transfer_all() path already guards the rx-less case. Send TX-only transfers down the transfer_all() path regardless of their length. Fixes: 3674931621b2 ("arm64: spi: Phytium: Adapt SPI driver to use DDMA interface") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-phytium-dma.c b/drivers/spi/spi-phytium-dma.c index c2beabf2f4a89..c40a830352afe 100644 --- a/drivers/spi/spi-phytium-dma.c +++ b/drivers/spi/spi-phytium-dma.c @@ -527,7 +527,7 @@ static int phytium_spi_dma_transfer(struct phytium_spi *fts, * large transfer length caused spi RX FIFO full event * transfer 4096 bytes each time */ - if (xfer->len <= DMA_MAX_BUF_SIZE) + if (xfer->len <= DMA_MAX_BUF_SIZE || !xfer->rx_buf) ret = phytium_spi_dma_transfer_all(fts, xfer); else ret = phytium_spi_dma_transfer_one(fts, xfer); From 7f24a00d3ec3ee40b84036de3caa40a943f24648 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:17:51 +0800 Subject: [PATCH 051/101] spi: phytium-qspi: Fix the dummy cycle encoding in exec_op The command port setup shifted the dummy cycle count into QSPI_CMD_PORT_LATENCY_SHIFT (bit 14), which is a 1-bit latency-enable field, instead of the 5-bit dummy field at QSPI_CMD_PORT_DUMMY_SHIFT (bit 7). Eight dummy cycles therefore landed at bit 17, inside the TRANSFER field, corrupting the protocol encoding of every fast/dual/quad read issued through exec_op (the direct-mapping path uses the correct field). Use the dummy field and mask the count to its width. Fixes: 9f2e21865eb1 ("arm64: spi: Phytium-qspi: Add support for Phytium QSPI controller") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-qspi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index 4b089f76c6d2c..b1fe1499315c7 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -444,8 +444,8 @@ static int phytium_qspi_exec_op(struct spi_mem *mem, if (op->dummy.nbytes) { cmd |= QSPI_CMD_PORT_LATENCY_MASK; - cmd |= ((op->dummy.nbytes * 8 - 1) / op->dummy.buswidth) << - QSPI_CMD_PORT_DUMMY_SHIFT; + cmd |= (((op->dummy.nbytes * 8 - 1) / op->dummy.buswidth) << + QSPI_CMD_PORT_DUMMY_SHIFT) & QSPI_CMD_PORT_DUMMY_MASK; } if (op->data.nbytes) { From 17f545288b9d7cfde4955a1895a29271642b49ef Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:17:51 +0800 Subject: [PATCH 052/101] spi: phytium-qspi: Check the reg-name allocation and ACPI resources The ACPI probe path never checked the reg_name_array allocation, and dereferenced the struct resource * returned by platform_get_resource() before verifying it exists, so a missing resource meant an immediate NULL dereference via res->name. The reg-names property read can also leave the array entries NULL, which was then assigned to res->name unconditionally. Check the allocation, validate both resources and only override res->name when the corresponding reg-name was actually read. Fixes: 9f2e21865eb1 ("arm64: spi: Phytium-qspi: Add support for Phytium QSPI controller") Fixes: 7f4fb2e5cdaf ("arm64: phytium: UEFI mode acpi table support for qspi/spi driver") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-qspi.c | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index b1fe1499315c7..bf92fb6e7ae33 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -692,13 +692,23 @@ static int phytium_qspi_probe(struct platform_device *pdev) reg_name_array = devm_kcalloc(dev, 4, sizeof(*reg_name_array), GFP_KERNEL); - if (dev->of_node) + if (!reg_name_array) { + ret = -ENOMEM; + goto probe_master_put; + } + if (dev->of_node) { res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi"); - else if (has_acpi_companion(dev)) { + } else if (has_acpi_companion(dev)) { res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "missing QSPI register resource\n"); + ret = -ENODEV; + goto probe_master_put; + } fwnode_property_read_string_array(dev->fwnode, "reg-names", reg_name_array, 2); - res->name = reg_name_array[0]; + if (reg_name_array[0]) + res->name = reg_name_array[0]; } qspi->io_base = devm_ioremap_resource(dev, res); if (IS_ERR(qspi->io_base)) { @@ -706,11 +716,17 @@ static int phytium_qspi_probe(struct platform_device *pdev) goto probe_master_put; } - if (dev->of_node) + if (dev->of_node) { res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi_mm"); - else if (has_acpi_companion(dev)) { + } else if (has_acpi_companion(dev)) { res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - res->name = reg_name_array[1]; + if (!res) { + dev_err(dev, "missing QSPI memory resource\n"); + ret = -ENODEV; + goto probe_master_put; + } + if (reg_name_array[1]) + res->name = reg_name_array[1]; } qspi->mm_base = devm_ioremap_resource(dev, res); From de0098a3ef857833998c96cccfcf1bc5ac1bc88c Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:17:52 +0800 Subject: [PATCH 053/101] spi: phytium-qspi: Balance runtime PM only in OF mode The probe error paths and the remove callback call pm_runtime_put_sync()/pm_runtime_disable() unconditionally, but in ACPI mode runtime PM is never enabled and no reference is taken (the clock handling is skipped there as well), leaving the runtime PM accounting unbalanced. Gate the put/disable calls on dev->of_node, matching the enable side. Fixes: 7f4fb2e5cdaf ("arm64: phytium: UEFI mode acpi table support for qspi/spi driver") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-qspi.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index bf92fb6e7ae33..9792fc6d5feba 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -839,8 +839,10 @@ static int phytium_qspi_probe(struct platform_device *pdev) probe_setup_failed: clk_disable_unprepare(qspi->clk); probe_clk_failed: - pm_runtime_put_sync(dev); - pm_runtime_disable(dev); + if (dev->of_node) { + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + } probe_master_put: return ret; @@ -859,11 +861,14 @@ static int phytium_qspi_probe(struct platform_device *pdev) static void phytium_qspi_remove(struct platform_device *pdev) { struct phytium_qspi *qspi = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; clk_disable_unprepare(qspi->clk); - pm_runtime_put_sync(&pdev->dev); - pm_runtime_disable(&pdev->dev); + if (dev->of_node) { + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + } } static int __maybe_unused phytium_qspi_suspend(struct device *dev) From 8f1a11e4b4c0f413f6ce7ceaea015a37d41f983b Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:35:28 +0800 Subject: [PATCH 054/101] spi: phytium: Add a timeout to the polled transfer mode poll_transfer() spins until the RX pointer reaches the end with no timeout, so a hung or wedged controller keeps the CPU spinning forever inside the transfer. Bound the loop with a generous deadline based on the transfer length and report -ETIMEDOUT when it expires. Fixes: caf2f8ff84f6 ("arm64: spi: add Phytium SPI controller support") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-phytium.c b/drivers/spi/spi-phytium.c index 86bdb6ff8733b..0dd8b253b89fc 100644 --- a/drivers/spi/spi-phytium.c +++ b/drivers/spi/spi-phytium.c @@ -210,11 +210,19 @@ static irqreturn_t phytium_spi_irq(int irq, void *dev_id) static int poll_transfer(struct phytium_spi *fts) { + /* generous cap so a hung controller cannot spin forever */ + unsigned long deadline = jiffies + msecs_to_jiffies(100 + fts->len); + do { phytium_writer(fts); phytium_reader(fts); cpu_relax(); - } while (fts->rx_end > fts->rx); + } while (fts->rx_end > fts->rx && !time_after(jiffies, deadline)); + + if (fts->rx_end > fts->rx) { + dev_err(&fts->master->dev, "transfer timed out\n"); + return -ETIMEDOUT; + } return 0; } From 4c7c441e7d2f62b742dde7df9a4d8e0463ba8ac3 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:35:28 +0800 Subject: [PATCH 055/101] spi: phytium: Guard the clock division against a zero transfer speed phytium_spi_transfer_one() computes fts->max_freq / transfer->speed_hz without validating the speed. The SPI core substitutes a zero speed with spi->max_speed_hz, which can still be zero when the controller clock was never configured; the division then panics. Refuse such transfers with -EINVAL, mirroring the DMA path. Fixes: caf2f8ff84f6 ("arm64: spi: add Phytium SPI controller support") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/spi/spi-phytium.c b/drivers/spi/spi-phytium.c index 0dd8b253b89fc..b090454057b9c 100644 --- a/drivers/spi/spi-phytium.c +++ b/drivers/spi/spi-phytium.c @@ -238,6 +238,16 @@ static int phytium_spi_transfer_one(struct spi_controller *master, u32 cr0; int ret = 0; + /* + * The SPI core substitutes a zero speed with spi->max_speed_hz, + * which can still be zero when the controller clock was never + * configured. Refuse instead of dividing by zero below. + */ + if (!transfer->speed_hz) { + dev_err(&master->dev, "transfer speed is zero\n"); + return -EINVAL; + } + fts->dma_mapped = 0; fts->tx = (void *)transfer->tx_buf; fts->tx_end = fts->tx + transfer->len; From 903df8858bb360f3e2ce0cf21fc77d3e93415988 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:35:28 +0800 Subject: [PATCH 056/101] spi: phytium-qspi: Check the direct mapping window before assigning phytium_qspi_dirmap_create() keeps assigning flash->base at qspi->mm_base + used_size and growing used_size by each flash size without ever comparing against the mapped window, so flashes larger than the window silently map outside it. Reject the dirmap with -EOPNOTSUPP when the accumulated sizes no longer fit, matching how spi-nor falls back to regular transfers when dirmap creation fails. Fixes: 9f2e21865eb1 ("arm64: spi: Phytium-qspi: Add support for Phytium QSPI controller") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-qspi.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index 9792fc6d5feba..e74612c816bfd 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -493,6 +493,12 @@ static int phytium_qspi_dirmap_create(struct spi_mem_dirmap_desc *desc) } if (!flash->base) { + if (qspi->used_size + nor->mtd.size > qspi->mm_size) { + dev_err(qspi->dev, + "direct mapping window too small for the flashes\n"); + ret = -EOPNOTSUPP; + goto out; + } flash->base = qspi->mm_base + qspi->used_size; qspi->used_size += nor->mtd.size; } From dad5f5b1e4f3540c8d50e16bc7cf3395e27a4a7e Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:35:29 +0800 Subject: [PATCH 057/101] spi: phytium-qspi: Mask the flash count to the CAP NUM field width qspi->fnum can reach PHYTIUM_QSPI_MAX_NORCHIP (4) but the CAP NUM field of QSPI_FLASH_CAP_REG is only 2 bits wide, so the count was written shifted into the neighbouring CAP field bits. Mask the value to the field and warn when it does not fit. Fixes: 9f2e21865eb1 ("arm64: spi: Phytium-qspi: Add support for Phytium QSPI controller") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-qspi.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index e74612c816bfd..54b8674f0d0da 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -823,7 +823,11 @@ static int phytium_qspi_probe(struct platform_device *pdev) goto probe_setup_failed; } - flash_cap |= qspi->fnum << QSPI_FLASH_CAP_NUM_SHIFT; + if (qspi->fnum > (QSPI_FLASH_CAP_NUM_MASK >> QSPI_FLASH_CAP_NUM_SHIFT)) + dev_warn(dev, "%u flashes exceed the CAP NUM field width\n", + qspi->fnum); + flash_cap |= (qspi->fnum << QSPI_FLASH_CAP_NUM_SHIFT) & + QSPI_FLASH_CAP_NUM_MASK; writel_relaxed(flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); } else { From 43a7b56d7f4981e896dd6bc240e56e198d0c331e Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:35:29 +0800 Subject: [PATCH 058/101] spi: phytium-qspi: Allow building without device tree The QSPI driver gained ACPI support (PHYT0011 match and the ACPI probe branch), but its Kconfig entry still depends on OF, so an ACPI-only configuration cannot build the driver that exists for it. Relax the dependency to OF || ACPI, as the probe code checks dev->of_node before using OF helpers. Fixes: 7f4fb2e5cdaf ("arm64: phytium: UEFI mode acpi table support for qspi/spi driver") Signed-off-by: Wentao Guan --- drivers/spi/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 6e478567ab11f..e66227e0c3313 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -882,7 +882,7 @@ config SPI_PHYTIUM_PCI config SPI_PHYTIUM_QSPI tristate "Phytium Quad SPI controller" depends on ARCH_PHYTIUM || COMPILE_TEST - depends on OF + depends on OF || ACPI depends on SPI_MEM help This enables support for Phytium Quad SPI flash controller. From 49fbc4e2ec274666140e65a7e8ed0063e4e0d7e2 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:35:29 +0800 Subject: [PATCH 059/101] ALSA: hda: intel: Harden the Glenfly frame buffer setup gf_init_pci() carved the audio stream and BDL windows out of the display BAR with no validation at all: - it computed diu_fb_base + fb_size - 20 MB without checking that the BAR is at least that large, so a smaller frame buffer makes the offset wrap around and ioremap_wc() arbitrary memory; - none of the four ioremap_wc() results was checked, leaving NULL mappings that the PCM paths happily dereference later; - the caller in azx_first_init() ignored the return value, so a failed setup kept the half-initialized state. Validate the BAR size, unwind and fail cleanly when a mapping fails, and log an error in the caller when the setup fails while keeping the controller usable without the DIU frame buffer path. Fixes: 86858d4871e3 ("add gf hdaudio 001 patch in deepin kernel 6.6") Signed-off-by: Wentao Guan --- sound/hda/controllers/intel.c | 37 ++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c index c686599255043..6aa2c8ae1008c 100644 --- a/sound/hda/controllers/intel.c +++ b/sound/hda/controllers/intel.c @@ -415,6 +415,18 @@ static int gf_init_pci(struct azx *chip) diu_fb_base = pci_resource_start(diu_pci, 1); fb_size = pci_resource_len(diu_pci, 1); + /* + * The audio stream and BDL windows are carved out of + * the last 20 MB of the display frame buffer; make + * sure the BAR is at least that large. + */ + if (fb_size < (4 + 16) * 1024 * 1024) { + dev_err(chip->card->dev, + "gf_hda display frame buffer too small\n"); + pci_dev_put(diu_pci); + return -ENOMEM; + } + diu_fb_stream[0] = diu_fb_base + fb_size - (4+16)*1024*1024; gf_chip->diu_fb_stream_ofs[0] = diu_fb_stream[0] - diu_fb_base; // stream offset = fb_size -4M-16M gf_chip->diu_fb_stream_vaddr[0] = ioremap_wc(diu_fb_stream[0], GF_HDA_FB_STREAM_SIZE); // size = 7M @@ -431,6 +443,25 @@ static int gf_init_pci(struct azx *chip) gf_chip->diu_fb_bdl_ofs[1] = diu_fb_bdl[1] - diu_fb_base; // stream offset = fb_size -4M-16M+7M*2+4K gf_chip->diu_fb_bdl_vaddr[1] = ioremap_wc(diu_fb_bdl[1], BDL_SIZE); // size = 4K + if (!gf_chip->diu_fb_stream_vaddr[0] || !gf_chip->diu_fb_stream_vaddr[1] || + !gf_chip->diu_fb_bdl_vaddr[0] || !gf_chip->diu_fb_bdl_vaddr[1]) { + dev_err(chip->card->dev, "gf_hda can't map display frame buffer\n"); + if (gf_chip->diu_fb_stream_vaddr[0]) + iounmap(gf_chip->diu_fb_stream_vaddr[0]); + if (gf_chip->diu_fb_stream_vaddr[1]) + iounmap(gf_chip->diu_fb_stream_vaddr[1]); + if (gf_chip->diu_fb_bdl_vaddr[0]) + iounmap(gf_chip->diu_fb_bdl_vaddr[0]); + if (gf_chip->diu_fb_bdl_vaddr[1]) + iounmap(gf_chip->diu_fb_bdl_vaddr[1]); + gf_chip->diu_fb_stream_vaddr[0] = NULL; + gf_chip->diu_fb_stream_vaddr[1] = NULL; + gf_chip->diu_fb_bdl_vaddr[0] = NULL; + gf_chip->diu_fb_bdl_vaddr[1] = NULL; + pci_dev_put(diu_pci); + return -ENOMEM; + } + dev_info(chip->card->dev, "gf_hda diu fb base=0x%llx, size=%dM.\n", diu_fb_base, (unsigned int)(fb_size >> 20)); pci_dev_put(diu_pci); } @@ -2005,7 +2036,11 @@ static int azx_first_init(struct azx *chip) */ if (chip->driver_type == AZX_DRIVER_GFHDMI) { bus->polling_mode = 1; - gf_init_pci(chip); + if (gf_init_pci(chip)) + /* the controller still works, only the DIU frame + * buffer path stays disabled */ + dev_err(chip->card->dev, + "gf_hda frame buffer setup failed\n"); } if (chip->driver_type == AZX_DRIVER_LOONGSON) { From e87de12d8afda9c463f30fcdfa380fea6bfca81f Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Mon, 7 Sep 2026 17:35:29 +0800 Subject: [PATCH 060/101] dt-bindings: spi: phytium: Fix the example and document driver properties The example node failed its own schema: it lacked the required #address-cells/#size-cells and used the GIC_SPI/IRQ_TYPE_LEVEL_HIGH macros without including their definitions. Fix the example and add unevaluatedProperties: false. Also document the properties the driver consumes: num-cs (already required but unspecified), reg-io-width, global-cs and the rx/tx dmas/dma-names pair used to enable DMA mode. Fixes: 0b4bb76a7e80 ("dt-bindings: spi: phyhtium: add bindings for Phytium SPI") Signed-off-by: Wentao Guan --- .../devicetree/bindings/spi/phytium,spi.yaml | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Documentation/devicetree/bindings/spi/phytium,spi.yaml b/Documentation/devicetree/bindings/spi/phytium,spi.yaml index 983c592bae444..fb6cf65cdc7d7 100644 --- a/Documentation/devicetree/bindings/spi/phytium,spi.yaml +++ b/Documentation/devicetree/bindings/spi/phytium,spi.yaml @@ -28,6 +28,35 @@ properties: maxItems: 1 description: spi clock phandle + num-cs: + $ref: /schemas/types.yaml#/definitions/uint32 + description: number of chip selects used + default: 4 + + reg-io-width: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [ 4 ] + description: I/O register width in bytes + default: 4 + + global-cs: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [ 0, 1 ] + description: | + When set to 1, the controller drives all chip selects through a + single global chip-select line (GCSR mode). + default: 1 + + dmas: + minItems: 2 + maxItems: 2 + description: TX and RX DMA channel specifiers + + dma-names: + items: + - const: rx + - const: tx + required: - compatible - "#address-cells" @@ -37,11 +66,16 @@ required: - clocks - num-cs +unevaluatedProperties: false + examples: - | + #include spi0: spi@2800c000 { compatible = "phytium,spi"; + #address-cells = <1>; + #size-cells = <0>; interrupts = ; reg = <0x0 0x2800c000 0x0 0x1000>; clocks = <&sysclk_48mhz>; From 6ea1a1dd23cf4a857e08aafbc4ebf0726fde2336 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 14:07:57 +0800 Subject: [PATCH 061/101] spi: phytium-common: Capture spi_phytium_set return value in write MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit spi_phytium_write() calls spi_phytium_set(fts) without assigning its return value to ret, so the if (ret) error check that follows is dead code — controller errors are silently ignored and the function continues to send data to a controller in an error state. Fixes: b4cb0174adb1 ("spi-v2: phytium: Add the debug log function to the driver") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-phytium-common.c b/drivers/spi/spi-phytium-common.c index 8ff76bb1565aa..b92007769ec30 100644 --- a/drivers/spi/spi-phytium-common.c +++ b/drivers/spi/spi-phytium-common.c @@ -332,7 +332,7 @@ int spi_phytium_write(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, u64 tx_addr; if (spi_write_flag == 1) { - spi_phytium_set(fts); + ret = spi_phytium_set(fts); if (ret) { dev_err(&fts->master->dev, "AP <-> RV interaction failed\n"); return ret; From b320fa1e4450c5ff0b491803d15e963edae541a1 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 14:07:57 +0800 Subject: [PATCH 062/101] spi: phytium-common: Remove unconditional data[24] overwrite in xfer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit spi_phytium_xfer() sets fts->msg->data[24] via an if/else (1 for the first chunk, flags for subsequent chunks) but then unconditionally overwrites it with first, so flags are never actually sent on non-first chunks — the controller always receives 0 instead. Remove the spurious overwrite so the if/else takes effect. Fixes: 5e49cfbb5912 ("spi-v2: phytium: Add support for full-duplex transmission mode") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-common.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/spi/spi-phytium-common.c b/drivers/spi/spi-phytium-common.c index b92007769ec30..df0e793306b94 100644 --- a/drivers/spi/spi-phytium-common.c +++ b/drivers/spi/spi-phytium-common.c @@ -505,7 +505,6 @@ int spi_phytium_xfer(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, fts->msg->data[24] = 1; else fts->msg->data[24] = flags; - fts->msg->data[24] = first; ret = spi_phytium_set(fts); if (ret) { dev_err(&fts->master->dev, "AP <-> RV interaction failed\n"); From 12e0b2204f4997b834c263eb9669a91d3e14c264 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 14:07:57 +0800 Subject: [PATCH 063/101] spi: phytium-common: Cap DMA chunk size at SPI_TRANS_DATA_SIZE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit spi_phytium_read() and spi_phytium_xfer() compute the DMA chunk size as min_t(u32, remaining, remaining) — both arguments are identical, so the entire remaining buffer is transferred in one chunk with no upper bound. spi_phytium_write_pre() correctly caps at SPI_TRANS_DATA_SIZE; the read and xfer paths do not. Use SPI_TRANS_DATA_SIZE as the second min_t argument to cap the chunk size, matching the write path. Fixes: 3674931621b2 ("arm64: spi: Phytium: Adapt SPI driver to use DDMA interface") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-common.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-phytium-common.c b/drivers/spi/spi-phytium-common.c index df0e793306b94..6551f3c6dc66a 100644 --- a/drivers/spi/spi-phytium-common.c +++ b/drivers/spi/spi-phytium-common.c @@ -403,7 +403,7 @@ int spi_phytium_read(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, do { if (fts->dma_get_ddrdata) len = min_t(u32, (u32)(fts->rx_end - fts->rx), - (u32)(fts->rx_end - fts->rx)); + (u32)SPI_TRANS_DATA_SIZE); else len = min_t(u32, (u32)(fts->rx_end - fts->rx), 128); @@ -465,7 +465,7 @@ int spi_phytium_xfer(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, do { if (fts->dma_get_ddrdata) len = min_t(u32, (u32)(fts->rx_end - fts->rx), - (u32)(fts->rx_end - fts->rx)); + (u32)SPI_TRANS_DATA_SIZE); else len = min_t(u32, (u32)(fts->rx_end - fts->rx), 128); From cee531ab05df6311f49e23774146afac49f18a71 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 14:08:21 +0800 Subject: [PATCH 064/101] spi: phytium-plat-v2: Validate gpiod_count before using as kcalloc count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gpiod_count() can return a negative error code when no GPIO descriptors are found, which was then used directly as the count for devm_kcalloc — a negative size_t becomes a huge number, the allocation fails and returns NULL, and the function continues with fts->cs set to NULL. Check n <= 0 and skip the GPIO CS path when no descriptors are available. Also add a NULL check on the kcalloc return. Fixes: b4cb0174adb1 ("spi-v2: phytium: Add the debug log function to the driver") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-plat-v2.c | 40 ++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/drivers/spi/spi-phytium-plat-v2.c b/drivers/spi/spi-phytium-plat-v2.c index 88ea8bc324447..ff2d8fe8767d2 100644 --- a/drivers/spi/spi-phytium-plat-v2.c +++ b/drivers/spi/spi-phytium-plat-v2.c @@ -49,8 +49,9 @@ static ssize_t debug_store(struct device *dev, size_t size) { u8 loc, dis_en, status = 0; - char *p; + char *p, *orig; char *token; + int ret = 0; long value; u32 reg; struct phytium_spi *fts = dev_get_drvdata(dev); @@ -62,23 +63,32 @@ static ssize_t debug_store(struct device *dev, if (!p) return -ENOMEM; strscpy(p, buf, size); + orig = p; token = strsep(&p, " "); - if (!token) - return -EINVAL; + if (!token) { + ret = -EINVAL; + goto out_free; + } status = kstrtol(token, 0, &value); - if (status) - return status; + if (status) { + ret = status; + goto out_free; + } loc = (u8)value; token = strsep(&p, " "); - if (!token) - return -EINVAL; + if (!token) { + ret = -EINVAL; + goto out_free; + } status = kstrtol(token, 0, &value); - if (status) - return status; + if (status) { + ret = status; + goto out_free; + } dis_en = value; reg = phytium_read_regfile(fts, SPI_REGFILE_DEBUG); @@ -104,9 +114,10 @@ static ssize_t debug_store(struct device *dev, phytium_write_regfile(fts, SPI_REGFILE_DEBUG, reg); - kfree(p); +out_free: + kfree(orig); - return size; + return ret ? ret : size; } static DEVICE_ATTR_RW(debug); @@ -224,8 +235,14 @@ static int spi_phyt_probe(struct platform_device *pdev) struct gpio_desc *gpiod; n = gpiod_count(&pdev->dev, "cs"); + if (n <= 0) + goto skip_cs_gpio; cs = devm_kcalloc(&pdev->dev, n, sizeof(int), GFP_KERNEL); + if (!cs) { + ret = -ENOMEM; + goto out; + } fts->cs = cs; for (i = 0; i < n; i++) { @@ -242,6 +259,7 @@ static int spi_phyt_probe(struct platform_device *pdev) } } +skip_cs_gpio: device_property_read_u32(&pdev->dev, "global-cs", &global_cs); fts->global_cs = global_cs; From f306d979f2a7a2420fc641fdd7def46a23cd5df8 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 15:53:57 +0800 Subject: [PATCH 065/101] spi: phytium-v2: Fix remove callback, ioremap check and timer lifecycle Three bugs in the V2 platform/adapter code: - spi_phyt_remove() returns int but struct platform_driver.remove is void in 6.18; convert it and drop the return statement. - devm_ioremap() reports failure with NULL, not an error pointer; the IS_ERR() check let a failed mapping through and the next loop dereferenced address zero. Check for NULL instead. - The watchdog timer was armed with add_timer() before controller registration, the error path did not cancel it, and removal used non-synchronizing del_timer(). Initialize with timer_setup(), arm with mod_timer() only after successful registration, cancel with del_timer_sync() on both the error and remove paths. Fixes: b4cb0174adb1 ("spi-v2: phytium: Add the debug log function to the driver") Fixes: 5e49cfbb5912 ("spi-v2: phytium: Add support for full-duplex transmission mode") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-plat-v2.c | 4 +--- drivers/spi/spi-phytium-v2.c | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/drivers/spi/spi-phytium-plat-v2.c b/drivers/spi/spi-phytium-plat-v2.c index ff2d8fe8767d2..c8dc35dc38b88 100644 --- a/drivers/spi/spi-phytium-plat-v2.c +++ b/drivers/spi/spi-phytium-plat-v2.c @@ -285,15 +285,13 @@ static int spi_phyt_probe(struct platform_device *pdev) return ret; } -static int spi_phyt_remove(struct platform_device *pdev) +static void spi_phyt_remove(struct platform_device *pdev) { struct phytium_spi *fts = platform_get_drvdata(pdev); spi_phyt_remove_host(fts); sysfs_remove_group(&pdev->dev.kobj, &spi_phyt_device_group); clk_disable_unprepare(fts->clk); - - return 0; } #ifdef CONFIG_PM_SLEEP diff --git a/drivers/spi/spi-phytium-v2.c b/drivers/spi/spi-phytium-v2.c index 1cd633f0a3a67..e89ca077aed1f 100644 --- a/drivers/spi/spi-phytium-v2.c +++ b/drivers/spi/spi-phytium-v2.c @@ -473,7 +473,7 @@ static void spi_phyt_hw_init(struct device *dev, struct phytium_spi *fts) fts->log_size = ((reg & SPI_REGFILE_SIZE_MASK) >> 4) * SPI_DEBUG_LOG_SIZE; fts->log = devm_ioremap(dev, fts->ddr_paddr, fts->log_size); - if (IS_ERR(fts->log)) { + if (!fts->log) { dev_err(dev, "log_addr is err\n"); return; } @@ -536,9 +536,7 @@ int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts) fts->watchdog = spi_watchdog; fts->handle_debug_err = spi_handle_debug_err; - fts->timer.expires = jiffies + msecs_to_jiffies(50); timer_setup(&fts->timer, spi_phyt_timer_handle, 0); - add_timer(&fts->timer); spi_phyt_hw_init(dev, fts); @@ -548,9 +546,12 @@ int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts) goto err_exit; } + mod_timer(&fts->timer, jiffies + msecs_to_jiffies(50)); + return 0; err_exit: + del_timer_sync(&fts->timer); spi_phyt_enable_chip(fts, 0); err_free_master: spi_master_put(master); @@ -560,7 +561,7 @@ EXPORT_SYMBOL_GPL(spi_phyt_add_host); void spi_phyt_remove_host(struct phytium_spi *fts) { - del_timer(&fts->timer); + del_timer_sync(&fts->timer); spi_phyt_shutdown_chip(fts); } EXPORT_SYMBOL_GPL(spi_phyt_remove_host); @@ -573,6 +574,8 @@ int spi_phyt_suspend_host(struct phytium_spi *fts) if (ret) return ret; + /* stop the watchdog timer before shutting down the chip */ + del_timer_sync(&fts->timer); spi_phyt_shutdown_chip(fts); return 0; } @@ -589,9 +592,14 @@ int spi_phyt_resume_host(struct phytium_spi *fts) spi_phyt_enable_chip(fts, 1); ret = spi_controller_resume(fts->master); - if (ret) + if (ret) { dev_err(&fts->master->dev, "fail to start queue (%d)\n", ret); - return ret; + return ret; + } + + /* restart the watchdog timer after a successful resume */ + mod_timer(&fts->timer, jiffies + msecs_to_jiffies(50)); + return 0; } EXPORT_SYMBOL_GPL(spi_phyt_resume_host); From 6cde3e819a59f155b414102c12baf610e8845243 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 15:53:57 +0800 Subject: [PATCH 066/101] spi: phytium-common: Replace __virt_to_phys with DMA mapping API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The V2 adapter passed SPI transfer buffer virtual addresses through __virt_to_phys() to firmware for DMA. This is not a DMA mapping and is invalid for vmalloc/highmem buffers and on IOMMU systems — the resulting physical address may not be the address the device actually DMAs to, causing data corruption or silent failures. Replace all five call sites with dma_map_single()/dma_unmap_single() using DMA_TO_DEVICE / DMA_FROM_DEVICE directions, with proper error checking via dma_mapping_error() and unmap after each transfer chunk. Fixes: b4cb0174adb1 ("spi-v2: phytium: Add the debug log function to the driver") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-common.c | 40 ++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/drivers/spi/spi-phytium-common.c b/drivers/spi/spi-phytium-common.c index 6551f3c6dc66a..4ea8f4f5dba43 100644 --- a/drivers/spi/spi-phytium-common.c +++ b/drivers/spi/spi-phytium-common.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -226,8 +227,9 @@ void spi_phytium_write_pre(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, u8 t } if (len > 16 && fts->dma_get_ddrdata) { - tx_addr = (u64)__virt_to_phys((u64)fts->tx); - if (!tx_addr) { + tx_addr = dma_map_single(&fts->master->dev, fts->tx, len, + DMA_TO_DEVICE); + if (dma_mapping_error(&fts->master->dev, tx_addr)) { dev_err(&fts->master->dev, "tx address translation failed\n"); return; } @@ -358,8 +360,9 @@ int spi_phytium_write(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, } if (len > 16 && fts->dma_get_ddrdata) { - tx_addr = __virt_to_phys((u64)fts->tx); - if (!tx_addr) { + tx_addr = dma_map_single(&fts->master->dev, fts->tx, len, + DMA_TO_DEVICE); + if (dma_mapping_error(&fts->master->dev, tx_addr)) { dev_err(&fts->master->dev, "tx address translation failed\n"); return -1; } @@ -379,6 +382,9 @@ int spi_phytium_write(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, fts->msg->data[16] = flags; fts->msg->data[17] = first; ret = spi_phytium_set(fts); + if (len > 16 && fts->dma_get_ddrdata) + dma_unmap_single(&fts->master->dev, tx_addr, len, + DMA_TO_DEVICE); if (ret) { dev_err(&fts->master->dev, "AP <-> RV interaction failed\n"); return ret; @@ -413,8 +419,9 @@ int spi_phytium_read(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, if (len > 16 && fts->dma_get_ddrdata) { fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_DMA_RX; - rx_addr = __virt_to_phys((u64)fts->rx); - if (!rx_addr) { + rx_addr = dma_map_single(&fts->master->dev, fts->rx, len, + DMA_FROM_DEVICE); + if (dma_mapping_error(&fts->master->dev, rx_addr)) { dev_err(&fts->master->dev, "rx address translation failed\n"); return -1; } @@ -437,6 +444,9 @@ int spi_phytium_read(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, fts->msg->data[16] = 0; fts->msg->data[17] = first; ret = spi_phytium_set(fts); + if (len > 16 && fts->dma_get_ddrdata) + dma_unmap_single(&fts->master->dev, rx_addr, len, + DMA_FROM_DEVICE); if (ret) { dev_err(&fts->master->dev, "AP <-> RV interaction failed\n"); return ret; @@ -476,13 +486,17 @@ int spi_phytium_xfer(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, if (len > 16 && fts->dma_get_ddrdata) { fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_DMA_XFER; - tx_addr = __virt_to_phys((u64)fts->tx); - if (!tx_addr) { + tx_addr = dma_map_single(&fts->master->dev, fts->tx, len, + DMA_TO_DEVICE); + if (dma_mapping_error(&fts->master->dev, tx_addr)) { dev_err(&fts->master->dev, "tx address translation failed\n"); return -1; } - rx_addr = __virt_to_phys((u64)fts->rx); - if (!rx_addr) { + rx_addr = dma_map_single(&fts->master->dev, fts->rx, len, + DMA_FROM_DEVICE); + if (dma_mapping_error(&fts->master->dev, rx_addr)) { + dma_unmap_single(&fts->master->dev, tx_addr, len, + DMA_TO_DEVICE); dev_err(&fts->master->dev, "rx address translation failed\n"); return -1; } @@ -506,6 +520,12 @@ int spi_phytium_xfer(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, else fts->msg->data[24] = flags; ret = spi_phytium_set(fts); + if (len > 16 && fts->dma_get_ddrdata) { + dma_unmap_single(&fts->master->dev, tx_addr, len, + DMA_TO_DEVICE); + dma_unmap_single(&fts->master->dev, rx_addr, len, + DMA_FROM_DEVICE); + } if (ret) { dev_err(&fts->master->dev, "AP <-> RV interaction failed\n"); return ret; From 6f50842cd4a7556605bbd1ef22149ae3119c7efb Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 15:53:57 +0800 Subject: [PATCH 067/101] spi: phytium-qspi: Fix protocol validation, ret init, alloc ownership and capacity encoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six bugs in the QSPI driver: - phytium_spi_nor_protocol_encode() silently encoded unsupported phase combinations (e.g. 1-2-4) as 1-1-1, sending them with the wrong protocol, while it required a nonzero width on every phase and thus rejected phase-less operations such as WREN. Normalize absent phases to the active single-bit width so command-only operations encode as 1-1-1, return -EOPNOTSUPP for genuinely unsupported combinations, and make supports_op() apply the same encoder check so an accepted operation can never fail in exec_op(); the now unused check_buswidth() helper is removed. - phytium_qspi_exec_op() left ret uninitialized on the command-only path; initialize it to 0 so success is deterministic. - spi_alloc_host() was never put on probe failure or removal, and the single probe_master_put label was also reached after successful registration, where the devres action still owns the controller — double release. Switch to devm_spi_alloc_host() so every exit, before or after registration, releases the reference exactly once. - The capacity-setup loops used fnum as the upper bound, so a sparse configuration (e.g. a single flash at CS1) only inspected flash[0], and the same-size check compared against an unpopulated CS0. Iterate all PHYTIUM_QSPI_MAX_NORCHIP slots, skip empty ones and compare against the first populated slot. - Size discovery ran only in the direct-mapping branch, so the nodirmap path always encoded zero sizes and failed probe. Discover all flash sizes before selecting either capacity encoding path. - The nodirmap branch unconditionally computed (fnum-1) << 16, which underflows to all-ones when no flash was instantiated. Guard it with fnum != 0. Fixes: 9f2e21865eb1 ("arm64: spi: Phytium-qspi: Add support for Phytium QSPI controller") Fixes: 839098a4be05 ("qspi: phytium: Use the corresponding configurations for each chip") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-qspi.c | 177 +++++++++++++++------------------ 1 file changed, 82 insertions(+), 95 deletions(-) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index 54b8674f0d0da..79a90de0ccda3 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -183,18 +183,6 @@ struct phytium_qspi { u32 flash_cap; }; -static bool phytium_qspi_check_buswidth(u8 width) -{ - switch (width) { - case 1: - case 2: - case 4: - return 0; - } - - return -EOPNOTSUPP; -} - static uint phytium_spi_nor_clac_clk_div(int div) { uint clk_div = 0; @@ -221,40 +209,34 @@ static uint phytium_spi_nor_clac_clk_div(int div) static int phytium_spi_nor_protocol_encode(const struct spi_mem_op *op, u32 *code) { - int ret = 0; - - if (op->cmd.buswidth == 1 && - op->addr.buswidth == 1 && - op->data.buswidth == 1) + /* + * Phases absent from the operation (e.g. the address and data + * phases of SPI_NOR_WREN_OP) carry a buswidth of zero; treat them + * as the active single-bit width so such operations encode as + * 1-1-1 instead of being rejected. + */ + u8 cmd_width = op->cmd.buswidth ?: 1; + u8 addr_width = op->addr.buswidth ?: 1; + u8 data_width = op->data.buswidth ?: 1; + + if (cmd_width == 1 && addr_width == 1 && data_width == 1) *code = XFER_PROTO_1_1_1; - else if (op->cmd.buswidth == 1 && - op->addr.buswidth == 1 && - op->data.buswidth == 2) + else if (cmd_width == 1 && addr_width == 1 && data_width == 2) *code = XFER_PROTO_1_1_2; - else if (op->cmd.buswidth == 1 && - op->addr.buswidth == 1 && - op->data.buswidth == 4) + else if (cmd_width == 1 && addr_width == 1 && data_width == 4) *code = XFER_PROTO_1_1_4; - else if (op->cmd.buswidth == 1 && - op->addr.buswidth == 2 && - op->data.buswidth == 2) + else if (cmd_width == 1 && addr_width == 2 && data_width == 2) *code = XFER_PROTO_1_2_2; - else if (op->cmd.buswidth == 1 && - op->addr.buswidth == 4 && - op->data.buswidth == 4) + else if (cmd_width == 1 && addr_width == 4 && data_width == 4) *code = XFER_PROTO_1_4_4; - else if (op->cmd.buswidth == 2 && - op->addr.buswidth == 2 && - op->data.buswidth == 2) + else if (cmd_width == 2 && addr_width == 2 && data_width == 2) *code = XFER_PROTO_2_2_2; - else if (op->cmd.buswidth == 4 && - op->addr.buswidth == 4 && - op->data.buswidth == 4) + else if (cmd_width == 4 && addr_width == 4 && data_width == 4) *code = XFER_PROTO_4_4_4; else - *code = XFER_PROTO_1_1_1; + return -EOPNOTSUPP; - return ret; + return 0; } static int phytium_qspi_flash_capacity_encode_new(u32 size, @@ -386,20 +368,15 @@ static int phytium_qspi_adjust_op_size(struct spi_mem *mem, static bool phytium_qspi_supports_op(struct spi_mem *mem, const struct spi_mem_op *op) { - int ret; - - ret = phytium_qspi_check_buswidth(op->cmd.buswidth); - - if (op->addr.nbytes) - ret |= phytium_qspi_check_buswidth(op->addr.buswidth); - - if (op->dummy.nbytes) - ret |= phytium_qspi_check_buswidth(op->dummy.buswidth); - - if (op->data.nbytes) - ret |= phytium_qspi_check_buswidth(op->data.buswidth); - - if (ret) + u32 code; + + /* + * Apply the same protocol encoding check exec_op() uses, so an + * operation accepted here can never fail later with + * -EOPNOTSUPP. Phase-less operations (e.g. WREN) are normalized + * to 1-bit by the encoder and accepted as 1-1-1. + */ + if (phytium_spi_nor_protocol_encode(op, &code)) return false; /* Max 32 dummy clock cycles supported */ @@ -416,7 +393,7 @@ static int phytium_qspi_exec_op(struct spi_mem *mem, struct phytium_qspi *qspi = spi_controller_get_devdata(mem->spi->controller); struct phytium_qspi_flash *flash = &qspi->flash[spi_get_chipselect(mem->spi, 0)]; u32 cmd, transfer; - int ret; + int ret = 0; dev_dbg(qspi->dev, "cmd:%#x mode: %d.%d.%d.%d addr:%#llx len:%#x\n", op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth, @@ -679,7 +656,7 @@ static int phytium_qspi_probe(struct platform_device *pdev) struct spi_nor *nor; const char **reg_name_array; - ctrl = spi_alloc_host(dev, sizeof(*qspi)); + ctrl = devm_spi_alloc_host(dev, sizeof(*qspi)); if (!ctrl) return -ENOMEM; @@ -790,58 +767,70 @@ static int phytium_qspi_probe(struct platform_device *pdev) goto probe_setup_failed; } - if (!qspi->nodirmap && qspi->fnum != 0) { + if (qspi->fnum != 0) { + int first_flash = -1; + /* - * The controller supports direct mapping access only if all - * flashes are of same size. + * Discover the size of every bound flash first; the capacity + * encoding below needs it in both dirmap and nodirmap modes. */ - - i = 0; - for (i = 0; qspi->fnum > i && i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { - if (qspi->flash[i].spi) { - mem = spi_get_drvdata(qspi->flash[i].spi); - if (mem) { - nor = spi_mem_get_drvdata(mem); - if (nor) - qspi->flash[i].size = nor->mtd.size; - } + for (i = 0; i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { + if (!qspi->flash[i].spi) + continue; + if (first_flash < 0) + first_flash = i; + mem = spi_get_drvdata(qspi->flash[i].spi); + if (mem) { + nor = spi_mem_get_drvdata(mem); + if (nor) + qspi->flash[i].size = nor->mtd.size; } } - for (i = 1; qspi->fnum > i && i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { - if (qspi->flash[i].size != qspi->flash[0].size) { - dev_err(dev, "Flashes are of different sizes.\n"); - ret = -EINVAL; - goto probe_setup_failed; + if (!qspi->nodirmap) { + /* + * The controller supports direct mapping access only if + * all flashes are of same size. + */ + for (i = 0; i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { + if (!qspi->flash[i].spi || i == first_flash) + continue; + if (qspi->flash[i].size != qspi->flash[first_flash].size) { + dev_err(dev, "Flashes are of different sizes.\n"); + ret = -EINVAL; + goto probe_setup_failed; + } } - } - - ret = phytium_qspi_flash_capacity_encode(qspi->flash[0].size, - &flash_cap); - if (ret) { - dev_err(dev, "Flash size is invalid.\n"); - goto probe_setup_failed; - } - - if (qspi->fnum > (QSPI_FLASH_CAP_NUM_MASK >> QSPI_FLASH_CAP_NUM_SHIFT)) - dev_warn(dev, "%u flashes exceed the CAP NUM field width\n", - qspi->fnum); - flash_cap |= (qspi->fnum << QSPI_FLASH_CAP_NUM_SHIFT) & - QSPI_FLASH_CAP_NUM_MASK; - writel_relaxed(flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); - } else { - for (i = 0; qspi->fnum > i && i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { - ret = phytium_qspi_flash_capacity_encode_new(qspi->flash[i].size, - &qspi->flash_cap, i); + ret = phytium_qspi_flash_capacity_encode(qspi->flash[first_flash].size, + &flash_cap); if (ret) { dev_err(dev, "Flash size is invalid.\n"); goto probe_setup_failed; } - } - qspi->flash_cap |= (qspi->fnum - 1) << QSPI_FLASH_CAP_NUM_SHIFT_NEW; - writel_relaxed(qspi->flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); + if (qspi->fnum > (QSPI_FLASH_CAP_NUM_MASK >> QSPI_FLASH_CAP_NUM_SHIFT)) + dev_warn(dev, "%u flashes exceed the CAP NUM field width\n", + qspi->fnum); + flash_cap |= (qspi->fnum << QSPI_FLASH_CAP_NUM_SHIFT) & + QSPI_FLASH_CAP_NUM_MASK; + + writel_relaxed(flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); + } else { + for (i = 0; i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { + if (!qspi->flash[i].spi) + continue; + ret = phytium_qspi_flash_capacity_encode_new(qspi->flash[i].size, + &qspi->flash_cap, i); + if (ret) { + dev_err(dev, "Flash size is invalid.\n"); + goto probe_setup_failed; + } + } + qspi->flash_cap |= (qspi->fnum - 1) << QSPI_FLASH_CAP_NUM_SHIFT_NEW; + + writel_relaxed(qspi->flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); + } } return 0; @@ -853,8 +842,6 @@ static int phytium_qspi_probe(struct platform_device *pdev) pm_runtime_put_sync(dev); pm_runtime_disable(dev); } -probe_master_put: - return ret; } From 5476a80477fc1f85638b9d2b460f6beeacc989e4 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 15:53:57 +0800 Subject: [PATCH 068/101] spi: phytium: Change bus_num from u16 to int for dynamic numbering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PCI driver stores -1 in bus_num to request dynamic SPI bus numbering, but the field is u16, so -1 becomes 65535 — a fixed invalid bus number instead of the SPI core's dynamic sentinel. Change the type to int, matching the SPI core's bus_num field. Fixes: caf2f8ff84f6 ("arm64: spi: add Phytium SPI controller support") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-phytium.h b/drivers/spi/spi-phytium.h index 758333c0cd3af..5e6c87d523680 100644 --- a/drivers/spi/spi-phytium.h +++ b/drivers/spi/spi-phytium.h @@ -191,7 +191,7 @@ struct phytium_spi { u32 max_freq; u32 reg_io_width; - u16 bus_num; + int bus_num; u16 num_cs; int *cs; From b9699dfbb11ed1b1813f4c5f22c71ea590061efb Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 15:53:57 +0800 Subject: [PATCH 069/101] mtd: partitions: Propagate fwnode to partition devices and add cleanup The ACPI partition parser stored a fwnode reference in parts[i].fwnode, but allocate_partition() never propagated it to the child MTD device and the parser had no cleanup callback, so every stored reference leaked. Propagate part->fwnode to child->dev.fwnode in allocate_partition() and add a custom cleanup to acpipart that calls fwnode_handle_put() on each partition's fwnode before freeing the array. Fixes: 7f4fb2e5cdaf ("arm64: phytium: UEFI mode acpi table support for qspi/spi driver") Signed-off-by: Wentao Guan --- drivers/mtd/mtdpart.c | 1 + drivers/mtd/parsers/acpipart_core.c | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index 1c1bfdbaf1a1f..e0b709aba5755 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -92,6 +92,7 @@ static struct mtd_info *allocate_partition(struct mtd_info *parent, child->dev.parent = IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER) || mtd_is_partition(parent) ? &parent->dev : parent->dev.parent; child->dev.of_node = part->of_node; + child->dev.fwnode = part->fwnode; child->parent = parent; child->part.offset = part->offset; INIT_LIST_HEAD(&child->partitions); diff --git a/drivers/mtd/parsers/acpipart_core.c b/drivers/mtd/parsers/acpipart_core.c index e272f63bc21e2..246503a6982db 100644 --- a/drivers/mtd/parsers/acpipart_core.c +++ b/drivers/mtd/parsers/acpipart_core.c @@ -130,8 +130,18 @@ static const struct acpi_device_id parse_acpipart_match_table[] = { MODULE_DEVICE_TABLE(acpi, parse_acpipart_match_table); +static void acpipart_cleanup(const struct mtd_partition *pparts, int nr_parts) +{ + int i; + + for (i = 0; i < nr_parts; i++) + fwnode_handle_put(pparts[i].fwnode); + kfree(pparts); +} + static struct mtd_part_parser acpipart_parser = { .parse_fn = parse_acpi_fixed_partitions, + .cleanup = acpipart_cleanup, .name = "acpi-fixed-partitions", .acpi_match_table = ACPI_PTR(parse_acpipart_match_table), }; From 91dc92a9ad5c89ff53cd839d79f018ba9ca7f645 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 15:53:57 +0800 Subject: [PATCH 070/101] ALSA: hda: phytium: Fix probe failure cleanup and drop resume stream hack Two bugs in the Phytium HDA controller: - azx_probe_continue() failure path only freed the IRQ but left the card in driver data, so the platform device stayed bound to an unusable controller and its remove/shutdown/PM callbacks would dereference the freed card. Clear the driver data before freeing the card, mirroring the snd-hda-intel failure path. - azx_resume() restored suspended streams by mutating runtime->status->state and calling the low-level prepare callback directly, bypassing the ALSA state machine and its locking, leaving resumed streams inconsistent or unusable. Drop the manual restore entirely and leave PCM state restoration to the ALSA core, as snd-hda-intel does. This also removes the hda_ft substream member, which only served the removed hack. Fixes: 8da93e4a61f7 ("hda: phytium: Add Phytium hda driver support") Signed-off-by: Wentao Guan --- sound/hda/controllers/phytium.c | 36 +++++++++++++-------------------- sound/hda/controllers/phytium.h | 1 - 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/sound/hda/controllers/phytium.c b/sound/hda/controllers/phytium.c index e0431fa4a19da..43457b8afd0ed 100644 --- a/sound/hda/controllers/phytium.c +++ b/sound/hda/controllers/phytium.c @@ -456,10 +456,6 @@ static int azx_resume(struct device *dev) struct azx *chip; struct hda_ft *hda; struct hdac_bus *bus; - int index; - struct snd_pcm_substream *substream; - struct azx_dev *azx_dev; - int err; if (!card) return 0; @@ -473,8 +469,6 @@ static int azx_resume(struct device *dev) if (azx_acquire_irq(chip, 1) < 0) return -EIO; - index = chip->dev_index; - snd_hdac_bus_exit_link_reset(bus); usleep_range(1000, 1200); @@ -482,21 +476,6 @@ static int azx_resume(struct device *dev) snd_power_change_state(card, SNDRV_CTL_POWER_D0); - if (hda->substream && hda->substream->runtime) { - substream = hda->substream; - - if (substream->runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { - substream->runtime->status->state = - substream->runtime->status->suspended_state; - err = substream->ops->prepare(substream); - if (err < 0) - return err; - } - - azx_dev = get_azx_dev(substream); - hda->substream = NULL; - } - return 0; } #endif /* CONFIG_PM_SLEEP */ @@ -630,7 +609,9 @@ static int azx_free(struct azx *chip) bus->irq = -1; } - devm_iounmap(hddev, bus->remap_addr); + /* remap_addr may be NULL when called from an early probe failure */ + if (bus->remap_addr) + devm_iounmap(hddev, bus->remap_addr); azx_free_stream_pages(chip); azx_free_streams(chip); @@ -1019,6 +1000,17 @@ static int azx_probe_continue(struct azx *chip) free_irq(bus->irq, (void *)chip); bus->irq = -1; } + chip->disabled = 1; + /* release the reserved slot so a later rebind can reclaim it */ + clear_bit(chip->dev_index, probed_devs); + /* + * Clear the driver data before freeing the card: the platform + * device stays bound and its remove/shutdown/PM callbacks must + * not dereference the freed card, mirroring snd-hda-intel. + */ + dev_set_drvdata(hddev, NULL); + if (chip->card) + snd_card_free(chip->card); return err; } diff --git a/sound/hda/controllers/phytium.h b/sound/hda/controllers/phytium.h index a2183ef5e0d83..09ac99f0c29c9 100644 --- a/sound/hda/controllers/phytium.h +++ b/sound/hda/controllers/phytium.h @@ -11,7 +11,6 @@ struct hda_ft { struct azx chip; - struct snd_pcm_substream *substream; struct device *dev; void __iomem *regs; From 822ae63eb94901523a49e63ce6a400829ecfd6d6 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 17:29:55 +0800 Subject: [PATCH 071/101] mtd: acpipart: Remove the device-ID gate and fix slc-mode semantics Two bugs in the ACPI partition parser: - parse_acpi_fixed_partitions() matched the MTD flash device itself against the parser's sole ACPI ID "acpi-partitions" and returned without parsing in dedicated mode when the match failed. The flash device is bound with a different _HID (PHYT8009 or a JEDEC ID), so the match always failed and dedicated mode never parsed anything. The caller has already selected this parser by name through the "fixed" property; drop the unrelated device-ID gate and its now unused acpi_id variable. - The "slc-mode" property was applied to mask_flags, which removes the flag from the partition, instead of enabling the capability. The OF parser uses add_flags for the same property; do the same here. Fixes: 7f4fb2e5cdaf ("arm64: phytium: UEFI mode acpi table support for qspi/spi driver") Signed-off-by: Wentao Guan --- drivers/mtd/parsers/acpipart_core.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/mtd/parsers/acpipart_core.c b/drivers/mtd/parsers/acpipart_core.c index 246503a6982db..bd8956dda17d0 100644 --- a/drivers/mtd/parsers/acpipart_core.c +++ b/drivers/mtd/parsers/acpipart_core.c @@ -20,7 +20,6 @@ static int parse_acpi_fixed_partitions(struct mtd_info *master, struct mtd_part_parser_data *data) { struct mtd_partition *parts; - const struct acpi_device_id *acpi_id; const char *partname; int nr_parts, i, ret = 0; struct fwnode_handle *child_handle = NULL; @@ -45,9 +44,13 @@ static int parse_acpi_fixed_partitions(struct mtd_info *master, } } - acpi_id = acpi_match_device(parse_acpipart_match_table, dev); - if (dedicated && !acpi_id) - return 0; + /* + * The caller already selected this parser by name through the + * "fixed" property, so there is nothing to match here: matching + * the MTD device against the parser's ACPI IDs would fail, as the + * flash device itself carries a different _HID (e.g. PHYT8009 or + * a JEDEC ID). + */ nr_parts = 0; device_for_each_child_node(dev, child_handle) { @@ -95,7 +98,7 @@ static int parse_acpi_fixed_partitions(struct mtd_info *master, parts[i].mask_flags |= MTD_POWERUP_LOCK; bool_match = fwnode_property_read_bool(child_handle, "slc-mode"); if (bool_match) - parts[i].mask_flags |= MTD_SLC_ON_MLC_EMULATION; + parts[i].add_flags |= MTD_SLC_ON_MLC_EMULATION; i++; } @@ -110,7 +113,12 @@ static int parse_acpi_fixed_partitions(struct mtd_info *master, pr_err("%s: error parsing acpipart partition %pfw (%pfw)\n", master->name, child_handle, dev->fwnode); ret = -EINVAL; + /* the iterator still holds a reference to the current child */ + fwnode_handle_put(child_handle); acpipart_none: + /* entries collected so far keep their own references */ + for (i = 0; i < nr_parts; i++) + fwnode_handle_put(parts[i].fwnode); kfree(parts); return ret; } From 46a2d1da9893cb87ded9bee8f1ead0f0085f1721 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 17:33:25 +0800 Subject: [PATCH 072/101] spi: phytium-v2: Fix setup div-by-zero, completion race, ioremap leak and xfer tx advance Four bugs in the V2 SPI adapter: - spi_phyt_setup() computed max_freq / spi->max_speed_hz with no validation; firmware can provide a zero max_speed_hz, panicking during device registration. Reject it with -EINVAL, as the v1 transfer path already does. - check_result() reinitialized cmd_completion only after the AP2RV notification had already been written, so a fast reply IRQ landing in between was discarded by reinit_completion() and the transfer waited the full timeout. Move reinit_completion() to before each notification, on all five AP-to-RV sites. - spi_phyt_hw_init() runs from both probe and resume and performed a devm_ioremap() of the firmware log each time; managed mappings are retained until device removal, so every suspend/resume cycle leaked another mapping and devres entry. Map only when fts->log is still NULL. - spi_phytium_xfer() advanced only fts->rx between chunks, so every chunk after the first resent the first TX block. Advance fts->tx by the same length. Fixes: b4cb0174adb1 ("spi-v2: phytium: Add the debug log function to the driver") Fixes: 5e49cfbb5912 ("spi-v2: phytium: Add support for full-duplex transmission mode") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-common.c | 7 ++++++- drivers/spi/spi-phytium-v2.c | 24 ++++++++++++++++++------ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/drivers/spi/spi-phytium-common.c b/drivers/spi/spi-phytium-common.c index 4ea8f4f5dba43..03d7a107f37d1 100644 --- a/drivers/spi/spi-phytium-common.c +++ b/drivers/spi/spi-phytium-common.c @@ -114,7 +114,6 @@ int spi_phytium_check_result(struct phytium_spi *fts) unsigned long long ms = 300000; struct msg *msg = (struct msg *)fts->tx_shmem_addr; - reinit_completion(&fts->cmd_completion); ms = wait_for_completion_timeout(&fts->cmd_completion, msecs_to_jiffies(ms)); if (ms == 0) { @@ -130,6 +129,7 @@ int spi_phytium_set(struct phytium_spi *fts) int ret; spi_phytium_show_msg(fts->msg); + reinit_completion(&fts->cmd_completion); phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); ret = spi_phytium_check_result(fts); @@ -143,6 +143,7 @@ void spi_phytium_default(struct phytium_spi *fts) fts->msg->cmd_id = PHYTSPI_MSG_CMD_DEFAULT; spi_phytium_show_msg(fts->msg); + reinit_completion(&fts->cmd_completion); phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); spi_phytium_check_result(fts); } @@ -161,6 +162,7 @@ void spi_phytium_set_cmd8(struct phytium_spi *fts, u16 sub_cmd, spi_phytium_set_subid(fts, sub_cmd); fts->msg->data[0] = data; spi_phytium_show_msg(fts->msg); + reinit_completion(&fts->cmd_completion); phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); spi_phytium_check_result(fts); } @@ -175,6 +177,7 @@ void spi_phytium_set_cmd16(struct phytium_spi *fts, u16 sub_cmd, spi_phytium_set_subid(fts, sub_cmd); *cp_data = data; spi_phytium_show_msg(fts->msg); + reinit_completion(&fts->cmd_completion); phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); spi_phytium_check_result(fts); } @@ -189,6 +192,7 @@ void spi_phytium_set_cmd32(struct phytium_spi *fts, u16 sub_cmd, spi_phytium_set_subid(fts, sub_cmd); *cp_data = data; spi_phytium_show_msg(fts->msg); + reinit_completion(&fts->cmd_completion); phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); spi_phytium_check_result(fts); } @@ -533,6 +537,7 @@ int spi_phytium_xfer(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, if (len <= 16 || !fts->dma_get_ddrdata) memcpy_byte(fts->rx, (void *)smem_rx, len); + fts->tx += len; fts->rx += len; first = 0; } while (fts->rx_end > fts->rx); diff --git a/drivers/spi/spi-phytium-v2.c b/drivers/spi/spi-phytium-v2.c index e89ca077aed1f..5437eac9ff699 100644 --- a/drivers/spi/spi-phytium-v2.c +++ b/drivers/spi/spi-phytium-v2.c @@ -327,6 +327,11 @@ static int spi_phyt_setup(struct spi_device *spi) spi_phyt_enable_chip(fts, 0); + if (!spi->max_speed_hz) { + dev_err(&spi->dev, "max_speed_hz is zero\n"); + return -EINVAL; + } + clk_div = (fts->max_freq / spi->max_speed_hz + 1) & 0xfffe; spi_phyt_set_clk(fts, clk_div); fts->clk_div = clk_div; @@ -471,15 +476,22 @@ static void spi_phyt_hw_init(struct device *dev, struct phytium_spi *fts) } fts->log_size = ((reg & SPI_REGFILE_SIZE_MASK) >> 4) * SPI_DEBUG_LOG_SIZE; - fts->log = devm_ioremap(dev, fts->ddr_paddr, fts->log_size); + /* + * Map the firmware log buffer only once: hw_init also runs from + * the resume path and a devm_ioremap() there would leak a mapping + * and a devres entry on every suspend/resume cycle. + */ if (!fts->log) { - dev_err(dev, "log_addr is err\n"); - return; - } + fts->log = devm_ioremap(dev, fts->ddr_paddr, fts->log_size); + if (!fts->log) { + dev_err(dev, "log_addr is err\n"); + return; + } - for (i = 0; i < fts->log_size; i++) - fts->log[i] = 0; + for (i = 0; i < fts->log_size; i++) + fts->log[i] = 0; + } } int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts) From 6b541a164b0cfce5761cced0d8aaeae831e6ce78 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 17:37:39 +0800 Subject: [PATCH 073/101] ALSA: hda: Use proper MMIO access for Glenfly frame buffer writes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Glenfly frame buffer and BDL mappings are ioremap_wc() MMIO regions accessed through void __iomem * pointers, but the common controller code used plain memcpy() to write into them and, for the BDL, read back previously written entries directly from the WC mapping. This is not a portable MMIO access pattern — WC reads can return stale data on some architectures. Build the rewritten BDL in a normal-memory stack buffer first, then flush it to the WC mapping with memcpy_toio(). Replace the plain memcpy() calls in gf_pre_trigger() and gf_update_stream() with memcpy_toio() as well. Fixes: 86858d4871e3 ("add gf hdaudio 001 patch in deepin kernel 6.6") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-qspi.c | 18 +++++------ sound/hda/common/controller.c | 56 ++++++++++++++++++++++------------ 2 files changed, 45 insertions(+), 29 deletions(-) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index 79a90de0ccda3..6028da1049fa3 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -677,7 +677,7 @@ static int phytium_qspi_probe(struct platform_device *pdev) GFP_KERNEL); if (!reg_name_array) { ret = -ENOMEM; - goto probe_master_put; + return ret; } if (dev->of_node) { res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi"); @@ -686,7 +686,7 @@ static int phytium_qspi_probe(struct platform_device *pdev) if (!res) { dev_err(dev, "missing QSPI register resource\n"); ret = -ENODEV; - goto probe_master_put; + return ret; } fwnode_property_read_string_array(dev->fwnode, "reg-names", reg_name_array, 2); @@ -696,7 +696,7 @@ static int phytium_qspi_probe(struct platform_device *pdev) qspi->io_base = devm_ioremap_resource(dev, res); if (IS_ERR(qspi->io_base)) { ret = PTR_ERR(qspi->io_base); - goto probe_master_put; + return ret; } if (dev->of_node) { @@ -706,7 +706,7 @@ static int phytium_qspi_probe(struct platform_device *pdev) if (!res) { dev_err(dev, "missing QSPI memory resource\n"); ret = -ENODEV; - goto probe_master_put; + return ret; } if (reg_name_array[1]) res->name = reg_name_array[1]; @@ -715,13 +715,13 @@ static int phytium_qspi_probe(struct platform_device *pdev) qspi->mm_base = devm_ioremap_resource(dev, res); if (IS_ERR(qspi->mm_base)) { ret = PTR_ERR(qspi->mm_base); - goto probe_master_put; + return ret; } qspi->mm_size = resource_size(res); if (qspi->mm_size > PHYTIUM_QSPI_MAX_MMAP_SZ) { ret = -EINVAL; - goto probe_master_put; + return ret; } qspi->used_size = 0; @@ -729,20 +729,20 @@ static int phytium_qspi_probe(struct platform_device *pdev) qspi->clk = devm_clk_get(dev, NULL); if (IS_ERR(qspi->clk)) { ret = PTR_ERR(qspi->clk); - goto probe_master_put; + return ret; } qspi->clk_rate = clk_get_rate(qspi->clk); if (!qspi->clk_rate) { ret = -EINVAL; - goto probe_master_put; + return ret; } pm_runtime_enable(dev); ret = pm_runtime_get_sync(dev); if (ret < 0) { pm_runtime_put_noidle(dev); - goto probe_master_put; + return ret; } ret = clk_prepare_enable(qspi->clk); diff --git a/sound/hda/common/controller.c b/sound/hda/common/controller.c index 70c6c63be42d8..66bb751e6d2e2 100644 --- a/sound/hda/common/controller.c +++ b/sound/hda/common/controller.c @@ -104,21 +104,35 @@ static int gf_setup_bdle(struct snd_pcm_substream *substream) } stream_idx = apcm->codec->addr - 1; if ((stream_idx <= 1) && (gf_chip->diu_fb_bdl_vaddr[stream_idx])) { - if (azx_dev->core.bdl.bytes <= BDL_SIZE) { - memcpy(gf_chip->diu_fb_bdl_vaddr[stream_idx], azx_dev->core.bdl.area, azx_dev->core.bdl.bytes); - } else { - memcpy(gf_chip->diu_fb_bdl_vaddr[stream_idx], azx_dev->core.bdl.area, BDL_SIZE); - } - bdl = (__le32 *)gf_chip->diu_fb_bdl_vaddr[stream_idx]; - for (i = 0; i < azx_dev->core.frags; i++) { - if (i > 0) { - bdl[i*4] = cpu_to_le32((u32)(bdl[(i-1)*4] + bdl[(i-1)*4+2])); - bdl[i*4+1] = cpu_to_le32(upper_32_bits(gf_chip->diu_fb_stream_ofs[stream_idx])); - } - else { - bdl[i*4] = cpu_to_le32((u32)gf_chip->diu_fb_stream_ofs[stream_idx]); - bdl[i*4+1] = cpu_to_le32(upper_32_bits(gf_chip->diu_fb_stream_ofs[stream_idx])); + /* + * Rewrite the BDL address entries in a normal-memory copy + * first, then flush it to the WC mapping with a single + * memcpy_toio(). Reading back from a WC ioremap mapping is + * not a portable MMIO pattern and can return stale data on + * some architectures. Allocate the copy before writing to + * the mapping, so an allocation failure leaves the mapping + * untouched instead of a stale uncorrected BDL. + */ + { + __le32 *tmp; + u32 copy_bytes = min_t(u32, azx_dev->core.bdl.bytes, BDL_SIZE); + + tmp = kmemdup(azx_dev->core.bdl.area, copy_bytes, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + bdl = tmp; + for (i = 0; i < azx_dev->core.frags; i++) { + if (i > 0) { + bdl[i*4] = cpu_to_le32((u32)(le32_to_cpu(bdl[(i-1)*4]) + le32_to_cpu(bdl[(i-1)*4+2]))); + bdl[i*4+1] = cpu_to_le32(upper_32_bits(gf_chip->diu_fb_stream_ofs[stream_idx])); + } + else { + bdl[i*4] = cpu_to_le32((u32)gf_chip->diu_fb_stream_ofs[stream_idx]); + bdl[i*4+1] = cpu_to_le32(upper_32_bits(gf_chip->diu_fb_stream_ofs[stream_idx])); + } } + memcpy_toio(gf_chip->diu_fb_bdl_vaddr[stream_idx], tmp, copy_bytes); + kfree(tmp); } snd_hdac_stream_writel((azx_stream(azx_dev)), SD_BDLPL, (u32)gf_chip->diu_fb_bdl_ofs[stream_idx]); snd_hdac_stream_writel((azx_stream(azx_dev)), SD_BDLPU, upper_32_bits(gf_chip->diu_fb_bdl_ofs[stream_idx])); @@ -144,7 +158,7 @@ static int gf_pre_trigger(struct snd_pcm_substream *substream, int cmd) (stream_idx <= 1) && (gf_chip->diu_fb_stream_vaddr[stream_idx]) && (substream->runtime->dma_area) && (substream->runtime->dma_bytes <= GF_HDA_FB_STREAM_SIZE)) { - memcpy(gf_chip->diu_fb_stream_vaddr[stream_idx], substream->runtime->dma_area, substream->runtime->dma_bytes); + memcpy_toio(gf_chip->diu_fb_stream_vaddr[stream_idx], substream->runtime->dma_area, substream->runtime->dma_bytes); gf_chip->diu_fb_stream_pos[stream_idx] = 0; } } @@ -170,16 +184,16 @@ static int gf_update_stream(struct snd_pcm_substream *substream) appl_pos = frames_to_bytes(substream->runtime, substream->runtime->control->appl_ptr % substream->runtime->buffer_size); if (hw_pos == appl_pos) { - memcpy(gf_chip->diu_fb_stream_vaddr[stream_idx], substream->runtime->dma_area, substream->runtime->dma_bytes); + memcpy_toio(gf_chip->diu_fb_stream_vaddr[stream_idx], substream->runtime->dma_area, substream->runtime->dma_bytes); } else if (appl_pos > gf_chip->diu_fb_stream_pos[stream_idx]) { - memcpy(gf_chip->diu_fb_stream_vaddr[stream_idx] + gf_chip->diu_fb_stream_pos[stream_idx], substream->runtime->dma_area + gf_chip->diu_fb_stream_pos[stream_idx], (appl_pos - gf_chip->diu_fb_stream_pos[stream_idx])); + memcpy_toio(gf_chip->diu_fb_stream_vaddr[stream_idx] + gf_chip->diu_fb_stream_pos[stream_idx], substream->runtime->dma_area + gf_chip->diu_fb_stream_pos[stream_idx], (appl_pos - gf_chip->diu_fb_stream_pos[stream_idx])); } else if (appl_pos < gf_chip->diu_fb_stream_pos[stream_idx]) { if(substream->runtime->dma_bytes > gf_chip->diu_fb_stream_pos[stream_idx]) { - memcpy(gf_chip->diu_fb_stream_vaddr[stream_idx] + gf_chip->diu_fb_stream_pos[stream_idx], substream->runtime->dma_area + gf_chip->diu_fb_stream_pos[stream_idx], (substream->runtime->dma_bytes - gf_chip->diu_fb_stream_pos[stream_idx])); + memcpy_toio(gf_chip->diu_fb_stream_vaddr[stream_idx] + gf_chip->diu_fb_stream_pos[stream_idx], substream->runtime->dma_area + gf_chip->diu_fb_stream_pos[stream_idx], (substream->runtime->dma_bytes - gf_chip->diu_fb_stream_pos[stream_idx])); } - memcpy(gf_chip->diu_fb_stream_vaddr[stream_idx], substream->runtime->dma_area, appl_pos); + memcpy_toio(gf_chip->diu_fb_stream_vaddr[stream_idx], substream->runtime->dma_area, appl_pos); } gf_chip->diu_fb_stream_pos[stream_idx] = appl_pos; } @@ -289,7 +303,9 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream) snd_hdac_stream_setup(azx_stream(azx_dev), false); - gf_setup_bdle(substream); + err = gf_setup_bdle(substream); + if (err < 0) + return err; stream_tag = azx_dev->core.stream_tag; /* CA-IBG chips need the playback stream starting from 1 */ From 8bdaf7d939324e6136a55348b84e509e791f2754 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 18:08:52 +0800 Subject: [PATCH 074/101] spi: phytium: Drop the architecture gate from the hidden SPI_PHYTIUM symbol The front-end symbols SPI_PHYTIUM_PLAT and SPI_PHYTIUM_PCI allow COMPILE_TEST on non-Phytium architectures but select SPI_PHYTIUM, whose direct "depends on ARCH_PHYTIUM" is then unmet: Kconfig reports an unmet direct dependency and the compile-test paths never build the core. Follow the in-tree convention (e.g. SPI_ROCKCHIP_spi selection chains) of leaving architecture gating to the user-visible front-end symbols and dropping the depends from the hidden helper symbol. Fixes: caf2f8ff84f6 ("arm64: spi: add Phytium SPI controller support") Signed-off-by: Wentao Guan --- drivers/spi/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index e66227e0c3313..d59c02379ee17 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -854,7 +854,6 @@ config SPI_PCI1XXXX config SPI_PHYTIUM tristate - depends on ARCH_PHYTIUM config SPI_PHYTIUM_PLAT tristate "Phytium SPI controller platform support" From 59cd367368791a2a5d6c963a96690123de79d156 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 18:09:32 +0800 Subject: [PATCH 075/101] spi: phytium-v2: Bound the command timeout and reject bufferless transfers Two issues in the V2 SPI adapter: - check_result() waited for the firmware response with a hard-coded 300000 ms timeout, so a single unavailable RV endpoint stalled device probing or an SPI operation for five minutes. Use the already defined SPI_MASTER_TIMEOUT (8 ms), matching how long a healthy AP-to-RV round trip is expected to take. - spi_phyt_transfer_one() accepted transfers with both buffers NULL; the tx/rx end pointers were then constructed from NULL arithmetic and the function fell through to a return of an uninitialized ret, reporting an arbitrary result without clocking anything. Reject this unsupported transfer shape with -EOPNOTSUPP up front. Fixes: b4cb0174adb1 ("spi-v2: phytium: Add the debug log function to the driver") Fixes: 5e49cfbb5912 ("spi-v2: phytium: Add support for full-duplex transmission mode") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-common.c | 5 +++-- drivers/spi/spi-phytium-v2.c | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-phytium-common.c b/drivers/spi/spi-phytium-common.c index 03d7a107f37d1..145743dcaba73 100644 --- a/drivers/spi/spi-phytium-common.c +++ b/drivers/spi/spi-phytium-common.c @@ -111,10 +111,11 @@ int spi_phytium_print_status(struct phytium_spi *fts, u8 status0, int spi_phytium_check_result(struct phytium_spi *fts) { - unsigned long long ms = 300000; + unsigned long ms; struct msg *msg = (struct msg *)fts->tx_shmem_addr; - ms = wait_for_completion_timeout(&fts->cmd_completion, msecs_to_jiffies(ms)); + ms = wait_for_completion_timeout(&fts->cmd_completion, + msecs_to_jiffies(SPI_MASTER_TIMEOUT)); if (ms == 0) { dev_err(&fts->master->dev, "SPI controller timed out\n"); diff --git a/drivers/spi/spi-phytium-v2.c b/drivers/spi/spi-phytium-v2.c index 5437eac9ff699..4c5f0855b441a 100644 --- a/drivers/spi/spi-phytium-v2.c +++ b/drivers/spi/spi-phytium-v2.c @@ -152,6 +152,12 @@ static int spi_phyt_transfer_one(struct spi_master *master, if (mem) nor = spi_mem_get_drvdata(mem); + if (!transfer->tx_buf && !transfer->rx_buf) { + /* clock-only transfers without buffers are not supported */ + dev_err(&master->dev, "transfer with no tx/rx buffer\n"); + return -EOPNOTSUPP; + } + fts->tx = (void *)transfer->tx_buf; fts->tx_end = fts->tx + transfer->len; fts->rx = transfer->rx_buf; From 3daf3b3373065805d9b41d3893973ecc732415e3 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 18:10:16 +0800 Subject: [PATCH 076/101] spi: phytium-qspi: Cache the programmed flash capacity in dirmap mode The direct-mapping branch computed flash capacity into a local variable, wrote it to the register and dropped it, while the nodirmap branch caches its value in qspi->flash_cap. The resume path rewrites the capacity register from qspi->flash_cap, so after a suspend/resume cycle in dirmap mode the zero-initialized cache zeroed out the flash capacity the probe had programmed. Cache the programmed value in the dirmap branch as well. Fixes: 9f2e21865eb1 ("arm64: spi: Phytium-qspi: Add support for Phytium QSPI controller") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-qspi.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index 6028da1049fa3..04b3f7f56459c 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -814,8 +814,10 @@ static int phytium_qspi_probe(struct platform_device *pdev) qspi->fnum); flash_cap |= (qspi->fnum << QSPI_FLASH_CAP_NUM_SHIFT) & QSPI_FLASH_CAP_NUM_MASK; + /* cache the programmed value for the resume path */ + qspi->flash_cap = flash_cap; - writel_relaxed(flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); + writel_relaxed(qspi->flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); } else { for (i = 0; i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { if (!qspi->flash[i].spi) From 250272cfe8dbf33aace4f34573b44d053476a2a9 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 22:31:20 +0800 Subject: [PATCH 077/101] spi: phytium: Use IO accessors for the firmware shared memory mapping spi-phytium-plat-v2.c casts the devm_ioremap_resource() mapping of the shared memory to a plain struct msg *; the common code then uses memset, plain dereferences and ordinary copies on it. Those accesses are not valid for an ioremap'd resource and can violate MMIO access width/order requirements on Phytium architectures. Introduce a normal-memory shadow buffer (struct msg msg_buf) alongside the __iomem pointer, populate it with ordinary field writes, and flush it to the mapping with memcpy_toio() at each notification boundary. Read the response status back with memcpy_fromio(). Convert the secondary message slot in spi_phytium_write_pre() to the same pattern. Replace the smem_tx/smem_rx u64 arithmetic with __iomem pointer arithmetic and use memcpy_toio()/memcpy_fromio() for those transfers as well. Fixes: b4cb0174adb1 ("spi-v2: phytium: Add the debug log function to the driver") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-common.c | 238 ++++++++++++++++-------------- drivers/spi/spi-phytium-plat-v2.c | 2 +- drivers/spi/spi-phytium.h | 3 +- 3 files changed, 127 insertions(+), 116 deletions(-) diff --git a/drivers/spi/spi-phytium-common.c b/drivers/spi/spi-phytium-common.c index 145743dcaba73..f798c2120c8cd 100644 --- a/drivers/spi/spi-phytium-common.c +++ b/drivers/spi/spi-phytium-common.c @@ -112,7 +112,7 @@ int spi_phytium_print_status(struct phytium_spi *fts, u8 status0, int spi_phytium_check_result(struct phytium_spi *fts) { unsigned long ms; - struct msg *msg = (struct msg *)fts->tx_shmem_addr; + struct msg *msg = &fts->msg_buf; ms = wait_for_completion_timeout(&fts->cmd_completion, msecs_to_jiffies(SPI_MASTER_TIMEOUT)); @@ -122,6 +122,9 @@ int spi_phytium_check_result(struct phytium_spi *fts) return -1; } + /* read the response status only after the firmware completed */ + memcpy_fromio(&fts->msg_buf, fts->msg, sizeof(struct msg)); + return spi_phytium_print_status(fts, msg->status0, msg->status1); } @@ -129,7 +132,8 @@ int spi_phytium_set(struct phytium_spi *fts) { int ret; - spi_phytium_show_msg(fts->msg); + spi_phytium_show_msg(&fts->msg_buf); + memcpy_toio(fts->msg, &fts->msg_buf, sizeof(struct msg)); reinit_completion(&fts->cmd_completion); phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); ret = spi_phytium_check_result(fts); @@ -139,11 +143,12 @@ int spi_phytium_set(struct phytium_spi *fts) void spi_phytium_default(struct phytium_spi *fts) { - memset(fts->msg, 0, sizeof(struct msg)); + memset(&fts->msg_buf, 0, sizeof(struct msg)); - fts->msg->cmd_id = PHYTSPI_MSG_CMD_DEFAULT; + fts->msg_buf.cmd_id = PHYTSPI_MSG_CMD_DEFAULT; - spi_phytium_show_msg(fts->msg); + spi_phytium_show_msg(&fts->msg_buf); + memcpy_toio(fts->msg, &fts->msg_buf, sizeof(struct msg)); reinit_completion(&fts->cmd_completion); phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); spi_phytium_check_result(fts); @@ -152,17 +157,18 @@ EXPORT_SYMBOL_GPL(spi_phytium_default); void spi_phytium_set_subid(struct phytium_spi *fts, u16 sub_cmd) { - fts->msg->cmd_id = PHYTSPI_MSG_CMD_SET; - fts->msg->cmd_subid = sub_cmd; + fts->msg_buf.cmd_id = PHYTSPI_MSG_CMD_SET; + fts->msg_buf.cmd_subid = sub_cmd; } void spi_phytium_set_cmd8(struct phytium_spi *fts, u16 sub_cmd, u8 data) { - memset(fts->msg, 0, sizeof(struct msg)); + memset(&fts->msg_buf, 0, sizeof(struct msg)); spi_phytium_set_subid(fts, sub_cmd); - fts->msg->data[0] = data; - spi_phytium_show_msg(fts->msg); + fts->msg_buf.data[0] = data; + spi_phytium_show_msg(&fts->msg_buf); + memcpy_toio(fts->msg, &fts->msg_buf, sizeof(struct msg)); reinit_completion(&fts->cmd_completion); phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); spi_phytium_check_result(fts); @@ -172,12 +178,13 @@ EXPORT_SYMBOL_GPL(spi_phytium_set_cmd8); void spi_phytium_set_cmd16(struct phytium_spi *fts, u16 sub_cmd, u16 data) { - u16 *cp_data = (u16 *)&fts->msg->data[0]; + u16 *cp_data = (u16 *)&fts->msg_buf.data[0]; - memset(fts->msg, 0, sizeof(struct msg)); + memset(&fts->msg_buf, 0, sizeof(struct msg)); spi_phytium_set_subid(fts, sub_cmd); *cp_data = data; - spi_phytium_show_msg(fts->msg); + spi_phytium_show_msg(&fts->msg_buf); + memcpy_toio(fts->msg, &fts->msg_buf, sizeof(struct msg)); reinit_completion(&fts->cmd_completion); phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); spi_phytium_check_result(fts); @@ -187,12 +194,13 @@ EXPORT_SYMBOL_GPL(spi_phytium_set_cmd16); void spi_phytium_set_cmd32(struct phytium_spi *fts, u16 sub_cmd, u32 data) { - u32 *cp_data = (u32 *)&fts->msg->data[0]; + u32 *cp_data = (u32 *)&fts->msg_buf.data[0]; - memset(fts->msg, 0, sizeof(struct msg)); + memset(&fts->msg_buf, 0, sizeof(struct msg)); spi_phytium_set_subid(fts, sub_cmd); *cp_data = data; - spi_phytium_show_msg(fts->msg); + spi_phytium_show_msg(&fts->msg_buf); + memcpy_toio(fts->msg, &fts->msg_buf, sizeof(struct msg)); reinit_completion(&fts->cmd_completion); phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); spi_phytium_check_result(fts); @@ -201,34 +209,35 @@ EXPORT_SYMBOL_GPL(spi_phytium_set_cmd32); void spi_phytium_data_subid(struct phytium_spi *fts, u16 sub_cmd) { - fts->msg->cmd_id = PHYTSPI_MSG_CMD_DATA; - fts->msg->cmd_subid = sub_cmd; + fts->msg_buf.cmd_id = PHYTSPI_MSG_CMD_DATA; + fts->msg_buf.cmd_subid = sub_cmd; } void spi_phytium_write_pre(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, u8 tmode, u8 flags, u8 spi_write_flag) { - struct msg *msg; u32 len; - u64 smem_tx; + void __iomem *smem_tx; u8 first = 1; u64 tx_addr; + void __iomem *msg_io; + struct msg msg_shadow; len = min((u32)(fts->tx_end - fts->tx), (u32)SPI_TRANS_DATA_SIZE); - msg = (struct msg *)((u64)fts->msg + (sizeof(struct msg) + FLASH_PAGE_SIZE)*spi_write_flag); - memset(msg, 0, sizeof(struct msg)); + msg_io = fts->msg + (sizeof(struct msg) + FLASH_PAGE_SIZE) * spi_write_flag; + memset(&msg_shadow, 0, sizeof(struct msg)); - msg->cmd_id = PHYTSPI_MSG_CMD_DATA; + msg_shadow.cmd_id = PHYTSPI_MSG_CMD_DATA; if (spi_write_flag) { if (len > 16 && fts->dma_get_ddrdata) - msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_FLASH_DMA_TX; + msg_shadow.cmd_subid = PHYTSPI_MSG_CMD_DATA_FLASH_DMA_TX; else - msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_FLASH_TX; + msg_shadow.cmd_subid = PHYTSPI_MSG_CMD_DATA_FLASH_TX; } else { if (len > 16 && fts->dma_get_ddrdata) - msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_DMA_TX; + msg_shadow.cmd_subid = PHYTSPI_MSG_CMD_DATA_DMA_TX; else - msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_TX; + msg_shadow.cmd_subid = PHYTSPI_MSG_CMD_DATA_TX; } if (len > 16 && fts->dma_get_ddrdata) { @@ -238,21 +247,22 @@ void spi_phytium_write_pre(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, u8 t dev_err(&fts->master->dev, "tx address translation failed\n"); return; } - *(u64 *)&msg->data[0] = tx_addr; + *(u64 *)&msg_shadow.data[0] = tx_addr; } else { - smem_tx = (u64)msg + sizeof(struct msg); - memcpy((void *)smem_tx, fts->tx, len); - *(u64 *)&msg->data[0] = sizeof(struct msg); + smem_tx = msg_io + sizeof(struct msg); + memcpy_toio(smem_tx, fts->tx, len); + *(u64 *)&msg_shadow.data[0] = sizeof(struct msg); } - *(u32 *)&msg->data[8] = len; + *(u32 *)&msg_shadow.data[8] = len; fts->tx += len; - msg->data[12] = cs; - msg->data[13] = dfs; - msg->data[14] = mode; - msg->data[15] = tmode; - msg->data[16] = flags; - msg->data[17] = first; + msg_shadow.data[12] = cs; + msg_shadow.data[13] = dfs; + msg_shadow.data[14] = mode; + msg_shadow.data[15] = tmode; + msg_shadow.data[16] = flags; + msg_shadow.data[17] = first; + memcpy_toio(msg_io, &msg_shadow, sizeof(struct msg)); first = 0; } EXPORT_SYMBOL_GPL(spi_phytium_write_pre); @@ -261,45 +271,45 @@ int spi_phytium_flash_erase(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, u8 tmode, u8 flags, u8 cmd) { u32 len; - u64 smem_tx; + void __iomem *smem_tx; u8 first = 1; u8 cmd_addr[8]; int ret; len = (u32)(fts->tx_end - fts->tx); - memset(fts->msg, 0, sizeof(struct msg)); + memset(&fts->msg_buf, 0, sizeof(struct msg)); - fts->msg->cmd_id = PHYTSPI_MSG_CMD_DATA; + fts->msg_buf.cmd_id = PHYTSPI_MSG_CMD_DATA; if (cmd == SPINOR_OP_BE_4K || cmd == SPINOR_OP_CHIP_ERASE) - fts->msg->cmd_subid = PHYTSPI_MSG_CMD_FLASH_ERASE; + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_FLASH_ERASE; else if (cmd == SPINOR_OP_READ || cmd == SPINOR_OP_READ_FAST || cmd == SPINOR_OP_READ_4B || cmd == SPINOR_OP_READ_FAST_4B) - fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_TX; + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_DATA_TX; - smem_tx = (u64)fts->msg + sizeof(struct msg); + smem_tx = fts->msg + sizeof(struct msg); cmd_addr[0] = cmd; if (cmd == SPINOR_OP_BE_4K || cmd == SPINOR_OP_READ || cmd == SPINOR_OP_READ_FAST || cmd == SPINOR_OP_READ_4B || cmd == SPINOR_OP_READ_FAST_4B) { memcpy_byte((void *)&cmd_addr[1], fts->tx, len); - memcpy_byte((void *)smem_tx, (void *)&cmd_addr[0], len + 1); - *(u32 *)&fts->msg->data[8] = len + 1; + memcpy_toio(smem_tx, (void *)&cmd_addr[0], len + 1); + *(u32 *)&fts->msg_buf.data[8] = len + 1; } else if (cmd == SPINOR_OP_CHIP_ERASE) { - memcpy_byte((void *)smem_tx, (void *)&cmd_addr[0], 1); - *(u32 *)&fts->msg->data[8] = len; + memcpy_toio(smem_tx, (void *)&cmd_addr[0], 1); + *(u32 *)&fts->msg_buf.data[8] = len; } - *(u32 *)&fts->msg->data[0] = sizeof(struct msg); + *(u32 *)&fts->msg_buf.data[0] = sizeof(struct msg); fts->tx += len; - fts->msg->data[12] = cs; - fts->msg->data[13] = dfs; - fts->msg->data[14] = mode; - fts->msg->data[15] = tmode; - fts->msg->data[16] = flags; - fts->msg->data[17] = first; + fts->msg_buf.data[12] = cs; + fts->msg_buf.data[13] = dfs; + fts->msg_buf.data[14] = mode; + fts->msg_buf.data[15] = tmode; + fts->msg_buf.data[16] = flags; + fts->msg_buf.data[17] = first; ret = spi_phytium_set(fts); if (ret) { @@ -318,12 +328,12 @@ int spi_phytium_flash_write(struct phytium_spi *fts, u8 cmd) cmd_addr[1] = cmd; memcpy_byte((void *)&cmd_addr[2], fts->tx, fts->len); - fts->msg->data[18] = cmd_addr[0]; - fts->msg->data[19] = cmd_addr[1]; - fts->msg->data[20] = cmd_addr[2]; - fts->msg->data[21] = cmd_addr[3]; - fts->msg->data[22] = cmd_addr[4]; - fts->msg->data[23] = cmd_addr[5]; + fts->msg_buf.data[18] = cmd_addr[0]; + fts->msg_buf.data[19] = cmd_addr[1]; + fts->msg_buf.data[20] = cmd_addr[2]; + fts->msg_buf.data[21] = cmd_addr[3]; + fts->msg_buf.data[22] = cmd_addr[4]; + fts->msg_buf.data[23] = cmd_addr[5]; return 0; } @@ -334,7 +344,7 @@ int spi_phytium_write(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, { int ret = 0; u32 len; - u64 smem_tx; + void __iomem *smem_tx; u8 first = 1; u64 tx_addr; @@ -350,18 +360,18 @@ int spi_phytium_write(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, len = min_t(u32, (u32)(fts->tx_end - fts->tx), (u32)SPI_TRANS_DATA_SIZE); - fts->msg->cmd_id = PHYTSPI_MSG_CMD_DATA; + fts->msg_buf.cmd_id = PHYTSPI_MSG_CMD_DATA; if (spi_write_flag == 2) { if (len > 16 && fts->dma_get_ddrdata) - fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_FLASH_DMA_TX; + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_DATA_FLASH_DMA_TX; else - fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_FLASH_TX; + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_DATA_FLASH_TX; } else { if (len > 16 && fts->dma_get_ddrdata) - fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_DMA_TX; + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_DATA_DMA_TX; else - fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_TX; + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_DATA_TX; } if (len > 16 && fts->dma_get_ddrdata) { @@ -371,21 +381,21 @@ int spi_phytium_write(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, dev_err(&fts->master->dev, "tx address translation failed\n"); return -1; } - *(u64 *)&fts->msg->data[0] = tx_addr; + *(u64 *)&fts->msg_buf.data[0] = tx_addr; } else { - smem_tx = (u64)fts->msg + sizeof(struct msg); - memcpy_byte((void *)smem_tx, fts->tx, len); - *(u64 *)&fts->msg->data[0] = sizeof(struct msg); + smem_tx = fts->msg + sizeof(struct msg); + memcpy_toio(smem_tx, fts->tx, len); + *(u64 *)&fts->msg_buf.data[0] = sizeof(struct msg); } - *(u32 *)&fts->msg->data[8] = len; + *(u32 *)&fts->msg_buf.data[8] = len; fts->tx += len; - fts->msg->data[12] = cs; - fts->msg->data[13] = dfs; - fts->msg->data[14] = mode; - fts->msg->data[15] = tmode; - fts->msg->data[16] = flags; - fts->msg->data[17] = first; + fts->msg_buf.data[12] = cs; + fts->msg_buf.data[13] = dfs; + fts->msg_buf.data[14] = mode; + fts->msg_buf.data[15] = tmode; + fts->msg_buf.data[16] = flags; + fts->msg_buf.data[17] = first; ret = spi_phytium_set(fts); if (len > 16 && fts->dma_get_ddrdata) dma_unmap_single(&fts->master->dev, tx_addr, len, @@ -407,7 +417,7 @@ int spi_phytium_read(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, { int ret; u32 len; - u64 smem_rx; + void __iomem *smem_rx; u8 first = 1; u64 rx_addr; @@ -418,36 +428,36 @@ int spi_phytium_read(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, else len = min_t(u32, (u32)(fts->rx_end - fts->rx), 128); - fts->msg->cmd_id = PHYTSPI_MSG_CMD_DATA; + fts->msg_buf.cmd_id = PHYTSPI_MSG_CMD_DATA; - smem_rx = (u64)fts->msg + sizeof(struct msg); + smem_rx = fts->msg + sizeof(struct msg); if (len > 16 && fts->dma_get_ddrdata) { - fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_DMA_RX; + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_DATA_DMA_RX; rx_addr = dma_map_single(&fts->master->dev, fts->rx, len, DMA_FROM_DEVICE); if (dma_mapping_error(&fts->master->dev, rx_addr)) { dev_err(&fts->master->dev, "rx address translation failed\n"); return -1; } - *(u64 *)&fts->msg->data[0] = rx_addr; + *(u64 *)&fts->msg_buf.data[0] = rx_addr; } else { - fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_RX; - *(u64 *)&fts->msg->data[0] = sizeof(struct msg); + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_DATA_RX; + *(u64 *)&fts->msg_buf.data[0] = sizeof(struct msg); } - *(u32 *)&fts->msg->data[8] = len; - fts->msg->data[12] = cs; - fts->msg->data[13] = dfs; - fts->msg->data[14] = mode; - fts->msg->data[15] = tmode; + *(u32 *)&fts->msg_buf.data[8] = len; + fts->msg_buf.data[12] = cs; + fts->msg_buf.data[13] = dfs; + fts->msg_buf.data[14] = mode; + fts->msg_buf.data[15] = tmode; if (fts->rx_end <= fts->rx + len) - fts->msg->data[16] = flags; + fts->msg_buf.data[16] = flags; else if (first == 1) - fts->msg->data[16] = 1; + fts->msg_buf.data[16] = 1; else - fts->msg->data[16] = 0; - fts->msg->data[17] = first; + fts->msg_buf.data[16] = 0; + fts->msg_buf.data[17] = first; ret = spi_phytium_set(fts); if (len > 16 && fts->dma_get_ddrdata) dma_unmap_single(&fts->master->dev, rx_addr, len, @@ -457,7 +467,7 @@ int spi_phytium_read(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, return ret; } if (len <= 16 || !fts->dma_get_ddrdata) - memcpy_byte(fts->rx, (void *)smem_rx, len); + memcpy_fromio(fts->rx, smem_rx, len); fts->rx += len; first = 0; @@ -472,7 +482,7 @@ int spi_phytium_xfer(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, { int ret; u32 len; - u64 smem_tx, smem_rx; + void __iomem *smem_tx, *smem_rx; u8 first = 1; u64 tx_addr, rx_addr; u64 *data = (u64 *)fts->tx; @@ -484,13 +494,13 @@ int spi_phytium_xfer(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, else len = min_t(u32, (u32)(fts->rx_end - fts->rx), 128); - fts->msg->cmd_id = PHYTSPI_MSG_CMD_DATA; + fts->msg_buf.cmd_id = PHYTSPI_MSG_CMD_DATA; - smem_tx = (u64)fts->msg + sizeof(struct msg); - smem_rx = (u64)fts->msg + sizeof(struct msg) + 128; + smem_tx = fts->msg + sizeof(struct msg); + smem_rx = fts->msg + sizeof(struct msg) + 128; if (len > 16 && fts->dma_get_ddrdata) { - fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_DMA_XFER; + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_DATA_DMA_XFER; tx_addr = dma_map_single(&fts->master->dev, fts->tx, len, DMA_TO_DEVICE); if (dma_mapping_error(&fts->master->dev, tx_addr)) { @@ -506,24 +516,24 @@ int spi_phytium_xfer(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, return -1; } - *(u64 *)&fts->msg->data[0] = tx_addr; - *(u64 *)&fts->msg->data[8] = rx_addr; + *(u64 *)&fts->msg_buf.data[0] = tx_addr; + *(u64 *)&fts->msg_buf.data[8] = rx_addr; } else { - fts->msg->cmd_subid = PHYTSPI_MSG_CMD_DATA_XFER; - memcpy_byte((void *)smem_tx, fts->tx, len); - *(u64 *)&fts->msg->data[0] = sizeof(struct msg); - *(u64 *)&fts->msg->data[8] = sizeof(struct msg) + 128; + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_DATA_XFER; + memcpy_toio(smem_tx, fts->tx, len); + *(u64 *)&fts->msg_buf.data[0] = sizeof(struct msg); + *(u64 *)&fts->msg_buf.data[8] = sizeof(struct msg) + 128; } - *(u32 *)&fts->msg->data[16] = len; - fts->msg->data[20] = cs; - fts->msg->data[21] = dfs; - fts->msg->data[22] = mode; - fts->msg->data[23] = tmode; + *(u32 *)&fts->msg_buf.data[16] = len; + fts->msg_buf.data[20] = cs; + fts->msg_buf.data[21] = dfs; + fts->msg_buf.data[22] = mode; + fts->msg_buf.data[23] = tmode; if (first == 1) - fts->msg->data[24] = 1; + fts->msg_buf.data[24] = 1; else - fts->msg->data[24] = flags; + fts->msg_buf.data[24] = flags; ret = spi_phytium_set(fts); if (len > 16 && fts->dma_get_ddrdata) { dma_unmap_single(&fts->master->dev, tx_addr, len, @@ -536,7 +546,7 @@ int spi_phytium_xfer(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, return ret; } if (len <= 16 || !fts->dma_get_ddrdata) - memcpy_byte(fts->rx, (void *)smem_rx, len); + memcpy_fromio(fts->rx, smem_rx, len); fts->tx += len; fts->rx += len; diff --git a/drivers/spi/spi-phytium-plat-v2.c b/drivers/spi/spi-phytium-plat-v2.c index c8dc35dc38b88..707543a7b5171 100644 --- a/drivers/spi/spi-phytium-plat-v2.c +++ b/drivers/spi/spi-phytium-plat-v2.c @@ -175,7 +175,7 @@ static int spi_phyt_probe(struct platform_device *pdev) return PTR_ERR(fts->tx_shmem_addr); } - fts->msg = (struct msg *)fts->tx_shmem_addr; + fts->msg = fts->tx_shmem_addr; fts->mem_tx = (u64)fts->msg + sizeof(struct msg); fts->mem_rx = (u64)fts->msg + sizeof(struct msg); diff --git a/drivers/spi/spi-phytium.h b/drivers/spi/spi-phytium.h index 5e6c87d523680..fc9321a2e1a1e 100644 --- a/drivers/spi/spi-phytium.h +++ b/drivers/spi/spi-phytium.h @@ -173,7 +173,8 @@ struct phytium_spi { void __iomem *tx_shmem_addr; void *rx_shmem_addr; - struct msg *msg; + struct msg msg_buf; + void __iomem *msg; u32 mem_tx_physic; u32 mem_rx_physic; u64 mem_tx; From a121744d769ab433bd7cf573c60a0879ec847c46 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 22:32:40 +0800 Subject: [PATCH 078/101] spi: phytium-qspi: Validate clock divider before committing setup state phytium_qspi_setup() committed flash->spi, flash->cs and fnum++ before validating the clock divider, so a setup that later returned -EINVAL left the controller with a stale flash entry and an inflated flash count. The post-registration capacity scan then dereferenced this rejected entry. Validate the divider first and only commit flash->spi, flash->cs and fnum when the slot was previously unset, making setup idempotent. Fixes: 9f2e21865eb1 ("arm64: spi: Phytium-qspi: Add support for Phytium QSPI controller") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-qspi.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index 04b3f7f56459c..51170e0da07f5 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -597,20 +597,25 @@ static int phytium_qspi_setup(struct spi_device *spi) flash = &qspi->flash[spi_get_chipselect(spi, 0)]; - flash->cs = spi_get_chipselect(spi, 0); - flash->spi = spi; - qspi->fnum++; - - + /* validate the clock divider before committing any state */ if (spi->max_speed_hz) { clk_div = DIV_ROUND_UP(qspi->clk_rate, spi->max_speed_hz); - flash->clk_div = phytium_spi_nor_clac_clk_div(clk_div); - if (flash->clk_div == 65535) { + clk_div = phytium_spi_nor_clac_clk_div(clk_div); + if (clk_div == 65535) { dev_err(qspi->dev, "qspi maximum frequency setting is error.\n"); return -EINVAL; } - } else + flash->clk_div = clk_div; + } else { flash->clk_div = PHYTIUM_QSPI_DEFAULT_SCK_SEL; + } + + /* install the slot only once; setup may be called repeatedly */ + if (!flash->spi) { + flash->cs = spi_get_chipselect(spi, 0); + flash->spi = spi; + qspi->fnum++; + } return 0; } From 4c56250ea12db94246df7e941789212955289711 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 22:32:41 +0800 Subject: [PATCH 079/101] spi: phytium-v2: Check RV2AP pending state and ack before completing The IRQ handler was registered with IRQF_SHARED but unconditionally completed the command without checking whether this controller raised the interrupt; an interrupt from another device could falsely complete an in-flight SPI command. Acknowledgement also happened after complete(), so a waiter on another CPU could submit a new command before the old status was cleared. Read the RV2AP interrupt state register and return IRQ_NONE when not ours; acknowledge before calling complete() so the old status is cleared first. Fixes: b4cb0174adb1 ("spi-v2: phytium: Add the debug log function to the driver") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-v2.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-phytium-v2.c b/drivers/spi/spi-phytium-v2.c index 4c5f0855b441a..f595456052e65 100644 --- a/drivers/spi/spi-phytium-v2.c +++ b/drivers/spi/spi-phytium-v2.c @@ -102,8 +102,8 @@ static void spi_phyt_set_cs(struct spi_device *spi, bool enable) if (fts->tx || fts->rx) return; - if (fts->msg->cmd_id == PHYTSPI_MSG_CMD_DATA && - fts->msg->cmd_subid == PHYTSPI_MSG_CMD_DATA_TX) + if (fts->msg_buf.cmd_id == PHYTSPI_MSG_CMD_DATA && + fts->msg_buf.cmd_subid == PHYTSPI_MSG_CMD_DATA_TX) return; if (chip && chip->cs_control) @@ -132,10 +132,21 @@ static irqreturn_t spi_phyt_irq(int irq, void *dev_id) { struct spi_master *master = dev_id; struct phytium_spi *fts = spi_master_get_devdata(master); + u32 state; - complete(&fts->cmd_completion); + /* Check whether this controller actually raised the interrupt; + * the handler is registered with IRQF_SHARED. + */ + state = readl_relaxed(fts->regfile + SPI_REGFILE_RV2AP_INTR_STATE); + if (!state) + return IRQ_NONE; + + /* Acknowledge before completing so a waiter on another CPU cannot + * submit a new command before the old status is cleared. + */ writel_relaxed(0, fts->regfile + SPI_REGFILE_RV2AP_INTR_STATE); writel_relaxed(0x10, fts->regfile + SPI_REGFILE_RV2AP_INT_CLEAN); + complete(&fts->cmd_completion); return IRQ_HANDLED; } From c5db55866fe7c79c82d848da1ac7ca4d28bbfc93 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 22:34:53 +0800 Subject: [PATCH 080/101] spi: phytium-v2: Fail probe when firmware is unresponsive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit spi_phytium_default() discarded the timeout/error from spi_phytium_check_result(), so spi_phyt_hw_init() — and therefore spi_phyt_add_host() — continued and registered a controller even when the backing RV firmware never responded, exposing a nonfunctional device. Return the status from spi_phytium_default(), propagate it through spi_phyt_hw_init() (now returning int), and abort probe or resume when the initial command fails. Fixes: b4cb0174adb1 ("spi-v2: phytium: Add the debug log function to the driver") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-common.c | 8 ++++++-- drivers/spi/spi-phytium-v2.c | 25 ++++++++++++++++++++----- drivers/spi/spi-phytium.h | 2 +- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-phytium-common.c b/drivers/spi/spi-phytium-common.c index f798c2120c8cd..3c941a2b3fe2e 100644 --- a/drivers/spi/spi-phytium-common.c +++ b/drivers/spi/spi-phytium-common.c @@ -141,8 +141,10 @@ int spi_phytium_set(struct phytium_spi *fts) return ret; } -void spi_phytium_default(struct phytium_spi *fts) +int spi_phytium_default(struct phytium_spi *fts) { + int ret; + memset(&fts->msg_buf, 0, sizeof(struct msg)); fts->msg_buf.cmd_id = PHYTSPI_MSG_CMD_DEFAULT; @@ -151,7 +153,9 @@ void spi_phytium_default(struct phytium_spi *fts) memcpy_toio(fts->msg, &fts->msg_buf, sizeof(struct msg)); reinit_completion(&fts->cmd_completion); phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); - spi_phytium_check_result(fts); + ret = spi_phytium_check_result(fts); + + return ret; } EXPORT_SYMBOL_GPL(spi_phytium_default); diff --git a/drivers/spi/spi-phytium-v2.c b/drivers/spi/spi-phytium-v2.c index f595456052e65..d0af237f1d2d4 100644 --- a/drivers/spi/spi-phytium-v2.c +++ b/drivers/spi/spi-phytium-v2.c @@ -476,11 +476,16 @@ void spi_handle_debug_err(struct phytium_spi *fts) phytium_write_regfile(fts, SPI_REGFILE_DEBUG, reg); } -static void spi_phyt_hw_init(struct device *dev, struct phytium_spi *fts) +static int spi_phyt_hw_init(struct device *dev, struct phytium_spi *fts) { u32 reg, i, reg_ddr_high; + int ret; - spi_phytium_default(fts); + ret = spi_phytium_default(fts); + if (ret) { + dev_err(dev, "firmware is not responsive: %d\n", ret); + return ret; + } reg = phytium_read_regfile(fts, SPI_REGFILE_DEBUG); @@ -503,12 +508,14 @@ static void spi_phyt_hw_init(struct device *dev, struct phytium_spi *fts) fts->log = devm_ioremap(dev, fts->ddr_paddr, fts->log_size); if (!fts->log) { dev_err(dev, "log_addr is err\n"); - return; + return -ENOMEM; } for (i = 0; i < fts->log_size; i++) fts->log[i] = 0; } + + return 0; } int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts) @@ -567,7 +574,11 @@ int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts) timer_setup(&fts->timer, spi_phyt_timer_handle, 0); - spi_phyt_hw_init(dev, fts); + ret = spi_phyt_hw_init(dev, fts); + if (ret) { + dev_err(dev, "hardware init failed: %d\n", ret); + goto err_exit; + } ret = devm_spi_register_master(dev, master); if (ret) { @@ -614,7 +625,11 @@ int spi_phyt_resume_host(struct phytium_spi *fts) { int ret; - spi_phyt_hw_init(&fts->master->dev, fts); + ret = spi_phyt_hw_init(&fts->master->dev, fts); + if (ret) { + dev_err(&fts->master->dev, "hardware init failed on resume: %d\n", ret); + return ret; + } spi_phyt_enable_chip(fts, 0); spi_phyt_set_clk(fts, fts->clk_div); diff --git a/drivers/spi/spi-phytium.h b/drivers/spi/spi-phytium.h index fc9321a2e1a1e..a3893121b10f1 100644 --- a/drivers/spi/spi-phytium.h +++ b/drivers/spi/spi-phytium.h @@ -339,7 +339,7 @@ static inline void phytium_write_regfile(struct phytium_spi *fts, u32 reg_off, u writel_relaxed(val, fts->regfile + reg_off); } -extern void spi_phytium_default(struct phytium_spi *fts); +extern int spi_phytium_default(struct phytium_spi *fts); extern void spi_phytium_set_cmd8(struct phytium_spi *fts, u16 sub_cmd, u8 data); extern void spi_phytium_set_cmd16(struct phytium_spi *fts, u16 sub_cmd, u16 data); extern void spi_phytium_set_cmd32(struct phytium_spi *fts, u16 sub_cmd, u32 data); From 5c6c37533b3b202f66ce9976b42b4289270c13fa Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 22:35:30 +0800 Subject: [PATCH 081/101] spi: phytium-qspi: Handle unaligned leading offset in dirmap write The direct-mapping write path rejected every non-4-byte-aligned offset with -EINVAL, but the SPI-mem dirmap API accepts byte-granular offsets and SPI NOR writes can begin at an arbitrary address, so otherwise valid MTD writes failed. Handle an unaligned leading fragment by padding it to a 4-byte word with non-programming 0xff bytes before the aligned word loop. Fixes: 9f2e21865eb1 ("arm64: spi: Phytium-qspi: Add support for Phytium QSPI controller") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-qspi.c | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index 51170e0da07f5..ff4f1251cf50f 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -555,21 +555,38 @@ static ssize_t phytium_qspi_dirmap_write(struct spi_mem_dirmap_desc *desc, void __iomem *addr; int i; size_t mask = 0x03; - u_char tmp[4] = {0}; + size_t orig_len = len; - /* set wr_cfg for drimap write */ + /* set wr_cfg for dirmap write */ writel_relaxed(qspi->wr_cfg_reg[spi_get_chipselect(spi, 0)], qspi->io_base + QSPI_WR_CFG_REG); + /* Handle an unaligned leading fragment so byte-granular MTD writes work. + * Align the MMIO address down to a 4-byte boundary and place the + * leading source bytes at the matching lane offset, so the write + * targets the correct byte lanes and all subsequent writes stay + * aligned. + */ if (offs & 0x03) { - dev_err(qspi->dev, "Addr not four-byte aligned!\n"); - return -EINVAL; + size_t lane = offs & 0x03; + size_t head = min(4 - lane, len); + u_char tmp[4] = { 0xff, 0xff, 0xff, 0xff }; + + memcpy(tmp + lane, buf, head); + writel_relaxed(*(u32 *)tmp, dst - lane); + buf += head; + dst += head; + len -= head; } for (i = 0; i < len / 4; i++) writel_relaxed(*(u32 *)(buf + 4 * i), dst + 4 * i); if (len & mask) { - addr = dst + (len & ~mask); + /* unused byte lanes must stay 0xff so adjacent flash + * content is not programmed to zero */ + u_char tmp[4] = { 0xff, 0xff, 0xff, 0xff }; + + addr = dst + (len & ~mask); memcpy(tmp, buf + (len & ~mask), len & mask); writel_relaxed(*(u32 *)(tmp), addr); } @@ -577,7 +594,7 @@ static ssize_t phytium_qspi_dirmap_write(struct spi_mem_dirmap_desc *desc, //write cache data to flash writel_relaxed(QSPI_FLUSH_EN, qspi->io_base + QSPI_FLUSH_REG); - return len; + return orig_len; } static int phytium_qspi_setup(struct spi_device *spi) From 7135c1c6e670705c1603ef26804fa31e270b281c Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 22:36:08 +0800 Subject: [PATCH 082/101] ALSA: hda: phytium: Fix irq_pending_work retry semantics and sleep azx_irq_pending_work() had two issues in the pending-IRQ workaround: - A negative azx_position_ok() result ("too early") reset the aggregate pending counter to zero, discarding retries already requested by earlier streams in the same loop iteration. Later changed to pending++, but that still retried indefinitely. - The retry loop used udelay(1000) in process context, busy-waiting a workqueue CPU. Follow the Intel implementation: treat a negative result as terminal for that stream in the current round (leave the flag set and let the next interrupt reschedule), and sleep between rounds with msleep(1) instead of busy-waiting. Fixes: 8da93e4a61f7 ("hda: phytium: Add Phytium hda driver support") Signed-off-by: Wentao Guan --- sound/hda/controllers/phytium.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sound/hda/controllers/phytium.c b/sound/hda/controllers/phytium.c index 43457b8afd0ed..68a6567cb5fab 100644 --- a/sound/hda/controllers/phytium.c +++ b/sound/hda/controllers/phytium.c @@ -285,7 +285,14 @@ static void azx_irq_pending_work(struct work_struct *work) snd_pcm_period_elapsed(s->substream); spin_lock(&bus->reg_lock); } else if (ok < 0) { - pending = 0; /* too early */ + /* + * Too early: give up on this stream for this + * round, as the Intel implementation does. A + * persistently early stream must not keep the + * worker busy-spinning; the flag stays set and + * the next interrupt reschedules the work. + */ + continue; } else { pending++; } @@ -293,7 +300,7 @@ static void azx_irq_pending_work(struct work_struct *work) spin_unlock_irq(&bus->reg_lock); if (!pending) return; - udelay(1000); + msleep(1); } } From 5ac80c94ef50ec6118f02b14281cd054faa04b97 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 22:36:43 +0800 Subject: [PATCH 083/101] ALSA: hda: phytium: Preserve platform_get_irq errors before request_irq MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hda_ft_probe() passed the return value of platform_get_irq() directly to request_irq() without checking for errors. A negative return — notably -EPROBE_DEFER when the interrupt provider has not yet probed — was interpreted as an IRQ number, the request failed, and the device was never reprobed. Check for a negative IRQ and propagate it, letting -EPROBE_DEFER reach the driver core for a deferred retry. Fixes: 8da93e4a61f7 ("hda: phytium: Add Phytium hda driver support") Signed-off-by: Wentao Guan --- sound/hda/controllers/phytium.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/hda/controllers/phytium.c b/sound/hda/controllers/phytium.c index 68a6567cb5fab..5db5a881f85c0 100644 --- a/sound/hda/controllers/phytium.c +++ b/sound/hda/controllers/phytium.c @@ -328,6 +328,9 @@ static int azx_acquire_irq(struct azx *chip, int do_disconnect) int irq_id = platform_get_irq(pdev, 0); int err; + if (irq_id < 0) + return irq_id; + err = request_irq(irq_id, azx_interrupt, IRQF_SHARED, KBUILD_MODNAME, chip); if (err) { From 108472d19e350a550df82773ee296ec0f3892a81 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 22:41:12 +0800 Subject: [PATCH 084/101] dt-bindings: phytium: Document spi-2.0 and hda compatibles The spi-phytium-plat-v2.c driver declares the "phytium,spi-2.0" compatible string but the Phytium SPI binding only accepted "phytium,spi", so firmware using the v2.0 interface could not be schema-validated. Add it as an accepted value. The Phytium HDA controller (phytium,hda) has no binding at all, so firmware cannot be validated against its register and interrupt requirements. Add a new binding under Documentation/devicetree/bindings/sound/phytium,hda.yaml. Fixes: caf2f8ff84f6 ("arm64: spi: add Phytium SPI controller support") Fixes: 8da93e4a61f7 ("hda: phytium: Add Phytium hda driver support") Signed-off-by: Wentao Guan --- .../bindings/sound/phytium,hda.yaml | 45 +++++++++++++++++++ .../devicetree/bindings/spi/phytium,spi.yaml | 5 ++- 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 Documentation/devicetree/bindings/sound/phytium,hda.yaml diff --git a/Documentation/devicetree/bindings/sound/phytium,hda.yaml b/Documentation/devicetree/bindings/sound/phytium,hda.yaml new file mode 100644 index 0000000000000..99df5d4bcb354 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/phytium,hda.yaml @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/phytium,hda.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium HD Audio controller + +maintainers: + - Chen Baozi + +properties: + compatible: + const: phytium,hda + + reg: + minItems: 1 + description: address and length of the HDA controller registers + + interrupts: + maxItems: 1 + description: HDA controller interrupt + + clocks: + maxItems: 1 + description: clock phandle for the HDA controller + +required: + - compatible + - reg + - interrupts + - clocks + +unevaluatedProperties: false + +examples: + - | + #include + + hda: hda@2800d000 { + compatible = "phytium,hda"; + reg = <0x0 0x2800d000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_48mhz>; + }; diff --git a/Documentation/devicetree/bindings/spi/phytium,spi.yaml b/Documentation/devicetree/bindings/spi/phytium,spi.yaml index fb6cf65cdc7d7..8652259e46cd7 100644 --- a/Documentation/devicetree/bindings/spi/phytium,spi.yaml +++ b/Documentation/devicetree/bindings/spi/phytium,spi.yaml @@ -14,7 +14,10 @@ allOf: properties: compatible: - const: phytium,spi + oneOf: + - const: phytium,spi + - const: phytium,spi-2.0 + description: Phytium SPI controller v2.0 interface reg: minItems: 1 From bfb04bae6bacd058876fffd774c008ba9f010864 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 8 Sep 2026 22:41:37 +0800 Subject: [PATCH 085/101] mtd: partitions: Fix misspelled fwnode_handle forward declaration The forward declaration for the fwnode_handle type added to struct mtd_partition was misspelled as "struct hwnode_handle;", creating an unrelated unused struct tag instead of declaring the type that mtd_partition.fwnode actually references. Fixes: 7f4fb2e5cdaf ("arm64: phytium: UEFI mode acpi table support for qspi/spi driver") Signed-off-by: Wentao Guan --- include/linux/mtd/partitions.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/mtd/partitions.h b/include/linux/mtd/partitions.h index 73f16747d9bcc..d504ac36c46f5 100644 --- a/include/linux/mtd/partitions.h +++ b/include/linux/mtd/partitions.h @@ -63,7 +63,7 @@ struct mtd_partition { struct mtd_info; struct device_node; struct acpi_device; -struct hwnode_handle; +struct fwnode_handle; /** * struct mtd_part_parser_data - used to pass data to MTD partition parsers. From a7bbdc8056d8dc75f36f8f3baa86eac403dcc1cb Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Wed, 9 Sep 2026 00:12:52 +0800 Subject: [PATCH 086/101] spi: phytium-v2: Do not reinterpret client drvdata as spi_mem spi_phyt_transfer_one() read spi_get_drvdata(spi) and treated the result as a struct spi_mem *, but that field belongs to the client driver: spi-mem drivers store a struct spi_mem there, while ordinary SPI clients such as the MCP251x CAN controller also supported by this driver store their own private structure. For a non-spi-nor client the pointer was then dereferenced as spi_mem/spi_nor, crashing during the first transfer. Only interpret the drvdata as a struct spi_mem when the bound client driver is actually "spi-nor". Fixes: b127bad4e875 ("spi-v2: phytium: Adapt the mcp251x device") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-v2.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-phytium-v2.c b/drivers/spi/spi-phytium-v2.c index d0af237f1d2d4..3222e5e6d4d9d 100644 --- a/drivers/spi/spi-phytium-v2.c +++ b/drivers/spi/spi-phytium-v2.c @@ -156,12 +156,23 @@ static int spi_phyt_transfer_one(struct spi_master *master, { struct phytium_spi *fts = spi_master_get_devdata(master); struct chip_data *chip = spi_get_ctldata(spi); - struct spi_mem *mem = spi_get_drvdata(spi); + struct spi_mem *mem = NULL; struct spi_nor *nor = NULL; int ret; - if (mem) - nor = spi_mem_get_drvdata(mem); + /* + * spi_get_drvdata() returns whatever the client driver stored: + * spi_mem for spi-mem drivers, but e.g. mcp251x_priv for the + * MCP251x CAN controller also supported here. Only interpret it + * as a struct spi_mem when the client is actually the spi-nor + * driver, otherwise the dereference below crashes on ordinary + * SPI clients. + */ + if (spi->dev.driver && !strcmp(spi->dev.driver->name, "spi-nor")) { + mem = spi_get_drvdata(spi); + if (mem) + nor = spi_mem_get_drvdata(mem); + } if (!transfer->tx_buf && !transfer->rx_buf) { /* clock-only transfers without buffers are not supported */ From 2bdaabe5ddfaa47d39f0f35457839cf4cc3cb22d Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Wed, 9 Sep 2026 00:31:37 +0800 Subject: [PATCH 087/101] spi: phytium-v2: Adapt to the 6.18 SPI, GPIO and timer APIs The V2 files were never actually compiled: SPI_PHYTIUM_V2 and SPI_PHYTIUM_PLAT_V2 depend on ARCH_PHYTIUM, which is unset on every build of this tree, so the Kconfig symbols were silently dropped and the stale pre-6.x API usage went unnoticed. Adapt the code so it builds against 6.18: - s/spi_master/spi_controller/ for the alloc, devdata, register and put helpers (spi_alloc_host, spi_controller_get/set_devdata, devm_spi_register_controller, spi_controller_put) and spi->master -> spi->controller - from_timer() no longer exists; use timer_container_of(), and del_timer_sync() is now timer_delete_sync() - the legacy of_get_named_gpio()/devm_gpio_request() OF path is gone; request the chip-select lines through the gpiod API for both DT and ACPI firmware, matching what the SPI core expects, and drop the now unused fts->cs array - add missing prototypes by making the file-local helpers static and remove the unused ones (spi_phytium_data_subid, spi_phyt_enable_debug) - drop the dead asm/memory.h and linux/of_gpio.h includes - allow COMPILE_TEST for SPI_PHYTIUM_PLAT_V2 so the files keep compiling on other architectures Fixes: b4cb0174adb1 ("spi-v2: phytium: Add the debug log function to the driver") Signed-off-by: Wentao Guan --- drivers/spi/Kconfig | 3 +- drivers/spi/spi-phytium-common.c | 21 ++++-------- drivers/spi/spi-phytium-plat-v2.c | 56 +++--------------------------- drivers/spi/spi-phytium-v2.c | 57 +++++++++++++------------------ 4 files changed, 35 insertions(+), 102 deletions(-) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index d59c02379ee17..ba09ca40674be 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -893,14 +893,13 @@ config SPI_PHYTIUM_QSPI config SPI_PHYTIUM_V2 tristate "spi phytium v2" - depends on ARCH_PHYTIUM help This config is similar to the "SPI_PHYTIUM" config. config SPI_PHYTIUM_PLAT_V2 tristate "Phytium SPI-v2 controller platform support" select SPI_PHYTIUM_V2 - depends on ARCH_PHYTIUM + depends on ARCH_PHYTIUM || COMPILE_TEST help This config is similar to the "SPI_PHYTIUM_PLAT" config. diff --git a/drivers/spi/spi-phytium-common.c b/drivers/spi/spi-phytium-common.c index 3c941a2b3fe2e..e6be0622bab26 100644 --- a/drivers/spi/spi-phytium-common.c +++ b/drivers/spi/spi-phytium-common.c @@ -19,19 +19,17 @@ #include #include #include -#include #include #include #include #include #include #include -#include #include "spi-phytium.h" #define SPI_SHOW_MSG_DEBUG 0 -void spi_phytium_show_msg(struct msg *info) +static void spi_phytium_show_msg(struct msg *info) { if (SPI_SHOW_MSG_DEBUG) { pr_err("module:0x%4x, cmd:0x%04x, sub:0x%04x\n", @@ -51,7 +49,7 @@ void spi_phytium_show_msg(struct msg *info) } } -void *memcpy_byte(void *_dest, const void *_src, size_t sz) +static void *memcpy_byte(void *_dest, const void *_src, size_t sz) { while (sz >= 8) { *(u64 *)_dest = *(u64 *)_src; @@ -70,7 +68,7 @@ void *memcpy_byte(void *_dest, const void *_src, size_t sz) return _dest; } -int spi_phytium_print_status(struct phytium_spi *fts, u8 status0, +static int spi_phytium_print_status(struct phytium_spi *fts, u8 status0, u8 status1) { if (status1 == 0) @@ -109,7 +107,7 @@ int spi_phytium_print_status(struct phytium_spi *fts, u8 status0, return -1; } -int spi_phytium_check_result(struct phytium_spi *fts) +static int spi_phytium_check_result(struct phytium_spi *fts) { unsigned long ms; struct msg *msg = &fts->msg_buf; @@ -128,7 +126,7 @@ int spi_phytium_check_result(struct phytium_spi *fts) return spi_phytium_print_status(fts, msg->status0, msg->status1); } -int spi_phytium_set(struct phytium_spi *fts) +static int spi_phytium_set(struct phytium_spi *fts) { int ret; @@ -159,7 +157,7 @@ int spi_phytium_default(struct phytium_spi *fts) } EXPORT_SYMBOL_GPL(spi_phytium_default); -void spi_phytium_set_subid(struct phytium_spi *fts, u16 sub_cmd) +static void spi_phytium_set_subid(struct phytium_spi *fts, u16 sub_cmd) { fts->msg_buf.cmd_id = PHYTSPI_MSG_CMD_SET; fts->msg_buf.cmd_subid = sub_cmd; @@ -211,12 +209,6 @@ void spi_phytium_set_cmd32(struct phytium_spi *fts, u16 sub_cmd, } EXPORT_SYMBOL_GPL(spi_phytium_set_cmd32); -void spi_phytium_data_subid(struct phytium_spi *fts, u16 sub_cmd) -{ - fts->msg_buf.cmd_id = PHYTSPI_MSG_CMD_DATA; - fts->msg_buf.cmd_subid = sub_cmd; -} - void spi_phytium_write_pre(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, u8 tmode, u8 flags, u8 spi_write_flag) { @@ -489,7 +481,6 @@ int spi_phytium_xfer(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, void __iomem *smem_tx, *smem_rx; u8 first = 1; u64 tx_addr, rx_addr; - u64 *data = (u64 *)fts->tx; do { if (fts->dma_get_ddrdata) diff --git a/drivers/spi/spi-phytium-plat-v2.c b/drivers/spi/spi-phytium-plat-v2.c index 707543a7b5171..83cbd0cb05d3b 100644 --- a/drivers/spi/spi-phytium-plat-v2.c +++ b/drivers/spi/spi-phytium-plat-v2.c @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -138,9 +137,7 @@ static int spi_phyt_probe(struct platform_device *pdev) struct resource *regfile_mem, *share_mem; int ret; int num_cs; - int cs_gpio; int global_cs = 0; - int i; u32 clk_rate = SPI_DEFAULT_CLK; fts = devm_kzalloc(&pdev->dev, sizeof(struct phytium_spi), @@ -210,56 +207,11 @@ static int spi_phyt_probe(struct platform_device *pdev) fts->num_cs = num_cs; - if (pdev->dev.of_node) { - int i; - - for (i = 0; i < fts->num_cs; i++) { - cs_gpio = of_get_named_gpio(pdev->dev.of_node, - "cs-gpios", i); - - if (cs_gpio == -EPROBE_DEFER) { - ret = cs_gpio; - goto out; - } - - if (gpio_is_valid(cs_gpio)) { - ret = devm_gpio_request(&pdev->dev, cs_gpio, - dev_name(&pdev->dev)); - if (ret) - goto out; - } - } - } else if (has_acpi_companion(&pdev->dev)) { - int n; - int *cs; - struct gpio_desc *gpiod; - - n = gpiod_count(&pdev->dev, "cs"); - if (n <= 0) - goto skip_cs_gpio; - - cs = devm_kcalloc(&pdev->dev, n, sizeof(int), GFP_KERNEL); - if (!cs) { - ret = -ENOMEM; - goto out; - } - fts->cs = cs; - - for (i = 0; i < n; i++) { - gpiod = devm_gpiod_get_index_optional(&pdev->dev, "cs", i, - GPIOD_OUT_LOW); - - if (IS_ERR(gpiod)) { - ret = PTR_ERR(gpiod); - goto out; - } - - cs_gpio = desc_to_gpio(gpiod); - cs[i] = cs_gpio; - } - } + /* The SPI core acquires and manages the cs-gpios when + * use_gpio_descriptors is set in spi_phyt_add_host; nothing + * to do here. + */ -skip_cs_gpio: device_property_read_u32(&pdev->dev, "global-cs", &global_cs); fts->global_cs = global_cs; diff --git a/drivers/spi/spi-phytium-v2.c b/drivers/spi/spi-phytium-v2.c index 3222e5e6d4d9d..0a45f56e026b5 100644 --- a/drivers/spi/spi-phytium-v2.c +++ b/drivers/spi/spi-phytium-v2.c @@ -14,11 +14,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include @@ -94,7 +94,7 @@ struct chip_data { static void spi_phyt_set_cs(struct spi_device *spi, bool enable) { - struct phytium_spi *fts = spi_master_get_devdata(spi->master); + struct phytium_spi *fts = spi_controller_get_devdata(spi->controller); struct chip_data *chip = spi_get_ctldata(spi); u32 origin; u16 cs; @@ -130,8 +130,8 @@ static void spi_phyt_set_cs(struct spi_device *spi, bool enable) static irqreturn_t spi_phyt_irq(int irq, void *dev_id) { - struct spi_master *master = dev_id; - struct phytium_spi *fts = spi_master_get_devdata(master); + struct spi_controller *master = dev_id; + struct phytium_spi *fts = spi_controller_get_devdata(master); u32 state; /* Check whether this controller actually raised the interrupt; @@ -151,10 +151,10 @@ static irqreturn_t spi_phyt_irq(int irq, void *dev_id) return IRQ_HANDLED; } -static int spi_phyt_transfer_one(struct spi_master *master, +static int spi_phyt_transfer_one(struct spi_controller *master, struct spi_device *spi, struct spi_transfer *transfer) { - struct phytium_spi *fts = spi_master_get_devdata(master); + struct phytium_spi *fts = spi_controller_get_devdata(master); struct chip_data *chip = spi_get_ctldata(spi); struct spi_mem *mem = NULL; struct spi_nor *nor = NULL; @@ -335,10 +335,10 @@ static int spi_phyt_transfer_one(struct spi_master *master, return ret; } -static void spi_phyt_handle_err(struct spi_master *master, +static void spi_phyt_handle_err(struct spi_controller *master, struct spi_message *msg) { - struct phytium_spi *fts = spi_master_get_devdata(master); + struct phytium_spi *fts = spi_controller_get_devdata(master); spi_phyt_reset_chip(fts); } @@ -347,8 +347,8 @@ static int spi_phyt_setup(struct spi_device *spi) { struct phytium_spi_chip *chip_info = NULL; struct chip_data *chip; - struct spi_master *master = spi->master; - struct phytium_spi *fts = spi_master_get_devdata(master); + struct spi_controller *master = spi->controller; + struct phytium_spi *fts = spi_controller_get_devdata(master); u8 data_width, scph, scpol, tmode; u16 mode; u16 clk_div; @@ -408,17 +408,7 @@ static void spi_phyt_cleanup(struct spi_device *spi) spi_set_ctldata(spi, NULL); } -void spi_phyt_enable_debug(struct phytium_spi *fts) -{ - u32 reg; - - reg = phytium_read_regfile(fts, SPI_REGFILE_DEBUG); - - phytium_write_regfile(fts, SPI_REGFILE_DEBUG, - reg | SPI_REGFILE_DEBUG_VAL); -} - -void spi_phyt_disable_debug(struct phytium_spi *fts) +static void spi_phyt_disable_debug(struct phytium_spi *fts) { u32 reg; @@ -428,7 +418,7 @@ void spi_phyt_disable_debug(struct phytium_spi *fts) phytium_write_regfile(fts, SPI_REGFILE_DEBUG, reg); } -void spi_phyt_disable_alive(struct phytium_spi *fts) +static void spi_phyt_disable_alive(struct phytium_spi *fts) { u32 reg; @@ -438,7 +428,7 @@ void spi_phyt_disable_alive(struct phytium_spi *fts) phytium_write_regfile(fts, SPI_REGFILE_DEBUG, reg); } -void spi_watchdog(struct phytium_spi *fts) +static void spi_watchdog(struct phytium_spi *fts) { u32 reg; @@ -449,7 +439,7 @@ void spi_watchdog(struct phytium_spi *fts) static void spi_phyt_timer_handle(struct timer_list *t) { - struct phytium_spi *fts = from_timer(fts, t, timer); + struct phytium_spi *fts = timer_container_of(fts, t, timer); if (fts->alive_enabled && fts->watchdog) { if (fts->runtimes < 20) @@ -461,7 +451,7 @@ static void spi_phyt_timer_handle(struct timer_list *t) mod_timer(&fts->timer, jiffies + msecs_to_jiffies(10)); } -void spi_handle_debug_err(struct phytium_spi *fts) +static void spi_handle_debug_err(struct phytium_spi *fts) { struct device *dev = &fts->master->dev; u32 reg, len, i; @@ -531,12 +521,12 @@ static int spi_phyt_hw_init(struct device *dev, struct phytium_spi *fts) int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts) { - struct spi_master *master; + struct spi_controller *master; int ret; WARN_ON(fts == NULL); - master = spi_alloc_master(dev, 0); + master = spi_alloc_host(dev, 0); if (!master) return -ENOMEM; @@ -554,6 +544,7 @@ int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts) master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16); master->bus_num = fts->bus_num; master->num_chipselect = fts->num_cs; + master->use_gpio_descriptors = true; master->setup = spi_phyt_setup; master->cleanup = spi_phyt_cleanup; master->set_cs = spi_phyt_set_cs; @@ -572,7 +563,7 @@ int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts) master->flags |= SPI_CONTROLLER_HALF_DUPLEX; } - spi_master_set_devdata(master, fts); + spi_controller_set_devdata(master, fts); spi_phyt_disable_debug(fts); spi_phyt_disable_alive(fts); @@ -591,7 +582,7 @@ int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts) goto err_exit; } - ret = devm_spi_register_master(dev, master); + ret = devm_spi_register_controller(dev, master); if (ret) { dev_err(&master->dev, "problem registering spi master\n"); goto err_exit; @@ -602,17 +593,17 @@ int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts) return 0; err_exit: - del_timer_sync(&fts->timer); + timer_delete_sync(&fts->timer); spi_phyt_enable_chip(fts, 0); err_free_master: - spi_master_put(master); + spi_controller_put(master); return ret; } EXPORT_SYMBOL_GPL(spi_phyt_add_host); void spi_phyt_remove_host(struct phytium_spi *fts) { - del_timer_sync(&fts->timer); + timer_delete_sync(&fts->timer); spi_phyt_shutdown_chip(fts); } EXPORT_SYMBOL_GPL(spi_phyt_remove_host); @@ -626,7 +617,7 @@ int spi_phyt_suspend_host(struct phytium_spi *fts) return ret; /* stop the watchdog timer before shutting down the chip */ - del_timer_sync(&fts->timer); + timer_delete_sync(&fts->timer); spi_phyt_shutdown_chip(fts); return 0; } From 002062babe383f190e8b8b876f96bdfee57e466e Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Wed, 9 Sep 2026 00:32:15 +0800 Subject: [PATCH 088/101] spi: phytium-v2: Return after chip erase instead of falling through MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After issuing SPINOR_OP_CHIP_ERASE to the firmware, execution fell through to the generic TX path, which sent the same opcode a second time — a duplicate flash operation. The return value of spi_phytium_flash_erase() was also ignored and the flash_erase state was committed even on failure. Return right after the erase command and only update the state flags on success, propagating the error otherwise. Fixes: b127bad4e875 ("spi-v2: phytium: Adapt the mcp251x device") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-v2.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/spi/spi-phytium-v2.c b/drivers/spi/spi-phytium-v2.c index 0a45f56e026b5..fcd18e8e954cf 100644 --- a/drivers/spi/spi-phytium-v2.c +++ b/drivers/spi/spi-phytium-v2.c @@ -226,8 +226,16 @@ static int spi_phyt_transfer_one(struct spi_controller *master, ret = spi_phytium_flash_erase(fts, spi_get_chipselect(spi, 0), transfer->bits_per_word, spi->mode, chip->tmode, 3, SPINOR_OP_CHIP_ERASE); + if (ret) { + dev_err(&spi->dev, "chip erase failed: %d\n", ret); + return ret; + } fts->spi_write_flag = 0; fts->flash_erase = 2; + /* the erase was issued to the firmware; do not fall + * through to the generic TX path below, which would + * send the opcode a second time */ + return ret; } if ((*(u8 *)fts->tx == SPINOR_OP_READ || *(u8 *)fts->tx == SPINOR_OP_READ_FAST || From f4f9eb54b20dafa5a14a6df0e9b0543bacae278f Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Wed, 9 Sep 2026 00:34:08 +0800 Subject: [PATCH 089/101] spi: phytium-v2: Rearm the alive timer only while monitoring is enabled The alive watchdog timer handler rearmed itself unconditionally every 10 ms even though alive_enabled defaults to false, causing 100 unnecessary wakeups per second per controller indefinitely. The add_host and resume paths also armed the timer unconditionally. Rearm only while alive monitoring is enabled, and start the timer from the sysfs enable path when alive monitoring transitions to enabled. Fixes: b4cb0174adb1 ("spi-v2: phytium: Add the debug log function to the driver") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-plat-v2.c | 4 ++++ drivers/spi/spi-phytium-v2.c | 13 +++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-phytium-plat-v2.c b/drivers/spi/spi-phytium-plat-v2.c index 83cbd0cb05d3b..f25a171a61481 100644 --- a/drivers/spi/spi-phytium-plat-v2.c +++ b/drivers/spi/spi-phytium-plat-v2.c @@ -96,6 +96,10 @@ static ssize_t debug_store(struct device *dev, if (dis_en == 1) { fts->alive_enabled = true; reg |= BIT(loc); + /* start the watchdog timer when alive monitoring + * transitions to enabled */ + mod_timer(&fts->timer, + jiffies + msecs_to_jiffies(10)); } else if (dis_en == 0) { fts->alive_enabled = false; reg &= ~BIT(loc); diff --git a/drivers/spi/spi-phytium-v2.c b/drivers/spi/spi-phytium-v2.c index fcd18e8e954cf..b77a33d872c28 100644 --- a/drivers/spi/spi-phytium-v2.c +++ b/drivers/spi/spi-phytium-v2.c @@ -454,9 +454,10 @@ static void spi_phyt_timer_handle(struct timer_list *t) fts->watchdog(fts); fts->runtimes++; - } - mod_timer(&fts->timer, jiffies + msecs_to_jiffies(10)); + /* rearm only while alive monitoring is enabled */ + mod_timer(&fts->timer, jiffies + msecs_to_jiffies(10)); + } } static void spi_handle_debug_err(struct phytium_spi *fts) @@ -596,8 +597,6 @@ int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts) goto err_exit; } - mod_timer(&fts->timer, jiffies + msecs_to_jiffies(50)); - return 0; err_exit: @@ -651,8 +650,10 @@ int spi_phyt_resume_host(struct phytium_spi *fts) return ret; } - /* restart the watchdog timer after a successful resume */ - mod_timer(&fts->timer, jiffies + msecs_to_jiffies(50)); + /* restart the watchdog timer after a successful resume, + * but only while alive monitoring is enabled */ + if (fts->alive_enabled) + mod_timer(&fts->timer, jiffies + msecs_to_jiffies(10)); return 0; } EXPORT_SYMBOL_GPL(spi_phyt_resume_host); From 9c9f81a446d33f768fade3b7c2da6de4848a2f73 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Wed, 9 Sep 2026 00:36:12 +0800 Subject: [PATCH 090/101] ALSA: hda: Do not copy the whole framebuffer from the pointer callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When hw_pos == appl_pos, gf_update_stream() copied the entire PCM buffer — up to 7 MiB — to the framebuffer MMIO mapping. The function runs from the PCM pointer callback, which can be invoked from the period/IRQ path, so a caught-up or underrun stream caused extreme interrupt latency and repeatedly copied an empty buffer. Drop the full-copy branch: the pointer callback now only moves the newly committed range (appl_pos advance), and the full refresh remains in gf_pre_trigger() when the stream starts. Fixes: 86858d4871e3 ("add gf hdaudio 001 patch in deepin kernel 6.6") Signed-off-by: Wentao Guan --- sound/hda/common/controller.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/sound/hda/common/controller.c b/sound/hda/common/controller.c index 66bb751e6d2e2..0d923b2353c4d 100644 --- a/sound/hda/common/controller.c +++ b/sound/hda/common/controller.c @@ -169,7 +169,7 @@ static int gf_update_stream(struct snd_pcm_substream *substream) { struct azx_pcm *apcm = snd_pcm_substream_chip(substream); struct azx *chip = apcm->chip; - unsigned int stream_idx, hw_pos, appl_pos; + unsigned int stream_idx, appl_pos; struct gf_private *gf_chip = NULL; if ((substream->runtime) && (chip->pci != NULL) && (chip->pci->vendor == 0x6766) && (chip->pci->device == 0x3d40)) { @@ -180,13 +180,16 @@ static int gf_update_stream(struct snd_pcm_substream *substream) stream_idx = apcm->codec->addr - 1; if ((stream_idx <= 1) && (gf_chip->diu_fb_stream_vaddr[stream_idx]) && (substream->runtime->dma_area) && (substream->runtime->dma_bytes <= GF_HDA_FB_STREAM_SIZE) && snd_pcm_running(substream)) { - hw_pos = frames_to_bytes(substream->runtime, substream->runtime->status->hw_ptr % substream->runtime->buffer_size); appl_pos = frames_to_bytes(substream->runtime, substream->runtime->control->appl_ptr % substream->runtime->buffer_size); - if (hw_pos == appl_pos) { - memcpy_toio(gf_chip->diu_fb_stream_vaddr[stream_idx], substream->runtime->dma_area, substream->runtime->dma_bytes); - } - else if (appl_pos > gf_chip->diu_fb_stream_pos[stream_idx]) { + /* + * Sync only the newly committed range. This runs + * from the PCM pointer callback, which can be + * called in interrupt context, so never copy the + * whole buffer here; a full refresh is done by + * gf_pre_trigger() when the stream starts. + */ + if (appl_pos > gf_chip->diu_fb_stream_pos[stream_idx]) { memcpy_toio(gf_chip->diu_fb_stream_vaddr[stream_idx] + gf_chip->diu_fb_stream_pos[stream_idx], substream->runtime->dma_area + gf_chip->diu_fb_stream_pos[stream_idx], (appl_pos - gf_chip->diu_fb_stream_pos[stream_idx])); } else if (appl_pos < gf_chip->diu_fb_stream_pos[stream_idx]) { From 5546afb0d8204294d0754caa06bb2c2a51de9fd7 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Wed, 9 Sep 2026 02:25:03 +0800 Subject: [PATCH 091/101] spi: phytium-dma: Propagate controller error after DMA completion The DMA completion wait returned success even when the controller error IRQ had already set cur_msg->status to -EIO via phytium_spi_check_status(). The chunked transfer path then submitted another DMA chunk after the controller was reset, corrupting state. Check cur_msg->status after the completion fires and propagate the error so the transfer stops and handle_err terminates outstanding DMA. Fixes: caf2f8ff84f6 ("arm64: spi: add Phytium SPI controller support") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-dma.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/spi/spi-phytium-dma.c b/drivers/spi/spi-phytium-dma.c index c40a830352afe..1565f106ee8d6 100644 --- a/drivers/spi/spi-phytium-dma.c +++ b/drivers/spi/spi-phytium-dma.c @@ -160,6 +160,15 @@ static int phytium_spi_dma_wait(struct phytium_spi *fts, unsigned int len, return -ETIMEDOUT; } + /* The same completion is signaled by both normal DMA callbacks + * and the controller error IRQ. On an error IRQ, + * phytium_spi_check_status() sets cur_msg->status to -EIO; + * propagate it so the chunked path stops submitting and + * handle_err terminates outstanding DMA. + */ + if (fts->master->cur_msg->status != -EINPROGRESS) + return fts->master->cur_msg->status; + return 0; } From 0c81b3651ca9ff9979f61d12586a459beb04b48b Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Wed, 9 Sep 2026 02:25:37 +0800 Subject: [PATCH 092/101] dt-bindings: spi: phytium: Require two reg ranges for spi-2.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v2 probe unconditionally requires resource 0 (register file) and resource 1 (shared memory), but the schema allowed a single reg range for phytium,spi-2.0 — a v2 DT with one range validated and then always failed probing. Add a per-compatible conditional: require exactly two reg ranges for phytium,spi-2.0, keeping one for the v1 compatible. Fixes: caf2f8ff84f6 ("arm64: spi: add Phytium SPI controller support") Signed-off-by: Wentao Guan --- .../devicetree/bindings/spi/phytium,spi.yaml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Documentation/devicetree/bindings/spi/phytium,spi.yaml b/Documentation/devicetree/bindings/spi/phytium,spi.yaml index 8652259e46cd7..703562bcbbfab 100644 --- a/Documentation/devicetree/bindings/spi/phytium,spi.yaml +++ b/Documentation/devicetree/bindings/spi/phytium,spi.yaml @@ -11,6 +11,26 @@ maintainers: allOf: - $ref: spi-controller.yaml# + - if: + properties: + compatible: + contains: + const: phytium,spi-2.0 + then: + properties: + reg: + minItems: 2 + maxItems: 2 + description: + reg[0]: register file window; + reg[1]: AP-RV shared memory window + required: + - reg + else: + properties: + reg: + minItems: 1 + maxItems: 1 properties: compatible: From af9f763221e24fd2cc3c6e5d96c8fa65bd256924 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Wed, 9 Sep 2026 02:26:46 +0800 Subject: [PATCH 093/101] spi: phytium-v2: Fix the controller lifetime and unregister ordering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit spi_phyt_add_host() allocated the controller with spi_alloc_host() (non-devm) but registered it with devm_spi_register_controller(). The devres cleanup only unregisters the controller; it does not drop the initial reference taken by the allocation, so every successful unbind leaked the controller. The managed registration also ordered teardown wrongly: the devres unregister action only runs after the platform .remove callback has returned, but spi_phyt_remove_host() shuts the chip down inside .remove — leaving child devices and the transfer queue registered against hardware that is already disabled. Switch to devm_spi_alloc_host() so the initial reference is managed, register explicitly with spi_register_controller(), and make spi_phyt_remove_host() unregister the controller before stopping the hardware, matching the V1 driver. Fixes: b4cb0174adb1 ("spi-v2: phytium: Add the debug log function to the driver") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-v2.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-phytium-v2.c b/drivers/spi/spi-phytium-v2.c index b77a33d872c28..0b64ad03064a4 100644 --- a/drivers/spi/spi-phytium-v2.c +++ b/drivers/spi/spi-phytium-v2.c @@ -535,7 +535,7 @@ int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts) WARN_ON(fts == NULL); - master = spi_alloc_host(dev, 0); + master = devm_spi_alloc_host(dev, 0); if (!master) return -ENOMEM; @@ -546,7 +546,7 @@ int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts) ret = devm_request_irq(dev, fts->irq, spi_phyt_irq, IRQF_SHARED, fts->name, master); if (ret < 0) { dev_err(dev, "can not get IRQ\n"); - goto err_free_master; + return ret; } master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP; @@ -591,7 +591,15 @@ int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts) goto err_exit; } - ret = devm_spi_register_controller(dev, master); + /* + * Register explicitly rather than devm: the devres unregister + * action would only run after the platform .remove callback has + * already shut the chip down, leaving child devices and the + * transfer queue registered against disabled hardware. With an + * explicit registration, spi_phyt_remove_host() unregisters the + * controller first and only then stops the hardware. + */ + ret = spi_register_controller(master); if (ret) { dev_err(&master->dev, "problem registering spi master\n"); goto err_exit; @@ -602,14 +610,16 @@ int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts) err_exit: timer_delete_sync(&fts->timer); spi_phyt_enable_chip(fts, 0); -err_free_master: - spi_controller_put(master); return ret; } EXPORT_SYMBOL_GPL(spi_phyt_add_host); void spi_phyt_remove_host(struct phytium_spi *fts) { + /* unregister children and the transfer queue before the + * hardware is turned off */ + spi_unregister_controller(fts->master); + timer_delete_sync(&fts->timer); spi_phyt_shutdown_chip(fts); } From 7da97740ecbdc5aa860d7dee42bbc6b1fd1935da Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Wed, 9 Sep 2026 02:27:16 +0800 Subject: [PATCH 094/101] spi: phytium: Put the controller in remove_host to fix a leak phytium_spi_add_host() allocates the controller with spi_alloc_host(), which takes an initial reference, but phytium_spi_remove_host() only calls spi_unregister_controller(). That function does device_del() without put_device(), so the initial reference is never released and the controller allocation leaks on every unbind. Add spi_controller_put() after spi_unregister_controller() to drop the reference. Fixes: caf2f8ff84f6 ("arm64: spi: add Phytium SPI controller support") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/spi/spi-phytium.c b/drivers/spi/spi-phytium.c index b090454057b9c..d0e25ac23d9c5 100644 --- a/drivers/spi/spi-phytium.c +++ b/drivers/spi/spi-phytium.c @@ -490,6 +490,12 @@ void phytium_spi_remove_host(struct phytium_spi *fts) spi_shutdown_chip(fts); free_irq(fts->irq, fts->master); + + /* spi_unregister_controller() does not drop the initial reference + * taken by spi_alloc_host(); release it here to avoid leaking the + * controller allocation on every unbind. + */ + spi_controller_put(fts->master); } EXPORT_SYMBOL_GPL(phytium_spi_remove_host); From c26ffac4ea2a4dc29d0870452fb43c995eeb4e28 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Wed, 9 Sep 2026 02:29:02 +0800 Subject: [PATCH 095/101] spi: phytium: Return int from set_cmd8/16/32 and propagate failures spi_phytium_set_cmd8/16/32() discarded the return value of spi_phytium_check_result(), so their setup callers returned success even when a firmware command timed out or reported an error, leaving the controller partially configured. Change these helpers to return int and propagate the check_result status everywhere: - spi_phyt_setup() aborts on the first failed command (chip disable, clock setting, DATA_WIDTH/MODE/TMOD, final chip enable); - the spi_phyt_enable_chip()/set_clk()/dma_reset()/global_cs() wrappers return int instead of discarding the result; - spi_phyt_resume_host() propagates failures from the chip disable, clock setting and re-enable sequence. Fixes: b4cb0174adb1 ("spi-v2: phytium: Add the debug log function to the driver") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-common.c | 12 ++--- drivers/spi/spi-phytium-v2.c | 77 +++++++++++++++++++++++--------- drivers/spi/spi-phytium.h | 6 +-- 3 files changed, 65 insertions(+), 30 deletions(-) diff --git a/drivers/spi/spi-phytium-common.c b/drivers/spi/spi-phytium-common.c index e6be0622bab26..ba6dd11cbbed2 100644 --- a/drivers/spi/spi-phytium-common.c +++ b/drivers/spi/spi-phytium-common.c @@ -163,7 +163,7 @@ static void spi_phytium_set_subid(struct phytium_spi *fts, u16 sub_cmd) fts->msg_buf.cmd_subid = sub_cmd; } -void spi_phytium_set_cmd8(struct phytium_spi *fts, u16 sub_cmd, +int spi_phytium_set_cmd8(struct phytium_spi *fts, u16 sub_cmd, u8 data) { memset(&fts->msg_buf, 0, sizeof(struct msg)); @@ -173,11 +173,11 @@ void spi_phytium_set_cmd8(struct phytium_spi *fts, u16 sub_cmd, memcpy_toio(fts->msg, &fts->msg_buf, sizeof(struct msg)); reinit_completion(&fts->cmd_completion); phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); - spi_phytium_check_result(fts); + return spi_phytium_check_result(fts); } EXPORT_SYMBOL_GPL(spi_phytium_set_cmd8); -void spi_phytium_set_cmd16(struct phytium_spi *fts, u16 sub_cmd, +int spi_phytium_set_cmd16(struct phytium_spi *fts, u16 sub_cmd, u16 data) { u16 *cp_data = (u16 *)&fts->msg_buf.data[0]; @@ -189,11 +189,11 @@ void spi_phytium_set_cmd16(struct phytium_spi *fts, u16 sub_cmd, memcpy_toio(fts->msg, &fts->msg_buf, sizeof(struct msg)); reinit_completion(&fts->cmd_completion); phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); - spi_phytium_check_result(fts); + return spi_phytium_check_result(fts); } EXPORT_SYMBOL_GPL(spi_phytium_set_cmd16); -void spi_phytium_set_cmd32(struct phytium_spi *fts, u16 sub_cmd, +int spi_phytium_set_cmd32(struct phytium_spi *fts, u16 sub_cmd, u32 data) { u32 *cp_data = (u32 *)&fts->msg_buf.data[0]; @@ -205,7 +205,7 @@ void spi_phytium_set_cmd32(struct phytium_spi *fts, u16 sub_cmd, memcpy_toio(fts->msg, &fts->msg_buf, sizeof(struct msg)); reinit_completion(&fts->cmd_completion); phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); - spi_phytium_check_result(fts); + return spi_phytium_check_result(fts); } EXPORT_SYMBOL_GPL(spi_phytium_set_cmd32); diff --git a/drivers/spi/spi-phytium-v2.c b/drivers/spi/spi-phytium-v2.c index 0b64ad03064a4..4487620c91dd3 100644 --- a/drivers/spi/spi-phytium-v2.c +++ b/drivers/spi/spi-phytium-v2.c @@ -29,26 +29,26 @@ #define MCP251x_READ_RXB0 0x90 #define MCP251x_READ_RXB1 0x94 -static inline void spi_phyt_enable_chip(struct phytium_spi *fts, u8 enable) +static inline int spi_phyt_enable_chip(struct phytium_spi *fts, u8 enable) { u8 val = enable ? 1 : 2; - spi_phytium_set_cmd8(fts, PHYTSPI_MSG_CMD_SET_MODULE_EN, val); + return spi_phytium_set_cmd8(fts, PHYTSPI_MSG_CMD_SET_MODULE_EN, val); } -static inline void spi_phyt_set_clk(struct phytium_spi *fts, u16 div) +static inline int spi_phyt_set_clk(struct phytium_spi *fts, u16 div) { u32 new_div = div; - spi_phytium_set_cmd32(fts, PHYTSPI_MSG_CMD_SET_BAUDR, new_div); + return spi_phytium_set_cmd32(fts, PHYTSPI_MSG_CMD_SET_BAUDR, new_div); } -static inline void spi_phyt_dma_reset(struct phytium_spi *fts, u8 enable) +static inline int spi_phyt_dma_reset(struct phytium_spi *fts, u8 enable) { - spi_phytium_set_cmd8(fts, PHYTSPI_MSG_CMD_SET_DMA_RESET, enable); + return spi_phytium_set_cmd8(fts, PHYTSPI_MSG_CMD_SET_DMA_RESET, enable); } -static inline void spi_phyt_global_cs(struct phytium_spi *fts) +static inline int spi_phyt_global_cs(struct phytium_spi *fts) { u32 global_cs_en; u16 cs; @@ -56,7 +56,7 @@ static inline void spi_phyt_global_cs(struct phytium_spi *fts) global_cs_en = GENMASK(fts->num_cs-1, 0) << fts->num_cs; cs = (u16)((0x1 << 8) | global_cs_en); - spi_phytium_set_cmd16(fts, PHYTSPI_MSG_CMD_SET_CS, cs); + return spi_phytium_set_cmd16(fts, PHYTSPI_MSG_CMD_SET_CS, cs); } static inline void spi_phyt_reset_chip(struct phytium_spi *fts) @@ -68,10 +68,15 @@ static inline void spi_phyt_reset_chip(struct phytium_spi *fts) spi_phyt_enable_chip(fts, 1); } -static inline void spi_phyt_shutdown_chip(struct phytium_spi *fts) +static inline int spi_phyt_shutdown_chip(struct phytium_spi *fts) { - spi_phyt_enable_chip(fts, 0); - spi_phyt_set_clk(fts, 0); + int ret; + + ret = spi_phyt_enable_chip(fts, 0); + if (ret) + return ret; + + return spi_phyt_set_clk(fts, 0); } struct phytium_spi_chip { @@ -360,8 +365,11 @@ static int spi_phyt_setup(struct spi_device *spi) u8 data_width, scph, scpol, tmode; u16 mode; u16 clk_div; + int ret; - spi_phyt_enable_chip(fts, 0); + ret = spi_phyt_enable_chip(fts, 0); + if (ret) + return ret; if (!spi->max_speed_hz) { dev_err(&spi->dev, "max_speed_hz is zero\n"); @@ -369,7 +377,9 @@ static int spi_phyt_setup(struct spi_device *spi) } clk_div = (fts->max_freq / spi->max_speed_hz + 1) & 0xfffe; - spi_phyt_set_clk(fts, clk_div); + ret = spi_phyt_set_clk(fts, clk_div); + if (ret) + return ret; fts->clk_div = clk_div; chip = spi_get_ctldata(spi); @@ -393,17 +403,25 @@ static int spi_phyt_setup(struct spi_device *spi) chip->tmode = 0; data_width = spi->bits_per_word; - spi_phytium_set_cmd8(fts, PHYTSPI_MSG_CMD_SET_DATA_WIDTH, data_width); + ret = spi_phytium_set_cmd8(fts, PHYTSPI_MSG_CMD_SET_DATA_WIDTH, data_width); + if (ret) + return ret; scph = spi->mode & (0x1); scpol = spi->mode >> 1; mode = (scph << 8) | scpol; - spi_phytium_set_cmd16(fts, PHYTSPI_MSG_CMD_SET_MODE, mode); + ret = spi_phytium_set_cmd16(fts, PHYTSPI_MSG_CMD_SET_MODE, mode); + if (ret) + return ret; tmode = chip->tmode; - spi_phytium_set_cmd8(fts, PHYTSPI_MSG_CMD_SET_TMOD, tmode); + ret = spi_phytium_set_cmd8(fts, PHYTSPI_MSG_CMD_SET_TMOD, tmode); + if (ret) + return ret; - spi_phyt_enable_chip(fts, 1); + ret = spi_phyt_enable_chip(fts, 1); + if (ret) + return ret; return 0; } @@ -635,7 +653,18 @@ int spi_phyt_suspend_host(struct phytium_spi *fts) /* stop the watchdog timer before shutting down the chip */ timer_delete_sync(&fts->timer); - spi_phyt_shutdown_chip(fts); + ret = spi_phyt_shutdown_chip(fts); + if (ret) { + dev_err(&fts->master->dev, "firmware shutdown failed: %d\n", ret); + /* + * Let system suspend abort: the controller may still be + * enabled or keep its active clock divider. The PM core + * resumes the device during rollback, which restarts the + * transfer queue via spi_phyt_resume_host(). + */ + return ret; + } + return 0; } EXPORT_SYMBOL_GPL(spi_phyt_suspend_host); @@ -650,9 +679,15 @@ int spi_phyt_resume_host(struct phytium_spi *fts) return ret; } - spi_phyt_enable_chip(fts, 0); - spi_phyt_set_clk(fts, fts->clk_div); - spi_phyt_enable_chip(fts, 1); + ret = spi_phyt_enable_chip(fts, 0); + if (ret) + return ret; + ret = spi_phyt_set_clk(fts, fts->clk_div); + if (ret) + return ret; + ret = spi_phyt_enable_chip(fts, 1); + if (ret) + return ret; ret = spi_controller_resume(fts->master); if (ret) { diff --git a/drivers/spi/spi-phytium.h b/drivers/spi/spi-phytium.h index a3893121b10f1..74269ec9f36c9 100644 --- a/drivers/spi/spi-phytium.h +++ b/drivers/spi/spi-phytium.h @@ -340,9 +340,9 @@ static inline void phytium_write_regfile(struct phytium_spi *fts, u32 reg_off, u } extern int spi_phytium_default(struct phytium_spi *fts); -extern void spi_phytium_set_cmd8(struct phytium_spi *fts, u16 sub_cmd, u8 data); -extern void spi_phytium_set_cmd16(struct phytium_spi *fts, u16 sub_cmd, u16 data); -extern void spi_phytium_set_cmd32(struct phytium_spi *fts, u16 sub_cmd, u32 data); +extern int spi_phytium_set_cmd8(struct phytium_spi *fts, u16 sub_cmd, u8 data); +extern int spi_phytium_set_cmd16(struct phytium_spi *fts, u16 sub_cmd, u16 data); +extern int spi_phytium_set_cmd32(struct phytium_spi *fts, u16 sub_cmd, u32 data); extern void spi_phytium_data_cmd_write(struct phytium_spi *fts, u16 sub_cmd); extern void spi_phytium_data_cmd_read(struct phytium_spi *fts, u16 sub_cmd); extern void spi_phytium_write_pre(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, u8 tmode, From 42f6b59c90bd6a743792ac0e112898aeed2da8ba Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Wed, 9 Sep 2026 10:55:24 +0800 Subject: [PATCH 096/101] spi: phytium-dma: Guard the RX timeout division against a zero clock rate phytium_spi_dma_wait_rx_done() computed the poll delay as 4U * NSEC_PER_SEC / fts->max_freq * nents without guarding the division. fts->max_freq is 0 when the clock is unconfigured or the ACPI "spi-clock" property is absent, so the FIFO drain on the DMA error-recovery path divided by zero and crashed. Use max_t(u32, fts->max_freq, 1) as the divisor, mirroring the guard already present in phytium_spi_dma_wait_tx_done(). Fixes: 3674931621b2 ("arm64: spi: Phytium: Adapt SPI driver to use DDMA interface") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-dma.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-phytium-dma.c b/drivers/spi/spi-phytium-dma.c index 1565f106ee8d6..a1647f9c8fc01 100644 --- a/drivers/spi/spi-phytium-dma.c +++ b/drivers/spi/spi-phytium-dma.c @@ -295,7 +295,10 @@ static int phytium_spi_dma_wait_rx_done(struct phytium_spi *fts) * controller). */ nents = phytium_readl(fts, RXFLR); - ns = 4U * NSEC_PER_SEC / fts->max_freq * nents; + /* fts->max_freq can be 0 when the clock is unconfigured or the + * ACPI property is absent; guard the division like the TX path. + */ + ns = 4U * NSEC_PER_SEC / max_t(u32, fts->max_freq, 1) * nents; while (phytium_spi_dma_rx_busy(fts) && retry--) spi_transfer_delay_ns(ns); From f78d5afc220fc350eb463555248b9f220c71468b Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Wed, 9 Sep 2026 10:58:15 +0800 Subject: [PATCH 097/101] spi: phytium-common: Bound the flash command address buffer copies Two stack buffer overflows in the firmware command helpers: - spi_phytium_flash_erase() copied len bytes (the full remaining transfer length) into cmd_addr[1..7], which holds only 7 bytes, and then read len + 1 bytes back from the 8-byte buffer, past its end. A malformed or oversized client transfer longer than 7 bytes corrupted the stack. - spi_phytium_flash_write() copied fts->len bytes into cmd_addr[2..7], 6 bytes of space, with no bound. cmd_addr[0] also assigned the size_t length to a u8 byte, silently truncating it. Cap the copies to the buffer space in both functions and make the u8 length conversion explicit. Also fix the unaligned u64 dereference in memcpy_byte(): its callers pass &cmd_addr[1]/&cmd_addr[2], which are not 8-byte aligned; use get_unaligned()/put_unaligned() so the helper is safe on strict-alignment architectures. Fixes: b4cb0174adb1 ("spi-v2: phytium: Add the debug log function to the driver") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-common.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-phytium-common.c b/drivers/spi/spi-phytium-common.c index ba6dd11cbbed2..c2c4661d566f2 100644 --- a/drivers/spi/spi-phytium-common.c +++ b/drivers/spi/spi-phytium-common.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "spi-phytium.h" #define SPI_SHOW_MSG_DEBUG 0 @@ -52,7 +53,8 @@ static void spi_phytium_show_msg(struct msg *info) static void *memcpy_byte(void *_dest, const void *_src, size_t sz) { while (sz >= 8) { - *(u64 *)_dest = *(u64 *)_src; + /* _dest/_src are not guaranteed to be 8-byte aligned */ + put_unaligned(get_unaligned((const u64 *)_src), (u64 *)_dest); _dest += 8; _src += 8; sz -= 8; @@ -267,12 +269,17 @@ int spi_phytium_flash_erase(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, u8 tmode, u8 flags, u8 cmd) { u32 len; + u32 copy_len; void __iomem *smem_tx; u8 first = 1; u8 cmd_addr[8]; int ret; len = (u32)(fts->tx_end - fts->tx); + /* cmd_addr packs the opcode plus at most a 7-byte address phase; + * longer transfers would overflow the stack buffer. + */ + copy_len = min_t(u32, len, sizeof(cmd_addr) - 1); memset(&fts->msg_buf, 0, sizeof(struct msg)); @@ -290,9 +297,9 @@ int spi_phytium_flash_erase(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, if (cmd == SPINOR_OP_BE_4K || cmd == SPINOR_OP_READ || cmd == SPINOR_OP_READ_FAST || cmd == SPINOR_OP_READ_4B || cmd == SPINOR_OP_READ_FAST_4B) { - memcpy_byte((void *)&cmd_addr[1], fts->tx, len); - memcpy_toio(smem_tx, (void *)&cmd_addr[0], len + 1); - *(u32 *)&fts->msg_buf.data[8] = len + 1; + memcpy_byte((void *)&cmd_addr[1], fts->tx, copy_len); + memcpy_toio(smem_tx, (void *)&cmd_addr[0], copy_len + 1); + *(u32 *)&fts->msg_buf.data[8] = copy_len + 1; } else if (cmd == SPINOR_OP_CHIP_ERASE) { memcpy_toio(smem_tx, (void *)&cmd_addr[0], 1); *(u32 *)&fts->msg_buf.data[8] = len; @@ -319,10 +326,15 @@ EXPORT_SYMBOL_GPL(spi_phytium_flash_erase); int spi_phytium_flash_write(struct phytium_spi *fts, u8 cmd) { u8 cmd_addr[8] = {0}; + size_t copy_len = min_t(size_t, fts->len, sizeof(cmd_addr) - 2); - cmd_addr[0] = fts->len + 1; + /* cmd_addr packs the length byte and opcode, leaving 6 bytes for + * the payload; fts->len is a size_t and must not be truncated + * implicitly either. + */ + cmd_addr[0] = (u8)(copy_len + 1); cmd_addr[1] = cmd; - memcpy_byte((void *)&cmd_addr[2], fts->tx, fts->len); + memcpy_byte((void *)&cmd_addr[2], fts->tx, copy_len); fts->msg_buf.data[18] = cmd_addr[0]; fts->msg_buf.data[19] = cmd_addr[1]; From c54ffdaf3eed09bd808ad5003d90849f28020bf2 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Wed, 9 Sep 2026 17:06:46 +0800 Subject: [PATCH 098/101] spi: phytium-qspi: Program flash capacity when spi-nor loads late The capacity registers need each flash's size, which comes from the spi-nor driver bound to the child. When MTD_SPI_NOR is a module that has not loaded yet, the children exist but no driver is bound, so the sizes stay zero and the capacity encoding cannot be programmed. Register a bus notifier and program the capacity when spi-nor binds. SPI children are parented by the controller device, so filter notifications against &qspi->ctrl->dev rather than the platform device. This keeps the controller independent of spi-nor module load order. Fixes: 9f2e21865eb1 ("arm64: spi: Phytium-qspi: Add support for Phytium QSPI controller") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-qspi.c | 199 +++++++++++++++++++++------------ 1 file changed, 128 insertions(+), 71 deletions(-) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index ff4f1251cf50f..5ba941cc02e4f 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -20,6 +20,8 @@ #include #include +#include +#include #define DRIVER_VERSION "1.0.3" @@ -181,6 +183,7 @@ struct phytium_qspi { u32 wr_cfg_reg[PHYTIUM_QSPI_MAX_NORCHIP]; u32 rd_cfg_reg[PHYTIUM_QSPI_MAX_NORCHIP]; u32 flash_cap; + struct notifier_block nb; }; static uint phytium_spi_nor_clac_clk_div(int div) @@ -597,9 +600,105 @@ static ssize_t phytium_qspi_dirmap_write(struct spi_mem_dirmap_desc *desc, return orig_len; } -static int phytium_qspi_setup(struct spi_device *spi) +static int phytium_qspi_program_capacity(struct phytium_qspi *qspi) { - struct spi_controller *ctrl = spi->controller; + struct device *dev = qspi->dev; + struct spi_mem *mem; + struct spi_nor *nor; + u32 flash_cap = 0; + int first_flash = -1; + int i, ret; + + if (qspi->fnum == 0) + return 0; + + /* Discover the size of every bound flash */ + for (i = 0; i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { + if (!qspi->flash[i].spi) + continue; + if (!qspi->flash[i].spi->dev.driver || + strcmp(qspi->flash[i].spi->dev.driver->name, "spi-nor")) + return -EPROBE_DEFER; + if (first_flash < 0) + first_flash = i; + mem = spi_get_drvdata(qspi->flash[i].spi); + if (mem) { + nor = spi_mem_get_drvdata(mem); + if (nor) + qspi->flash[i].size = nor->mtd.size; + } + if (!qspi->flash[i].size) + return -EPROBE_DEFER; + } + + if (!qspi->nodirmap) { + for (i = 0; i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { + if (!qspi->flash[i].spi || i == first_flash) + continue; + if (qspi->flash[i].size != qspi->flash[first_flash].size) { + dev_err(dev, "Flashes are of different sizes.\n"); + return -EINVAL; + } + } + + ret = phytium_qspi_flash_capacity_encode(qspi->flash[first_flash].size, + &flash_cap); + if (ret) { + dev_err(dev, "Flash size is invalid.\n"); + return ret; + } + + if (qspi->fnum > (QSPI_FLASH_CAP_NUM_MASK >> QSPI_FLASH_CAP_NUM_SHIFT)) + dev_warn(dev, "%u flashes exceed the CAP NUM field width\n", + qspi->fnum); + flash_cap |= (qspi->fnum << QSPI_FLASH_CAP_NUM_SHIFT) & + QSPI_FLASH_CAP_NUM_MASK; + qspi->flash_cap = flash_cap; + writel_relaxed(qspi->flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); + } else { + for (i = 0; i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { + if (!qspi->flash[i].spi) + continue; + ret = phytium_qspi_flash_capacity_encode_new(qspi->flash[i].size, + &qspi->flash_cap, i); + if (ret) { + dev_err(dev, "Flash size is invalid.\n"); + return ret; + } + } + qspi->flash_cap |= (qspi->fnum - 1) << QSPI_FLASH_CAP_NUM_SHIFT_NEW; + writel_relaxed(qspi->flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); + } + + return 0; +} + +static int phytium_qspi_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct phytium_qspi *qspi = + container_of(nb, struct phytium_qspi, nb); + struct device *dev = data; + int ret; + + if (action != BUS_NOTIFY_BOUND_DRIVER) + return NOTIFY_DONE; + if (!dev->driver || strcmp(dev->driver->name, "spi-nor")) + return NOTIFY_DONE; + /* SPI children are parented by the controller device, not the + * platform device that owns the controller. */ + if (dev->parent != &qspi->ctrl->dev) + return NOTIFY_DONE; + + ret = phytium_qspi_program_capacity(qspi); + if (ret && ret != -EPROBE_DEFER) + dev_err(qspi->dev, "capacity programming failed: %d\n", ret); + + return NOTIFY_OK; +} + +static int phytium_qspi_setup(struct spi_device *spi) +{ struct spi_controller *ctrl = spi->controller; struct phytium_qspi *qspi = spi_controller_get_devdata(ctrl); struct phytium_qspi_flash *flash; uint clk_div; @@ -672,10 +771,7 @@ static int phytium_qspi_probe(struct platform_device *pdev) struct spi_controller *ctrl; struct resource *res = NULL; struct phytium_qspi *qspi; - int i, ret; - u32 flash_cap; - struct spi_mem *mem; - struct spi_nor *nor; + int ret; const char **reg_name_array; ctrl = devm_spi_alloc_host(dev, sizeof(*qspi)); @@ -786,75 +882,34 @@ static int phytium_qspi_probe(struct platform_device *pdev) ret = devm_spi_register_controller(dev, ctrl); if (ret) { dev_err(dev, "failed to register SPI controller: %d\n", ret); - goto probe_setup_failed; + goto probe_clk_failed; } - if (qspi->fnum != 0) { - int first_flash = -1; - - /* - * Discover the size of every bound flash first; the capacity - * encoding below needs it in both dirmap and nodirmap modes. - */ - for (i = 0; i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { - if (!qspi->flash[i].spi) - continue; - if (first_flash < 0) - first_flash = i; - mem = spi_get_drvdata(qspi->flash[i].spi); - if (mem) { - nor = spi_mem_get_drvdata(mem); - if (nor) - qspi->flash[i].size = nor->mtd.size; - } + /* + * Program flash capacity registers. The flash sizes come from the + * spi-nor driver bound to each child. When MTD_SPI_NOR is a module + * that has not been loaded yet, the children exist but have no + * driver — return success now and register a bus notifier so the + * capacity registers are programmed when spi-nor binds. + */ + ret = phytium_qspi_program_capacity(qspi); + if (ret == -EPROBE_DEFER) { + qspi->nb.notifier_call = phytium_qspi_notifier; + ret = bus_register_notifier(&spi_bus_type, &qspi->nb); + if (ret) { + dev_err(dev, "failed to register bus notifier: %d\n", ret); + goto probe_setup_failed; } - - if (!qspi->nodirmap) { - /* - * The controller supports direct mapping access only if - * all flashes are of same size. - */ - for (i = 0; i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { - if (!qspi->flash[i].spi || i == first_flash) - continue; - if (qspi->flash[i].size != qspi->flash[first_flash].size) { - dev_err(dev, "Flashes are of different sizes.\n"); - ret = -EINVAL; - goto probe_setup_failed; - } - } - - ret = phytium_qspi_flash_capacity_encode(qspi->flash[first_flash].size, - &flash_cap); - if (ret) { - dev_err(dev, "Flash size is invalid.\n"); - goto probe_setup_failed; - } - - if (qspi->fnum > (QSPI_FLASH_CAP_NUM_MASK >> QSPI_FLASH_CAP_NUM_SHIFT)) - dev_warn(dev, "%u flashes exceed the CAP NUM field width\n", - qspi->fnum); - flash_cap |= (qspi->fnum << QSPI_FLASH_CAP_NUM_SHIFT) & - QSPI_FLASH_CAP_NUM_MASK; - /* cache the programmed value for the resume path */ - qspi->flash_cap = flash_cap; - - writel_relaxed(qspi->flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); - } else { - for (i = 0; i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { - if (!qspi->flash[i].spi) - continue; - ret = phytium_qspi_flash_capacity_encode_new(qspi->flash[i].size, - &qspi->flash_cap, i); - if (ret) { - dev_err(dev, "Flash size is invalid.\n"); - goto probe_setup_failed; - } - } - qspi->flash_cap |= (qspi->fnum - 1) << QSPI_FLASH_CAP_NUM_SHIFT_NEW; - - writel_relaxed(qspi->flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); + /* retry now in case spi-nor bound between the first attempt + * and the notifier registration */ + ret = phytium_qspi_program_capacity(qspi); + if (ret && ret != -EPROBE_DEFER) { + bus_unregister_notifier(&spi_bus_type, &qspi->nb); + dev_err(dev, "capacity programming failed: %d\n", ret); + goto probe_setup_failed; } + } else if (ret) { + goto probe_setup_failed; } return 0; @@ -884,6 +939,8 @@ static void phytium_qspi_remove(struct platform_device *pdev) struct phytium_qspi *qspi = platform_get_drvdata(pdev); struct device *dev = &pdev->dev; + bus_unregister_notifier(&spi_bus_type, &qspi->nb); + clk_disable_unprepare(qspi->clk); if (dev->of_node) { From 4ce3de4ecd3812a734c9688affa1cff934b559ea Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Wed, 9 Sep 2026 17:11:21 +0800 Subject: [PATCH 099/101] spi: phytium-qspi: Unregister the controller before shutting the hardware down phytium_qspi_remove() disabled the clock first, while the controller was registered through devm_spi_register_controller(): the devres unregister action only runs after the platform .remove callback returns, so child devices and the transfer queue stayed registered against dead hardware for that window. The deferred-probe error path added for spi-nor load ordering has the same inverted teardown. Register explicitly with spi_register_controller() and unregister the controller before disabling the clock, both in .remove and on the post-registration failure paths. Fixes: 9f2e21865eb1 ("arm64: spi: Phytium-qspi: Add support for Phytium QSPI controller") Signed-off-by: Wentao Guan --- drivers/spi/spi-phytium-qspi.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c index 5ba941cc02e4f..acbf1900f6d59 100644 --- a/drivers/spi/spi-phytium-qspi.c +++ b/drivers/spi/spi-phytium-qspi.c @@ -879,7 +879,7 @@ static int phytium_qspi_probe(struct platform_device *pdev) qspi->dev = dev; platform_set_drvdata(pdev, qspi); - ret = devm_spi_register_controller(dev, ctrl); + ret = spi_register_controller(ctrl); if (ret) { dev_err(dev, "failed to register SPI controller: %d\n", ret); goto probe_clk_failed; @@ -915,8 +915,11 @@ static int phytium_qspi_probe(struct platform_device *pdev) return 0; probe_setup_failed: - clk_disable_unprepare(qspi->clk); + /* the controller was registered: tear it down before killing + * the clock it depends on */ + spi_unregister_controller(ctrl); probe_clk_failed: + clk_disable_unprepare(qspi->clk); if (dev->of_node) { pm_runtime_put_sync(dev); pm_runtime_disable(dev); @@ -940,6 +943,9 @@ static void phytium_qspi_remove(struct platform_device *pdev) struct device *dev = &pdev->dev; bus_unregister_notifier(&spi_bus_type, &qspi->nb); + /* unregister children and the transfer queue before the hardware + * (clock, runtime PM) is turned off */ + spi_unregister_controller(qspi->ctrl); clk_disable_unprepare(qspi->clk); From b222f14404013b56dd329d27362ce06e6ef0e087 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Wed, 9 Sep 2026 17:14:11 +0800 Subject: [PATCH 100/101] mtd: acpipart: Descend into the dedicated partitions container node The parser treated any child of the flash device as proof that a dedicated partition layout was present, then iterated only the master's direct children. A conventional "partitions" container node therefore failed parsing: it has no offset/length of its own, so in dedicated mode it triggered -EINVAL, and its partition children were never visited. An unrelated child node (e.g. a GPIO consumer) likewise switched the parser into strict dedicated mode by mistake. Descend into a "partitions" subnode when present, mirroring the OF parser: the container enables dedicated mode and its children are the partitions. Without the container, keep parsing the direct subnodes leniently. Fixes: 7f4fb2e5cdaf ("arm64: phytium: UEFI mode acpi table support for qspi/spi driver") Signed-off-by: Wentao Guan --- drivers/mtd/parsers/acpipart_core.c | 42 ++++++++++++++++++----------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/drivers/mtd/parsers/acpipart_core.c b/drivers/mtd/parsers/acpipart_core.c index bd8956dda17d0..f3aeb2392ed7f 100644 --- a/drivers/mtd/parsers/acpipart_core.c +++ b/drivers/mtd/parsers/acpipart_core.c @@ -23,6 +23,8 @@ static int parse_acpi_fixed_partitions(struct mtd_info *master, const char *partname; int nr_parts, i, ret = 0; struct fwnode_handle *child_handle = NULL; + struct fwnode_handle *partitions_node = NULL; + const struct fwnode_handle *parse_root; bool dedicated = true; struct device *dev; @@ -30,14 +32,16 @@ static int parse_acpi_fixed_partitions(struct mtd_info *master, if (!master->parent) {/*master*/ /* - * Probe whether the device has any child node; the - * returned reference is only needed for the check. + * A conventional "partitions" subnode groups the actual + * partition children; descend into it when present, + * mirroring the OF parser. Only such a container enables + * strict dedicated mode — an unrelated child node must + * not, and without the container the direct subnodes are + * parsed leniently as partitions. */ - child_handle = device_get_next_child_node(dev, NULL); - if (child_handle) { - fwnode_handle_put(child_handle); - child_handle = NULL; - } else { + partitions_node = fwnode_get_named_child_node(dev->fwnode, + "partitions"); + if (!partitions_node) { pr_debug("%s: 'partitions' subnode not found on %pfw. Trying to parse direct subnodes as partitions.\n", master->name, dev->fwnode); dedicated = false; @@ -52,19 +56,24 @@ static int parse_acpi_fixed_partitions(struct mtd_info *master, * a JEDEC ID). */ + parse_root = partitions_node ? partitions_node : dev->fwnode; + nr_parts = 0; - device_for_each_child_node(dev, child_handle) { + fwnode_for_each_child_node(parse_root, child_handle) nr_parts++; - } - if (nr_parts == 0) - return 0; + if (nr_parts == 0) { + ret = 0; + goto out_put; + } parts = kcalloc(nr_parts, sizeof(*parts), GFP_KERNEL); - if (!parts) - return -ENOMEM; + if (!parts) { + ret = -ENOMEM; + goto out_put; + } i = 0; - device_for_each_child_node(dev, child_handle) { + fwnode_for_each_child_node(parse_root, child_handle) { u64 offset = 0, length = 0; bool bool_match; @@ -107,7 +116,7 @@ static int parse_acpi_fixed_partitions(struct mtd_info *master, *pparts = parts; ret = nr_parts; - return ret; + goto out_put; acpipart_fail: pr_err("%s: error parsing acpipart partition %pfw (%pfw)\n", @@ -120,6 +129,9 @@ static int parse_acpi_fixed_partitions(struct mtd_info *master, for (i = 0; i < nr_parts; i++) fwnode_handle_put(parts[i].fwnode); kfree(parts); +out_put: + /* NULL-safe when no container node was found */ + fwnode_handle_put(partitions_node); return ret; } From 3a831322a0153edb862c39fa7b77375a8fe6044a Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Thu, 10 Sep 2026 00:32:24 +0800 Subject: [PATCH 101/101] ALSA: hda: phytium: Enable runtime power management The driver provides runtime suspend and resume callbacks, but never sets AZX_DCAPS_PM_RUNTIME or enables runtime PM. Consequently the callbacks are unreachable and the controller remains active even after all codecs enter D3. Enable runtime PM and hold the controller active while the asynchronous probe accesses its registers. Allow codecs to use the configured power-save delay so the parent controller can stop its command engine and place the link in reset once every codec is idle. Use the same callbacks for system sleep and balance runtime PM during probe failure, removal and shutdown. Also quiesce and synchronize the deferred stream IRQ work before entering link reset. Fixes: 8da93e4a61f7 ("hda: phytium: Add Phytium hda driver support") Signed-off-by: Wentao Guan --- sound/hda/controllers/phytium.c | 200 ++++++++++++++++---------------- 1 file changed, 100 insertions(+), 100 deletions(-) diff --git a/sound/hda/controllers/phytium.c b/sound/hda/controllers/phytium.c index 5db5a881f85c0..65064914a608d 100644 --- a/sound/hda/controllers/phytium.c +++ b/sound/hda/controllers/phytium.c @@ -97,7 +97,14 @@ module_param_array(beep_mode, bool, NULL, 0444); MODULE_PARM_DESC(beep_mode, "Select HDA Beep registration mode (0=off, 1=on) (default=1)."); #endif +#ifdef CONFIG_PM +static int power_save = CONFIG_SND_HDA_POWER_SAVE_DEFAULT; +module_param(power_save, bint, 0644); +MODULE_PARM_DESC(power_save, + "Automatic power-saving timeout (in seconds, 0 = disable)."); +#else #define power_save 0 +#endif static int align_buffer_size = -1; module_param(align_buffer_size, bint, 0644); @@ -430,157 +437,125 @@ static void azx_del_card_list(struct azx *chip) #define azx_del_card_list(chip) /* NOP */ #endif /* CONFIG_PM */ -#if defined(CONFIG_PM_SLEEP) -/* power management */ -static int azx_suspend(struct device *dev) +#ifdef CONFIG_PM +static bool azx_is_pm_ready(struct snd_card *card) +{ + struct azx *chip; + + if (!card) + return false; + + chip = card->private_data; + return !chip->disabled && chip->running; +} + +static int azx_runtime_suspend(struct device *dev) { struct snd_card *card = dev_get_drvdata(dev); struct azx *chip; struct hda_ft *hda; - struct hdac_bus *bus; - if (!card) + if (!azx_is_pm_ready(card)) return 0; chip = card->private_data; hda = container_of(chip, struct hda_ft, chip); - if (chip->disabled || !chip->running) - return 0; - bus = azx_bus(chip); - snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) | + STATESTS_INT_MASK); + azx_stop_chip(chip); + if (azx_bus(chip)->irq >= 0) + synchronize_irq(azx_bus(chip)->irq); azx_clear_irq_pending(chip); cancel_work_sync(&hda->irq_pending_work); - azx_stop_chip(chip); - if (bus->irq >= 0) { - free_irq(bus->irq, (void *)chip); - bus->irq = -1; - } + azx_enter_link_reset(chip); return 0; } -static int azx_resume(struct device *dev) +static int azx_runtime_resume(struct device *dev) { struct snd_card *card = dev_get_drvdata(dev); struct azx *chip; - struct hda_ft *hda; - struct hdac_bus *bus; + struct hda_codec *codec; + int status; - if (!card) + if (!azx_is_pm_ready(card)) return 0; chip = card->private_data; - hda = container_of(chip, struct hda_ft, chip); - bus = azx_bus(chip); - if (chip->disabled || !chip->running) - return 0; - - if (azx_acquire_irq(chip, 1) < 0) - return -EIO; - snd_hdac_bus_exit_link_reset(bus); + /* STATESTS is cleared by the controller reset below. */ + status = azx_readw(chip, STATESTS); + snd_hdac_bus_exit_link_reset(azx_bus(chip)); usleep_range(1000, 1200); - azx_init_chip(chip, 0); + azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) & + ~STATESTS_INT_MASK); - snd_power_change_state(card, SNDRV_CTL_POWER_D0); + if (status && !chip->bus.shutdown) { + list_for_each_codec(codec, &chip->bus) + if (status & BIT(codec->addr)) + pm_request_resume(hda_codec_dev(codec)); + } return 0; } -#endif /* CONFIG_PM_SLEEP */ -#ifdef CONFIG_PM -static int azx_runtime_suspend(struct device *dev) +static int azx_runtime_idle(struct device *dev) { struct snd_card *card = dev_get_drvdata(dev); struct azx *chip; - struct hda_ft *hda; if (!card) return 0; chip = card->private_data; - hda = container_of(chip, struct hda_ft, chip); if (chip->disabled) return 0; - if (!azx_has_pm_runtime(chip)) - return 0; - - azx_stop_chip(chip); - azx_enter_link_reset(chip); - azx_clear_irq_pending(chip); + if (azx_bus(chip)->codec_powered || !chip->running) + return -EBUSY; return 0; } -static int azx_runtime_resume(struct device *dev) +static int azx_suspend(struct device *dev) { struct snd_card *card = dev_get_drvdata(dev); - struct azx *chip; - struct hda_ft *hda; - struct hdac_bus *bus; - struct hda_codec *codec; - int status; - int index; - - if (!card) - return 0; - - chip = card->private_data; - hda = container_of(chip, struct hda_ft, chip); - bus = azx_bus(chip); - if (chip->disabled) - return 0; + int err; - if (!azx_has_pm_runtime(chip)) + if (!azx_is_pm_ready(card)) return 0; - /* Read STATESTS before controller reset */ - status = azx_readw(chip, STATESTS); - - index = chip->dev_index; - - snd_hdac_bus_exit_link_reset(bus); - usleep_range(1000, 1200); - - azx_init_chip(chip, 0); - - if (status) { - list_for_each_codec(codec, &chip->bus) - if (status & (1 << codec->addr)) - schedule_delayed_work(&codec->jackpoll_work, - codec->jackpoll_interval); - } + err = pm_runtime_force_suspend(dev); + if (err < 0) + return err; + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); return 0; } -static int azx_runtime_idle(struct device *dev) +static int azx_resume(struct device *dev) { struct snd_card *card = dev_get_drvdata(dev); - struct azx *chip; - struct hda_ft *hda; + int err; - if (!card) + if (!azx_is_pm_ready(card)) return 0; - chip = card->private_data; - hda = container_of(chip, struct hda_ft, chip); - if (chip->disabled) - return 0; - - if (!azx_has_pm_runtime(chip) || - azx_bus(chip)->codec_powered || !chip->running) - return -EBUSY; + err = pm_runtime_force_resume(dev); + if (err < 0) + return err; + snd_power_change_state(card, SNDRV_CTL_POWER_D0); return 0; } static const struct dev_pm_ops azx_pm = { - SET_SYSTEM_SLEEP_PM_OPS(azx_suspend, azx_resume) - SET_RUNTIME_PM_OPS(azx_runtime_suspend, azx_runtime_resume, azx_runtime_idle) + SYSTEM_SLEEP_PM_OPS(azx_suspend, azx_resume) + RUNTIME_PM_OPS(azx_runtime_suspend, azx_runtime_resume, + azx_runtime_idle) }; #define hda_ft_pm (&azx_pm) @@ -589,6 +564,7 @@ static const struct dev_pm_ops azx_pm = { #endif /* CONFIG_PM */ static int azx_probe_continue(struct azx *chip); +static void azx_probe_failed(struct azx *chip); /* * destructor @@ -597,12 +573,8 @@ static int azx_free(struct azx *chip) { struct hda_ft *hda = container_of(chip, struct hda_ft, chip); struct hdac_bus *bus = azx_bus(chip); - struct platform_device *pdev = to_platform_device(hda->dev); struct device *hddev = hda->dev; - if (azx_has_pm_runtime(chip) && chip->running) - pm_runtime_get_noresume(&pdev->dev); - azx_del_card_list(chip); complete_all(&hda->probe_wait); @@ -703,8 +675,19 @@ static void check_probe_mask(struct azx *chip, int dev) static void azx_probe_work(struct work_struct *work) { struct hda_ft *hda = container_of(work, struct hda_ft, probe_work); + int err; - azx_probe_continue(&hda->chip); + err = pm_runtime_resume_and_get(hda->dev); + if (err < 0) { + azx_probe_failed(&hda->chip); + pm_runtime_disable(hda->dev); + return; + } + + err = azx_probe_continue(&hda->chip); + pm_runtime_put(hda->dev); + if (err < 0) + pm_runtime_disable(hda->dev); } /* @@ -912,7 +895,8 @@ static DECLARE_BITMAP(probed_devs, SNDRV_CARDS); static int hda_ft_probe(struct platform_device *pdev) { - const unsigned int driver_flags = AZX_DRIVER_FT; + const unsigned int driver_flags = AZX_DRIVER_FT | + AZX_DCAPS_PM_RUNTIME; struct snd_card *card; struct hda_ft *hda; struct azx *chip; @@ -943,13 +927,14 @@ static int hda_ft_probe(struct platform_device *pdev) hda = container_of(chip, struct hda_ft, chip); dev_set_drvdata(&pdev->dev, card); + pm_runtime_enable(&pdev->dev); schedule_probe = !chip->disabled; + set_bit(dev, probed_devs); if (schedule_probe) schedule_work(&hda->probe_work); - set_bit(dev, probed_devs); if (chip->disabled) complete_all(&hda->probe_wait); return 0; @@ -967,10 +952,8 @@ static unsigned int azx_max_codecs[AZX_NUM_DRIVERS] = { static int azx_probe_continue(struct azx *chip) { struct hda_ft *hda = container_of(chip, struct hda_ft, chip); - struct device *hddev = hda->dev; int dev = chip->dev_index; int err; - struct hdac_bus *bus = azx_bus(chip); hda->probe_continued = 1; @@ -1001,11 +984,18 @@ static int azx_probe_continue(struct azx *chip) azx_add_card_list(chip); snd_hda_set_power_save(&chip->bus, power_save * 1000); - if (azx_has_pm_runtime(chip)) - pm_runtime_put_noidle(hddev); return err; out_free: + azx_probe_failed(chip); + return err; +} + +static void azx_probe_failed(struct azx *chip) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + struct hdac_bus *bus = azx_bus(chip); + if (bus->irq >= 0) { free_irq(bus->irq, (void *)chip); bus->irq = -1; @@ -1018,10 +1008,9 @@ static int azx_probe_continue(struct azx *chip) * device stays bound and its remove/shutdown/PM callbacks must * not dereference the freed card, mirroring snd-hda-intel. */ - dev_set_drvdata(hddev, NULL); + dev_set_drvdata(hda->dev, NULL); if (chip->card) snd_card_free(chip->card); - return err; } static void hda_ft_remove(struct platform_device *pdev) @@ -1029,15 +1018,21 @@ static void hda_ft_remove(struct platform_device *pdev) struct snd_card *card = dev_get_drvdata(&pdev->dev); struct azx *chip; struct hda_ft *hda; + int err; if (card) { /* cancel the pending probing work */ chip = card->private_data; hda = container_of(chip, struct hda_ft, chip); cancel_work_sync(&hda->probe_work); + chip->bus.shutdown = 1; + err = pm_runtime_resume_and_get(&pdev->dev); + pm_runtime_disable(&pdev->dev); clear_bit(chip->dev_index, probed_devs); snd_card_free(card); + if (err >= 0) + pm_runtime_put_noidle(&pdev->dev); } } @@ -1049,8 +1044,13 @@ static void hda_ft_shutdown(struct platform_device *pdev) if (!card) return; chip = card->private_data; - if (chip && chip->running) + if (chip && chip->running) { + chip->bus.shutdown = 1; + if (pm_runtime_resume_and_get(&pdev->dev) < 0) + return; azx_stop_chip(chip); + pm_runtime_put_noidle(&pdev->dev); + } } static const struct of_device_id hda_ft_of_match[] = {