diff options
Diffstat (limited to 'drivers')
65 files changed, 16456 insertions, 230 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig index dcecc9f6e33f..1bd21494d4cd 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -235,4 +235,7 @@ source "drivers/interconnect/Kconfig" source "drivers/counter/Kconfig" source "drivers/most/Kconfig" + +source "drivers/peci/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index c0cd1b9075e3..bbc6d661b9de 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -188,3 +188,4 @@ obj-$(CONFIG_GNSS) += gnss/ obj-$(CONFIG_INTERCONNECT) += interconnect/ obj-$(CONFIG_COUNTER) += counter/ obj-$(CONFIG_MOST) += most/ +obj-$(CONFIG_PECI) += peci/ diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 98c3a5d8003e..98d2e04ac24f 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -333,6 +333,15 @@ config DEVMEM memory. When in doubt, say "Y". +config DEVMEM_BOOTPARAM + bool "mem.devmem boot parameter" + depends on DEVMEM + default n + help + This option adds a 'mem.devmem' kernel parameter which activates + the /dev/mem device when enabled. + When in doubt, say "N". + config DEVKMEM bool "/dev/kmem virtual device support" # On arm64, VMALLOC_START < PAGE_OFFSET, which confuses kmem read/write diff --git a/drivers/char/ipmi/bt-bmc.c b/drivers/char/ipmi/bt-bmc.c index a395e2e70dc5..a514f531cb34 100644 --- a/drivers/char/ipmi/bt-bmc.c +++ b/drivers/char/ipmi/bt-bmc.c @@ -508,6 +508,7 @@ static int bt_bmc_remove(struct platform_device *pdev) static const struct of_device_id bt_bmc_match[] = { { .compatible = "aspeed,ast2400-ibt-bmc" }, { .compatible = "aspeed,ast2500-ibt-bmc" }, + { .compatible = "aspeed,ast2600-ibt-bmc" }, { }, }; diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c index a140203c079b..2a2508d54e2a 100644 --- a/drivers/char/ipmi/kcs_bmc_aspeed.c +++ b/drivers/char/ipmi/kcs_bmc_aspeed.c @@ -400,6 +400,7 @@ static const struct of_device_id ast_kcs_bmc_match[] = { { .compatible = "aspeed,ast2500-kcs-bmc" }, { .compatible = "aspeed,ast2400-kcs-bmc-v2" }, { .compatible = "aspeed,ast2500-kcs-bmc-v2" }, + { .compatible = "aspeed,ast2600-kcs-bmc" }, { } }; MODULE_DEVICE_TABLE(of, ast_kcs_bmc_match); diff --git a/drivers/char/mem.c b/drivers/char/mem.c index 687d4af6945d..0050a401ae46 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c @@ -10,6 +10,7 @@ */ #include <linux/mm.h> +#include <linux/moduleparam.h> #include <linux/miscdevice.h> #include <linux/slab.h> #include <linux/vmalloc.h> @@ -994,6 +995,12 @@ static char *mem_devnode(struct device *dev, umode_t *mode) return NULL; } +#ifdef CONFIG_DEVMEM_BOOTPARAM +static bool devmem; +module_param(devmem, bool, 0444); +MODULE_PARM_DESC(devmem, "kernel parameter to activate /dev/mem"); +#endif + static struct class *mem_class; static int devmem_fs_init_fs_context(struct fs_context *fc) @@ -1054,6 +1061,10 @@ static int __init chr_dev_init(void) if (!devlist[minor].name) continue; +#ifdef CONFIG_DEVMEM_BOOTPARAM + if (minor == DEVMEM_MINOR && !devmem) + continue; +#endif /* * Create /dev/port? */ diff --git a/drivers/clk/clk-ast2600.c b/drivers/clk/clk-ast2600.c index 177368cac6dd..bbacaccad554 100644 --- a/drivers/clk/clk-ast2600.c +++ b/drivers/clk/clk-ast2600.c @@ -64,7 +64,7 @@ static const struct aspeed_gate_data aspeed_g6_gates[] = { [ASPEED_CLK_GATE_GCLK] = { 2, 7, "gclk-gate", NULL, 0 }, /* 2D engine */ /* vclk parent - dclk/d1clk/hclk/mclk */ [ASPEED_CLK_GATE_VCLK] = { 3, 6, "vclk-gate", NULL, 0 }, /* Video Capture */ - [ASPEED_CLK_GATE_BCLK] = { 4, 8, "bclk-gate", "bclk", 0 }, /* PCIe/PCI */ + [ASPEED_CLK_GATE_BCLK] = { 4, 8, "bclk-gate", "bclk", CLK_IS_CRITICAL }, /* PCIe/PCI */ /* From dpll */ [ASPEED_CLK_GATE_DCLK] = { 5, -1, "dclk-gate", NULL, CLK_IS_CRITICAL }, /* DAC */ [ASPEED_CLK_GATE_REF0CLK] = { 6, -1, "ref0clk-gate", "clkin", CLK_IS_CRITICAL }, diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig index 7b6ec3014ba2..fc30f2ef9782 100644 --- a/drivers/edac/Kconfig +++ b/drivers/edac/Kconfig @@ -530,4 +530,11 @@ config EDAC_DMC520 Support for error detection and correction on the SoCs with ARM DMC-520 DRAM controller. +config EDAC_NPCM7XX + tristate "Nuvoton NPCM7xx DDR Memory Controller" + depends on ARCH_NPCM7XX + help + Support for error detection and correction on the + Nuvoton NPCM7xx DDR memory controller. + endif # EDAC diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile index 269e15118cea..25c77763d666 100644 --- a/drivers/edac/Makefile +++ b/drivers/edac/Makefile @@ -88,3 +88,4 @@ obj-$(CONFIG_EDAC_QCOM) += qcom_edac.o obj-$(CONFIG_EDAC_ASPEED) += aspeed_edac.o obj-$(CONFIG_EDAC_BLUEFIELD) += bluefield_edac.o obj-$(CONFIG_EDAC_DMC520) += dmc520_edac.o +obj-$(CONFIG_EDAC_NPCM7XX) += npcm7xx_edac.o diff --git a/drivers/edac/npcm7xx_edac.c b/drivers/edac/npcm7xx_edac.c new file mode 100644 index 000000000000..c60761766ec1 --- /dev/null +++ b/drivers/edac/npcm7xx_edac.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019 Quanta Computer lnc. + */ + +#include <linux/edac.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of_address.h> +#include <linux/of_device.h> + +#include "edac_module.h" + +#define ECC_ENABLE BIT(24) +#define ECC_EN_INT_MASK 0x7fffff87 + +#define INT_STATUS_ADDR 116 +#define INT_ACK_ADDR 117 +#define INT_MASK_ADDR 118 + +#define ECC_EN_ADDR 93 +#define ECC_C_ADDR_ADDR 98 +#define ECC_C_DATA_ADDR 100 +#define ECC_C_ID_ADDR 101 +#define ECC_C_SYND_ADDR 99 +#define ECC_U_ADDR_ADDR 95 +#define ECC_U_DATA_ADDR 97 +#define ECC_U_ID_ADDR 101 +#define ECC_U_SYND_ADDR 96 + +#define ECC_ERROR -1 +#define EDAC_MSG_SIZE 256 +#define EDAC_MOD_NAME "npcm7xx-edac" + +struct ecc_error_signature_info { + u32 ecc_addr; + u32 ecc_data; + u32 ecc_id; + u32 ecc_synd; +}; + +struct npcm7xx_ecc_int_status { + u32 int_mask; + u32 int_status; + u32 int_ack; + u32 ce_cnt; + u32 ue_cnt; + struct ecc_error_signature_info ceinfo; + struct ecc_error_signature_info ueinfo; +}; + +struct npcm7xx_edac_priv { + void __iomem *baseaddr; + char message[EDAC_MSG_SIZE]; + struct npcm7xx_ecc_int_status stat; +}; + +/** + * npcm7xx_edac_get_ecc_syndrom - Get the current ecc error info + * @base: Pointer to the base address of the ddr memory controller + * @p: Pointer to the Nuvoton ecc status structure + * + * Determines there is any ecc error or not + * + * Return: ECC detection status + */ +static int npcm7xx_edac_get_ecc_syndrom(void __iomem *base, + struct npcm7xx_ecc_int_status *p) +{ + int status = 0; + u32 int_status = 0; + + int_status = readl(base + 4*INT_STATUS_ADDR); + writel(int_status, base + 4*INT_ACK_ADDR); + edac_dbg(3, "int_status: %#08x\n", int_status); + + if ((int_status & (1 << 6)) == (1 << 6)) { + edac_dbg(3, "6-Mult uncorrectable detected.\n"); + p->ue_cnt++; + status = ECC_ERROR; + } + + if ((int_status & (1 << 5)) == (1 << 5)) { + edac_dbg(3, "5-An uncorrectable detected\n"); + p->ue_cnt++; + status = ECC_ERROR; + } + + if ((int_status & (1 << 4)) == (1 << 4)) { + edac_dbg(3, "4-mult correctable detected.\n"); + p->ce_cnt++; + status = ECC_ERROR; + } + + if ((int_status & (1 << 3)) == (1 << 3)) { + edac_dbg(3, "3-A correctable detected.\n"); + p->ce_cnt++; + status = ECC_ERROR; + } + + if (status == ECC_ERROR) { + u32 ecc_id; + + p->ceinfo.ecc_addr = readl(base + 4*ECC_C_ADDR_ADDR); + p->ceinfo.ecc_data = readl(base + 4*ECC_C_DATA_ADDR); + p->ceinfo.ecc_synd = readl(base + 4*ECC_C_SYND_ADDR); + + p->ueinfo.ecc_addr = readl(base + 4*ECC_U_ADDR_ADDR); + p->ueinfo.ecc_data = readl(base + 4*ECC_U_DATA_ADDR); + p->ueinfo.ecc_synd = readl(base + 4*ECC_U_SYND_ADDR); + + /* ECC_C_ID_ADDR has same value as ECC_U_ID_ADDR */ + ecc_id = readl(base + 4*ECC_C_ID_ADDR); + p->ueinfo.ecc_id = ecc_id & 0xffff; + p->ceinfo.ecc_id = ecc_id >> 16; + } + + return status; +} + +/** + * npcm7xx_edac_handle_error - Handle controller error types CE and UE + * @mci: Pointer to the edac memory controller instance + * @p: Pointer to the Nuvoton ecc status structure + * + * Handles the controller ECC correctable and un correctable error. + */ +static void npcm7xx_edac_handle_error(struct mem_ctl_info *mci, + struct npcm7xx_ecc_int_status *p) +{ + struct npcm7xx_edac_priv *priv = mci->pvt_info; + u32 page, offset; + + if (p->ce_cnt) { + snprintf(priv->message, EDAC_MSG_SIZE, + "DDR ECC: synd=%#08x addr=%#08x data=%#08x source_id=%#08x ", + p->ceinfo.ecc_synd, p->ceinfo.ecc_addr, + p->ceinfo.ecc_data, p->ceinfo.ecc_id); + + page = p->ceinfo.ecc_addr >> PAGE_SHIFT; + offset = p->ceinfo.ecc_addr & ~PAGE_MASK; + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + p->ce_cnt, page, offset, + p->ceinfo.ecc_synd, + 0, 0, -1, + priv->message, ""); + } + + if (p->ue_cnt) { + snprintf(priv->message, EDAC_MSG_SIZE, + "DDR ECC: synd=%#08x addr=%#08x data=%#08x source_id=%#08x ", + p->ueinfo.ecc_synd, p->ueinfo.ecc_addr, + p->ueinfo.ecc_data, p->ueinfo.ecc_id); + + page = p->ueinfo.ecc_addr >> PAGE_SHIFT; + offset = p->ueinfo.ecc_addr & ~PAGE_MASK; + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + p->ue_cnt, page, offset, + p->ueinfo.ecc_synd, + 0, 0, -1, + priv->message, ""); + } + + memset(p, 0, sizeof(*p)); +} + +/** + * npcm7xx_edac_check - Check controller for ECC errors + * @mci: Pointer to the edac memory controller instance + * + * This routine is used to check and post ECC errors and is called by + * this driver's CE and UE interrupt handler. + */ +static void npcm7xx_edac_check(struct mem_ctl_info *mci) +{ + struct npcm7xx_edac_priv *priv = mci->pvt_info; + int status = 0; + + status = npcm7xx_edac_get_ecc_syndrom(priv->baseaddr, &priv->stat); + if (status != ECC_ERROR) + return; + + npcm7xx_edac_handle_error(mci, &priv->stat); +} + +/** + * npcm7xx_edac_isr - CE/UE interrupt service routine + * @irq: The virtual interrupt number being serviced. + * @dev_id: A pointer to the EDAC memory controller instance + * associated with the interrupt being handled. + * + * This routine implements the interrupt handler for both correctable + * (CE) and uncorrectable (UE) ECC errors for the Nuvoton Cadence DDR + * controller. It simply calls through to the routine used to check, + * report and clear the ECC status. + * + * Unconditionally returns IRQ_HANDLED. + */ +static irqreturn_t npcm7xx_edac_isr(int irq, void *dev_id) +{ + struct mem_ctl_info *mci = dev_id; + + npcm7xx_edac_check(mci); + + return IRQ_HANDLED; +} + +static int npcm7xx_edac_register_irq(struct mem_ctl_info *mci, + struct platform_device *pdev) +{ + int status = 0; + int mc_irq; + struct npcm7xx_edac_priv *priv = mci->pvt_info; + + /* Only enable MC interrupts with ECC - clear int_mask[6:3] */ + writel(ECC_EN_INT_MASK, priv->baseaddr + 4*INT_MASK_ADDR); + + mc_irq = platform_get_irq(pdev, 0); + + if (!mc_irq) { + edac_printk(KERN_ERR, EDAC_MC, "Unable to map interrupts.\n"); + status = -ENODEV; + goto fail; + } + + status = devm_request_irq(&pdev->dev, mc_irq, npcm7xx_edac_isr, 0, + "npcm-memory-controller", mci); + + if (status < 0) { + edac_printk(KERN_ERR, EDAC_MC, + "Unable to request irq %d for ECC", + mc_irq); + status = -ENODEV; + goto fail; + } + + return 0; + +fail: + return status; +} + +static const struct of_device_id npcm7xx_edac_of_match[] = { + { .compatible = "nuvoton,npcm7xx-sdram-edac"}, + { /* end of table */ } +}; + +MODULE_DEVICE_TABLE(of, npcm7xx_edac_of_match); + +/** + * npcm7xx_edac_mc_init - Initialize driver instance + * @mci: Pointer to the edac memory controller instance + * @pdev: Pointer to the platform_device struct + * + * Performs initialization of the EDAC memory controller instance and + * related driver-private data associated with the memory controller the + * instance is bound to. + * + * Returns 0 if OK; otherwise, < 0 on error. + */ +static int npcm7xx_edac_mc_init(struct mem_ctl_info *mci, + struct platform_device *pdev) +{ + const struct of_device_id *id; + + id = of_match_device(npcm7xx_edac_of_match, &pdev->dev); + if (!id) + return -ENODEV; + + /* Initialize controller capabilities and configuration */ + mci->mtype_cap = MEM_FLAG_DDR4; + mci->edac_ctl_cap = EDAC_FLAG_SECDED; + mci->edac_cap = EDAC_FLAG_SECDED; + mci->scrub_cap = SCRUB_FLAG_HW_SRC; + mci->scrub_mode = SCRUB_HW_SRC; + mci->ctl_name = id->compatible; + mci->dev_name = dev_name(&pdev->dev); + mci->mod_name = EDAC_MOD_NAME; + + edac_op_state = EDAC_OPSTATE_INT; + + return 0; +} + +/** + * npcm7xx_edac_get_eccstate - Return the controller ecc enable/disable status + * @base: Pointer to the ddr memory controller base address + * + * Get the ECC enable/disable status for the controller + * + * Return: a ecc status boolean i.e true/false - enabled/disabled. + */ +static bool npcm7xx_edac_get_eccstate(void __iomem *base) +{ + u32 ecc_en; + bool state = false; + + ecc_en = readl(base + 4*ECC_EN_ADDR); + if (ecc_en & ECC_ENABLE) { + edac_printk(KERN_INFO, EDAC_MC, "ECC reporting and correcting on. "); + state = true; + } + + return state; +} + +/** + * npcm7xx_edac_mc_probe - Check controller and bind driver + * @pdev: Pointer to the platform_device struct + * + * Probes a specific controller instance for binding with the driver. + * + * Return: 0 if the controller instance was successfully bound to the + * driver; otherwise, < 0 on error. + */ +static int npcm7xx_edac_mc_probe(struct platform_device *pdev) +{ + struct mem_ctl_info *mci; + struct edac_mc_layer layers[1]; + struct npcm7xx_edac_priv *priv; + struct resource *res; + void __iomem *baseaddr; + int rc; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + baseaddr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(baseaddr)) { + edac_printk(KERN_ERR, EDAC_MOD_NAME, + "DDR controller regs not defined\n"); + return PTR_ERR(baseaddr); + } + + /* + * Check if ECC is enabled. + * If not, there is no useful monitoring that can be done + * for this controller. + */ + if (!npcm7xx_edac_get_eccstate(baseaddr)) { + edac_printk(KERN_INFO, EDAC_MC, "ECC disabled\n"); + return -ENXIO; + } + + /* + * Allocate an EDA controller instance and perform the appropriate + * initialization. + */ + layers[0].type = EDAC_MC_LAYER_ALL_MEM; + layers[0].size = 1; + + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, + sizeof(struct npcm7xx_edac_priv)); + if (!mci) { + edac_printk(KERN_ERR, EDAC_MC, + "Failed memory allocation for mc instance\n"); + return -ENOMEM; + } + + mci->pdev = &pdev->dev; + priv = mci->pvt_info; + priv->baseaddr = baseaddr; + platform_set_drvdata(pdev, mci); + + rc = npcm7xx_edac_mc_init(mci, pdev); + if (rc) { + edac_printk(KERN_ERR, EDAC_MC, + "Failed to initialize instance\n"); + goto free_edac_mc; + } + + /* Attempt to register it with the EDAC subsystem */ + rc = edac_mc_add_mc(mci); + if (rc) { + edac_printk(KERN_ERR, EDAC_MC, + "Failed to register with EDAC core\n"); + goto free_edac_mc; + } + + /* Register interrupts */ + rc = npcm7xx_edac_register_irq(mci, pdev); + if (rc) + goto free_edac_mc; + + return 0; + +free_edac_mc: + edac_mc_free(mci); + + return rc; +} + +/** + * npcm7xx_edac_mc_remove - Unbind driver from controller + * @pdev: Pointer to the platform_device struct + * + * Return: Unconditionally 0 + */ +static int npcm7xx_edac_mc_remove(struct platform_device *pdev) +{ + struct mem_ctl_info *mci = platform_get_drvdata(pdev); + + edac_mc_del_mc(&pdev->dev); + edac_mc_free(mci); + + return 0; +} + +static struct platform_driver npcm7xx_edac_driver = { + .probe = npcm7xx_edac_mc_probe, + .remove = npcm7xx_edac_mc_remove, + .driver = { + .name = EDAC_MOD_NAME, + .of_match_table = npcm7xx_edac_of_match, + }, +}; + +module_platform_driver(npcm7xx_edac_driver); + +MODULE_AUTHOR("Quanta Computer Inc."); +MODULE_DESCRIPTION("Nuvoton NPCM7xx EDAC Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c index 8244da8a7241..4e60e84cd17a 100644 --- a/drivers/fsi/fsi-core.c +++ b/drivers/fsi/fsi-core.c @@ -50,6 +50,7 @@ static const int engine_page_size = 0x400; #define FSI_SMODE 0x0 /* R/W: Mode register */ #define FSI_SISC 0x8 /* R/W: Interrupt condition */ #define FSI_SSTAT 0x14 /* R : Slave status */ +#define FSI_SLBUS 0x30 /* W : LBUS Ownership */ #define FSI_LLMODE 0x100 /* R/W: Link layer mode register */ /* @@ -67,6 +68,11 @@ static const int engine_page_size = 0x400; #define FSI_SMODE_LBCRR_MASK 0xf /* Clk ratio mask */ /* + * SLBUS fields + */ +#define FSI_SLBUS_FORCE 0x80000000 /* Force LBUS ownership */ + +/* * LLMODE fields */ #define FSI_LLMODE_ASYNC 0x1 @@ -981,7 +987,7 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) uint32_t cfam_id; struct fsi_slave *slave; uint8_t crc; - __be32 data, llmode; + __be32 data, llmode, slbus; int rc; /* Currently, we only support single slaves on a link, and use the @@ -1052,6 +1058,14 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) } + slbus = cpu_to_be32(FSI_SLBUS_FORCE); + rc = fsi_master_write(master, link, id, FSI_SLAVE_BASE + FSI_SLBUS, + &slbus, sizeof(slbus)); + if (rc) + dev_warn(&master->dev, + "can't set slbus on slave:%02x:%02x %d\n", link, id, + rc); + rc = fsi_slave_set_smode(slave); if (rc) { dev_warn(&master->dev, @@ -1154,10 +1168,18 @@ static int fsi_master_write(struct fsi_master *master, int link, return rc; } +static int fsi_master_link_disable(struct fsi_master *master, int link) +{ + if (master->link_enable) + return master->link_enable(master, link, false); + + return 0; +} + static int fsi_master_link_enable(struct fsi_master *master, int link) { if (master->link_enable) - return master->link_enable(master, link); + return master->link_enable(master, link, true); return 0; } @@ -1192,12 +1214,15 @@ static int fsi_master_scan(struct fsi_master *master) } rc = fsi_master_break(master, link); if (rc) { + fsi_master_link_disable(master, link); dev_dbg(&master->dev, "break to link %d failed: %d\n", link, rc); continue; } - fsi_slave_init(master, link, 0); + rc = fsi_slave_init(master, link, 0); + if (rc) + fsi_master_link_disable(master, link); } return 0; diff --git a/drivers/fsi/fsi-master-aspeed.c b/drivers/fsi/fsi-master-aspeed.c index f49742b310c2..c006ec008a1a 100644 --- a/drivers/fsi/fsi-master-aspeed.c +++ b/drivers/fsi/fsi-master-aspeed.c @@ -13,6 +13,7 @@ #include <linux/regmap.h> #include <linux/slab.h> #include <linux/iopoll.h> +#include <linux/gpio/consumer.h> #include "fsi-master.h" @@ -21,6 +22,7 @@ struct fsi_master_aspeed { struct device *dev; void __iomem *base; struct clk *clk; + struct gpio_desc *cfam_reset_gpio; }; #define to_fsi_master_aspeed(m) \ @@ -82,7 +84,12 @@ static const u32 fsi_base = 0xa0000000; #define FSI_LINK_ENABLE_SETUP_TIME 10 /* in mS */ -#define DEFAULT_DIVISOR 14 +/* Run the bus at maximum speed by default */ +#define FSI_DIVISOR_DEFAULT 1 +#define FSI_DIVISOR_CABLED 2 +static u16 aspeed_fsi_divisor = FSI_DIVISOR_DEFAULT; +module_param_named(bus_div,aspeed_fsi_divisor, ushort, 0); + #define OPB_POLL_TIMEOUT 10000 static int __opb_write(struct fsi_master_aspeed *aspeed, u32 addr, @@ -241,9 +248,10 @@ static int aspeed_master_read(struct fsi_master *master, int link, struct fsi_master_aspeed *aspeed = to_fsi_master_aspeed(master); int ret; - if (id != 0) + if (id > 0x3) return -EINVAL; + addr |= id << 21; addr += link * FSI_HUB_LINK_SIZE; switch (size) { @@ -273,9 +281,10 @@ static int aspeed_master_write(struct fsi_master *master, int link, struct fsi_master_aspeed *aspeed = to_fsi_master_aspeed(master); int ret; - if (id != 0) + if (id > 0x3) return -EINVAL; + addr |= id << 21; addr += link * FSI_HUB_LINK_SIZE; switch (size) { @@ -299,32 +308,28 @@ static int aspeed_master_write(struct fsi_master *master, int link, return 0; } -static int aspeed_master_link_enable(struct fsi_master *master, int link) +static int aspeed_master_link_enable(struct fsi_master *master, int link, + bool enable) { struct fsi_master_aspeed *aspeed = to_fsi_master_aspeed(master); int idx, bit, ret; - __be32 reg, result; + __be32 reg; idx = link / 32; bit = link % 32; reg = cpu_to_be32(0x80000000 >> bit); + if (!enable) + return opb_writel(aspeed, ctrl_base + FSI_MCENP0 + (4 * idx), + reg); + ret = opb_writel(aspeed, ctrl_base + FSI_MSENP0 + (4 * idx), reg); if (ret) return ret; mdelay(FSI_LINK_ENABLE_SETUP_TIME); - ret = opb_readl(aspeed, ctrl_base + FSI_MENP0 + (4 * idx), &result); - if (ret) - return ret; - - if (result != reg) { - dev_err(aspeed->dev, "%s failed: %08x\n", __func__, result); - return -EIO; - } - return 0; } @@ -386,9 +391,11 @@ static int aspeed_master_init(struct fsi_master_aspeed *aspeed) opb_writel(aspeed, ctrl_base + FSI_MECTRL, reg); reg = cpu_to_be32(FSI_MMODE_ECRC | FSI_MMODE_EPC | FSI_MMODE_RELA - | fsi_mmode_crs0(DEFAULT_DIVISOR) - | fsi_mmode_crs1(DEFAULT_DIVISOR) + | fsi_mmode_crs0(aspeed_fsi_divisor) + | fsi_mmode_crs1(aspeed_fsi_divisor) | FSI_MMODE_P8_TO_LSB); + dev_info(aspeed->dev, "mmode set to %08x (divisor %d)\n", + be32_to_cpu(reg), aspeed_fsi_divisor); opb_writel(aspeed, ctrl_base + FSI_MMODE, reg); reg = cpu_to_be32(0xffff0000); @@ -419,6 +426,90 @@ static int aspeed_master_init(struct fsi_master_aspeed *aspeed) return 0; } +static ssize_t cfam_reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fsi_master_aspeed *aspeed = dev_get_drvdata(dev); + + gpiod_set_value(aspeed->cfam_reset_gpio, 1); + usleep_range(900, 1000); + gpiod_set_value(aspeed->cfam_reset_gpio, 0); + + return count; +} + +static DEVICE_ATTR(cfam_reset, 0200, NULL, cfam_reset_store); + +static int setup_cfam_reset(struct fsi_master_aspeed *aspeed) +{ + struct device *dev = aspeed->dev; + struct gpio_desc *gpio; + int rc; + + gpio = devm_gpiod_get_optional(dev, "cfam-reset", GPIOD_OUT_LOW); + if (IS_ERR(gpio)) + return PTR_ERR(gpio); + if (!gpio) + return 0; + + aspeed->cfam_reset_gpio = gpio; + + rc = device_create_file(dev, &dev_attr_cfam_reset); + if (rc) { + devm_gpiod_put(dev, gpio); + return rc; + } + + return 0; +} + +static int tacoma_cabled_fsi_fixup(struct device *dev) +{ + struct gpio_desc *routing_gpio, *mux_gpio; + int gpio; + + /* + * The routing GPIO is a jumper indicating we should mux for the + * externally connected FSI cable. + */ + routing_gpio = devm_gpiod_get_optional(dev, "fsi-routing", + GPIOD_IN | GPIOD_FLAGS_BIT_NONEXCLUSIVE); + if (IS_ERR(routing_gpio)) + return PTR_ERR(routing_gpio); + if (!routing_gpio) + return 0; + + mux_gpio = devm_gpiod_get_optional(dev, "fsi-mux", GPIOD_ASIS); + if (IS_ERR(mux_gpio)) + return PTR_ERR(mux_gpio); + if (!mux_gpio) + return 0; + + gpio = gpiod_get_value(routing_gpio); + if (gpio < 0) + return gpio; + + /* If the routing GPIO is high we should set the mux to low. */ + if (gpio) { + /* + * Cable signal integrity means we should run the bus + * slightly slower. Do not override if a kernel param + * has already overridden. + */ + if (aspeed_fsi_divisor == FSI_DIVISOR_DEFAULT) + aspeed_fsi_divisor = FSI_DIVISOR_CABLED; + + gpiod_direction_output(mux_gpio, 0); + dev_info(dev, "FSI configured for external cable\n"); + } else { + gpiod_direction_output(mux_gpio, 1); + } + + devm_gpiod_put(dev, routing_gpio); + + return 0; +} + static int fsi_master_aspeed_probe(struct platform_device *pdev) { struct fsi_master_aspeed *aspeed; @@ -426,6 +517,12 @@ static int fsi_master_aspeed_probe(struct platform_device *pdev) int rc, links, reg; __be32 raw; + rc = tacoma_cabled_fsi_fixup(&pdev->dev); + if (rc) { + dev_err(&pdev->dev, "Tacoma FSI cable fixup failed\n"); + return rc; + } + aspeed = devm_kzalloc(&pdev->dev, sizeof(*aspeed), GFP_KERNEL); if (!aspeed) return -ENOMEM; @@ -448,6 +545,11 @@ static int fsi_master_aspeed_probe(struct platform_device *pdev) return rc; } + rc = setup_cfam_reset(aspeed); + if (rc) { + dev_err(&pdev->dev, "CFAM reset GPIO setup failed\n"); + } + writel(0x1, aspeed->base + OPB_CLK_SYNC); writel(OPB1_XFER_ACK_EN | OPB0_XFER_ACK_EN, aspeed->base + OPB_IRQ_MASK); diff --git a/drivers/fsi/fsi-master-ast-cf.c b/drivers/fsi/fsi-master-ast-cf.c index 04d10ea8d343..57a779a89b07 100644 --- a/drivers/fsi/fsi-master-ast-cf.c +++ b/drivers/fsi/fsi-master-ast-cf.c @@ -838,7 +838,7 @@ static int load_copro_firmware(struct fsi_master_acf *master) rc = request_firmware(&fw, FW_FILE_NAME, master->dev); if (rc) { dev_err( - master->dev, "Error %d to load firwmare '%s' !\n", + master->dev, "Error %d to load firmware '%s' !\n", rc, FW_FILE_NAME); return rc; } @@ -1039,7 +1039,8 @@ static void fsi_master_acf_setup_external(struct fsi_master_acf *master) gpiod_direction_input(master->gpio_data); } -static int fsi_master_acf_link_enable(struct fsi_master *_master, int link) +static int fsi_master_acf_link_enable(struct fsi_master *_master, int link, + bool enable) { struct fsi_master_acf *master = to_fsi_master_acf(_master); int rc = -EBUSY; @@ -1049,7 +1050,7 @@ static int fsi_master_acf_link_enable(struct fsi_master *_master, int link) mutex_lock(&master->lock); if (!master->external_mode) { - gpiod_set_value(master->gpio_enable, 1); + gpiod_set_value(master->gpio_enable, enable ? 1 : 0); rc = 0; } mutex_unlock(&master->lock); diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index 4dcce17f243f..aa97c4a250cb 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -678,7 +678,8 @@ static void fsi_master_gpio_init_external(struct fsi_master_gpio *master) gpiod_direction_input(master->gpio_data); } -static int fsi_master_gpio_link_enable(struct fsi_master *_master, int link) +static int fsi_master_gpio_link_enable(struct fsi_master *_master, int link, + bool enable) { struct fsi_master_gpio *master = to_fsi_master_gpio(_master); int rc = -EBUSY; @@ -688,7 +689,7 @@ static int fsi_master_gpio_link_enable(struct fsi_master *_master, int link) mutex_lock(&master->cmd_lock); if (!master->external_mode) { - gpiod_set_value(master->gpio_enable, 1); + gpiod_set_value(master->gpio_enable, enable ? 1 : 0); rc = 0; } mutex_unlock(&master->cmd_lock); diff --git a/drivers/fsi/fsi-master-hub.c b/drivers/fsi/fsi-master-hub.c index def35cf92571..01f0a796111e 100644 --- a/drivers/fsi/fsi-master-hub.c +++ b/drivers/fsi/fsi-master-hub.c @@ -77,7 +77,8 @@ static int hub_master_break(struct fsi_master *master, int link) return hub_master_write(master, link, 0, addr, &cmd, sizeof(cmd)); } -static int hub_master_link_enable(struct fsi_master *master, int link) +static int hub_master_link_enable(struct fsi_master *master, int link, + bool enable) { struct fsi_master_hub *hub = to_fsi_master_hub(master); int idx, bit; @@ -89,13 +90,17 @@ static int hub_master_link_enable(struct fsi_master *master, int link) reg = cpu_to_be32(0x80000000 >> bit); + if (!enable) + return fsi_device_write(hub->upstream, FSI_MCENP0 + (4 * idx), + ®, 4); + rc = fsi_device_write(hub->upstream, FSI_MSENP0 + (4 * idx), ®, 4); + if (rc) + return rc; mdelay(FSI_LINK_ENABLE_SETUP_TIME); - fsi_device_read(hub->upstream, FSI_MENP0 + (4 * idx), ®, 4); - - return rc; + return 0; } static void hub_master_release(struct device *dev) @@ -271,7 +276,7 @@ static int hub_master_remove(struct device *dev) return 0; } -static struct fsi_device_id hub_master_ids[] = { +static const struct fsi_device_id hub_master_ids[] = { { .engine_type = FSI_ENGID_HUB_MASTER, .version = FSI_VERSION_ANY, diff --git a/drivers/fsi/fsi-master.h b/drivers/fsi/fsi-master.h index 6e8d4d4d5149..cd6bee5e12a7 100644 --- a/drivers/fsi/fsi-master.h +++ b/drivers/fsi/fsi-master.h @@ -130,7 +130,8 @@ struct fsi_master { uint32_t addr, const void *val, size_t size); int (*term)(struct fsi_master *, int link, uint8_t id); int (*send_break)(struct fsi_master *, int link); - int (*link_enable)(struct fsi_master *, int link); + int (*link_enable)(struct fsi_master *, int link, + bool enable); int (*link_config)(struct fsi_master *, int link, u8 t_send_delay, u8 t_echo_delay); }; diff --git a/drivers/fsi/fsi-occ.c b/drivers/fsi/fsi-occ.c index 7da9c81759ac..d92462222b68 100644 --- a/drivers/fsi/fsi-occ.c +++ b/drivers/fsi/fsi-occ.c @@ -14,6 +14,7 @@ #include <linux/mutex.h> #include <linux/fsi-occ.h> #include <linux/of.h> +#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/sched.h> #include <linux/slab.h> @@ -24,8 +25,13 @@ #define OCC_CMD_DATA_BYTES 4090 #define OCC_RESP_DATA_BYTES 4089 -#define OCC_SRAM_CMD_ADDR 0xFFFBE000 -#define OCC_SRAM_RSP_ADDR 0xFFFBF000 +#define OCC_P9_SRAM_CMD_ADDR 0xFFFBE000 +#define OCC_P9_SRAM_RSP_ADDR 0xFFFBF000 + +#define OCC_P10_SRAM_CMD_ADDR 0xFFFFD000 +#define OCC_P10_SRAM_RSP_ADDR 0xFFFFE000 + +#define OCC_P10_SRAM_MODE 0x58 /* Normal mode, OCB channel 2 */ /* * Assume we don't have much FFDC, if we do we'll overflow and @@ -37,11 +43,14 @@ #define OCC_TIMEOUT_MS 1000 #define OCC_CMD_IN_PRG_WAIT_MS 50 +enum versions { occ_p9, occ_p10 }; + struct occ { struct device *dev; struct device *sbefifo; char name[32]; int idx; + enum versions version; struct miscdevice mdev; struct mutex occ_lock; }; @@ -235,29 +244,43 @@ static int occ_verify_checksum(struct occ_response *resp, u16 data_length) return 0; } -static int occ_getsram(struct occ *occ, u32 address, void *data, ssize_t len) +static int occ_getsram(struct occ *occ, u32 offset, void *data, ssize_t len) { u32 data_len = ((len + 7) / 8) * 8; /* must be multiples of 8 B */ - size_t resp_len, resp_data_len; - __be32 *resp, cmd[5]; - int rc; + size_t cmd_len, resp_len, resp_data_len; + __be32 *resp, cmd[6]; + int idx = 0, rc; /* * Magic sequence to do SBE getsram command. SBE will fetch data from * specified SRAM address. */ - cmd[0] = cpu_to_be32(0x5); + switch (occ->version) { + default: + case occ_p9: + cmd_len = 5; + cmd[2] = cpu_to_be32(1); /* Normal mode */ + cmd[3] = cpu_to_be32(OCC_P9_SRAM_RSP_ADDR + offset); + break; + case occ_p10: + idx = 1; + cmd_len = 6; + cmd[2] = cpu_to_be32(OCC_P10_SRAM_MODE); + cmd[3] = 0; + cmd[4] = cpu_to_be32(OCC_P10_SRAM_RSP_ADDR + offset); + break; + } + + cmd[0] = cpu_to_be32(cmd_len); cmd[1] = cpu_to_be32(SBEFIFO_CMD_GET_OCC_SRAM); - cmd[2] = cpu_to_be32(1); - cmd[3] = cpu_to_be32(address); - cmd[4] = cpu_to_be32(data_len); + cmd[4 + idx] = cpu_to_be32(data_len); resp_len = (data_len >> 2) + OCC_SBE_STATUS_WORDS; resp = kzalloc(resp_len << 2, GFP_KERNEL); if (!resp) return -ENOMEM; - rc = sbefifo_submit(occ->sbefifo, cmd, 5, resp, &resp_len); + rc = sbefifo_submit(occ->sbefifo, cmd, cmd_len, resp, &resp_len); if (rc) goto free; @@ -287,20 +310,21 @@ free: return rc; } -static int occ_putsram(struct occ *occ, u32 address, const void *data, - ssize_t len) +static int occ_putsram(struct occ *occ, const void *data, ssize_t len) { size_t cmd_len, buf_len, resp_len, resp_data_len; u32 data_len = ((len + 7) / 8) * 8; /* must be multiples of 8 B */ __be32 *buf; - int rc; + int idx = 0, rc; + + cmd_len = (occ->version == occ_p10) ? 6 : 5; /* * We use the same buffer for command and response, make * sure it's big enough */ resp_len = OCC_SBE_STATUS_WORDS; - cmd_len = (data_len >> 2) + 5; + cmd_len += data_len >> 2; buf_len = max(cmd_len, resp_len); buf = kzalloc(buf_len << 2, GFP_KERNEL); if (!buf) @@ -312,11 +336,23 @@ static int occ_putsram(struct occ *occ, u32 address, const void *data, */ buf[0] = cpu_to_be32(cmd_len); buf[1] = cpu_to_be32(SBEFIFO_CMD_PUT_OCC_SRAM); - buf[2] = cpu_to_be32(1); - buf[3] = cpu_to_be32(address); - buf[4] = cpu_to_be32(data_len); - memcpy(&buf[5], data, len); + switch (occ->version) { + default: + case occ_p9: + buf[2] = cpu_to_be32(1); /* Normal mode */ + buf[3] = cpu_to_be32(OCC_P9_SRAM_CMD_ADDR); + break; + case occ_p10: + idx = 1; + buf[2] = cpu_to_be32(OCC_P10_SRAM_MODE); + buf[3] = 0; + buf[4] = cpu_to_be32(OCC_P10_SRAM_CMD_ADDR); + break; + } + + buf[4 + idx] = cpu_to_be32(data_len); + memcpy(&buf[5 + idx], data, len); rc = sbefifo_submit(occ->sbefifo, buf, cmd_len, buf, &resp_len); if (rc) @@ -356,21 +392,35 @@ free: static int occ_trigger_attn(struct occ *occ) { __be32 buf[OCC_SBE_STATUS_WORDS]; - size_t resp_len, resp_data_len; - int rc; + size_t cmd_len, resp_len, resp_data_len; + int idx = 0, rc; - BUILD_BUG_ON(OCC_SBE_STATUS_WORDS < 7); + BUILD_BUG_ON(OCC_SBE_STATUS_WORDS < 8); resp_len = OCC_SBE_STATUS_WORDS; - buf[0] = cpu_to_be32(0x5 + 0x2); /* Chip-op length in words */ + switch (occ->version) { + default: + case occ_p9: + cmd_len = 7; + buf[2] = cpu_to_be32(3); /* Circular mode */ + buf[3] = 0; + break; + case occ_p10: + idx = 1; + cmd_len = 8; + buf[2] = cpu_to_be32(0xd0); /* Circular mode, OCB Channel 1 */ + buf[3] = 0; + buf[4] = 0; + break; + } + + buf[0] = cpu_to_be32(cmd_len); /* Chip-op length in words */ buf[1] = cpu_to_be32(SBEFIFO_CMD_PUT_OCC_SRAM); - buf[2] = cpu_to_be32(0x3); /* Mode: Circular */ - buf[3] = cpu_to_be32(0x0); /* Address: ignore in mode 3 */ - buf[4] = cpu_to_be32(0x8); /* Data length in bytes */ - buf[5] = cpu_to_be32(0x20010000); /* Trigger OCC attention */ - buf[6] = 0; + buf[4 + idx] = cpu_to_be32(8); /* Data length in bytes */ + buf[5 + idx] = cpu_to_be32(0x20010000); /* Trigger OCC attention */ + buf[6 + idx] = 0; - rc = sbefifo_submit(occ->sbefifo, buf, 7, buf, &resp_len); + rc = sbefifo_submit(occ->sbefifo, buf, cmd_len, buf, &resp_len); if (rc) goto error; @@ -429,7 +479,7 @@ int fsi_occ_submit(struct device *dev, const void *request, size_t req_len, /* Extract the seq_no from the command (first byte) */ seq_no = *(const u8 *)request; - rc = occ_putsram(occ, OCC_SRAM_CMD_ADDR, request, req_len); + rc = occ_putsram(occ, request, req_len); if (rc) goto done; @@ -440,7 +490,7 @@ int fsi_occ_submit(struct device *dev, const void *request, size_t req_len, /* Read occ response header */ start = jiffies; do { - rc = occ_getsram(occ, OCC_SRAM_RSP_ADDR, resp, 8); + rc = occ_getsram(occ, 0, resp, 8); if (rc) goto done; @@ -476,8 +526,7 @@ int fsi_occ_submit(struct device *dev, const void *request, size_t req_len, /* Grab the rest */ if (resp_data_length > 1) { /* already got 3 bytes resp, also need 2 bytes checksum */ - rc = occ_getsram(occ, OCC_SRAM_RSP_ADDR + 8, - &resp->data[3], resp_data_length - 1); + rc = occ_getsram(occ, 8, &resp->data[3], resp_data_length - 1); if (rc) goto done; } @@ -508,6 +557,7 @@ static int occ_probe(struct platform_device *pdev) struct occ *occ; struct platform_device *hwmon_dev; struct device *dev = &pdev->dev; + const void *md = of_device_get_match_data(dev); struct platform_device_info hwmon_dev_info = { .parent = dev, .name = "occ-hwmon", @@ -517,6 +567,7 @@ static int occ_probe(struct platform_device *pdev) if (!occ) return -ENOMEM; + occ->version = (enum versions)md; occ->dev = dev; occ->sbefifo = dev->parent; mutex_init(&occ->occ_lock); @@ -555,7 +606,7 @@ static int occ_probe(struct platform_device *pdev) hwmon_dev_info.id = occ->idx; hwmon_dev = platform_device_register_full(&hwmon_dev_info); - if (!hwmon_dev) + if (IS_ERR(hwmon_dev)) dev_warn(dev, "failed to create hwmon device\n"); return 0; @@ -575,7 +626,14 @@ static int occ_remove(struct platform_device *pdev) } static const struct of_device_id occ_match[] = { - { .compatible = "ibm,p9-occ" }, + { + .compatible = "ibm,p9-occ", + .data = (void *)occ_p9 + }, + { + .compatible = "ibm,p10-occ", + .data = (void *)occ_p10 + }, { }, }; diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c index f54df9ebc8b3..84cb965bfed5 100644 --- a/drivers/fsi/fsi-sbefifo.c +++ b/drivers/fsi/fsi-sbefifo.c @@ -325,7 +325,8 @@ static int sbefifo_up_write(struct sbefifo *sbefifo, __be32 word) static int sbefifo_request_reset(struct sbefifo *sbefifo) { struct device *dev = &sbefifo->fsi_dev->dev; - u32 status, timeout; + unsigned long end_time; + u32 status; int rc; dev_dbg(dev, "Requesting FIFO reset\n"); @@ -341,7 +342,8 @@ static int sbefifo_request_reset(struct sbefifo *sbefifo) } /* Wait for it to complete */ - for (timeout = 0; timeout < SBEFIFO_RESET_TIMEOUT; timeout++) { + end_time = jiffies + msecs_to_jiffies(SBEFIFO_RESET_TIMEOUT); + while (!time_after(jiffies, end_time)) { rc = sbefifo_regr(sbefifo, SBEFIFO_UP | SBEFIFO_STS, &status); if (rc) { dev_err(dev, "Failed to read UP fifo status during reset" @@ -355,7 +357,7 @@ static int sbefifo_request_reset(struct sbefifo *sbefifo) return 0; } - msleep(1); + cond_resched(); } dev_err(dev, "FIFO reset timed out\n"); @@ -400,7 +402,7 @@ static int sbefifo_cleanup_hw(struct sbefifo *sbefifo) /* The FIFO already contains a reset request from the SBE ? */ if (down_status & SBEFIFO_STS_RESET_REQ) { dev_info(dev, "Cleanup: FIFO reset request set, resetting\n"); - rc = sbefifo_regw(sbefifo, SBEFIFO_UP, SBEFIFO_PERFORM_RESET); + rc = sbefifo_regw(sbefifo, SBEFIFO_DOWN, SBEFIFO_PERFORM_RESET); if (rc) { sbefifo->broken = true; dev_err(dev, "Cleanup: Reset reg write failed, rc=%d\n", rc); @@ -1028,7 +1030,7 @@ static int sbefifo_remove(struct device *dev) return 0; } -static struct fsi_device_id sbefifo_ids[] = { +static const struct fsi_device_id sbefifo_ids[] = { { .engine_type = FSI_ENGID_SBE, .version = FSI_VERSION_ANY, diff --git a/drivers/fsi/fsi-scom.c b/drivers/fsi/fsi-scom.c index 004dc03ccf09..b45bfab7b7f5 100644 --- a/drivers/fsi/fsi-scom.c +++ b/drivers/fsi/fsi-scom.c @@ -627,7 +627,7 @@ static int scom_remove(struct device *dev) return 0; } -static struct fsi_device_id scom_ids[] = { +static const struct fsi_device_id scom_ids[] = { { .engine_type = FSI_ENGID_SCOM, .version = FSI_VERSION_ANY, diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 288ae9f63588..9aa89d7d4193 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1435,6 +1435,34 @@ config SENSORS_PCF8591 These devices are hard to detect and rarely found on mainstream hardware. If unsure, say N. +config SENSORS_PECI_CPUTEMP + tristate "PECI CPU temperature monitoring client" + depends on PECI + select MFD_INTEL_PECI_CLIENT + help + If you say yes here you get support for the generic Intel PECI + cputemp driver which provides Digital Thermal Sensor (DTS) thermal + readings of the CPU package and CPU cores that are accessible using + the PECI Client Command Suite via the processor PECI client. + Check <file:Documentation/hwmon/peci-cputemp.rst> for details. + + This driver can also be built as a module. If so, the module + will be called peci-cputemp. + +config SENSORS_PECI_DIMMTEMP + tristate "PECI DIMM temperature monitoring client" + depends on PECI + select MFD_INTEL_PECI_CLIENT + help + If you say yes here you get support for the generic Intel PECI hwmon + driver which provides Digital Thermal Sensor (DTS) thermal readings of + DIMM components that are accessible using the PECI Client Command + Suite via the processor PECI client. + Check <file:Documentation/hwmon/peci-dimmtemp.rst> for details. + + This driver can also be built as a module. If so, the module + will be called peci-dimmtemp. + source "drivers/hwmon/pmbus/Kconfig" config SENSORS_PWM_FAN diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 3e32c21f5efe..ae41ee71a71b 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -151,6 +151,8 @@ obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o obj-$(CONFIG_SENSORS_PC87427) += pc87427.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o +obj-$(CONFIG_SENSORS_PECI_CPUTEMP) += peci-cputemp.o +obj-$(CONFIG_SENSORS_PECI_DIMMTEMP) += peci-dimmtemp.o obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index 30e18eb60da7..3e580a83ae61 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -41,6 +41,14 @@ struct temp_sensor_2 { u8 value; } __packed; +struct temp_sensor_10 { + u32 sensor_id; + u8 fru_type; + u8 value; + u8 throttle; + u8 reserved; +} __packed; + struct freq_sensor_1 { u16 sensor_id; u16 value; @@ -307,6 +315,60 @@ static ssize_t occ_show_temp_2(struct device *dev, return snprintf(buf, PAGE_SIZE - 1, "%u\n", val); } +static ssize_t occ_show_temp_10(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int rc; + u32 val = 0; + struct temp_sensor_10 *temp; + struct occ *occ = dev_get_drvdata(dev); + struct occ_sensors *sensors = &occ->sensors; + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + rc = occ_update_response(occ); + if (rc) + return rc; + + temp = ((struct temp_sensor_10 *)sensors->temp.data) + sattr->index; + + switch (sattr->nr) { + case 0: + val = get_unaligned_be32(&temp->sensor_id); + break; + case 1: + val = temp->value; + if (val == OCC_TEMP_SENSOR_FAULT) + return -EREMOTEIO; + + /* + * VRM doesn't return temperature, only alarm bit. This + * attribute maps to tempX_alarm instead of tempX_input for + * VRM + */ + if (temp->fru_type != OCC_FRU_TYPE_VRM) { + /* sensor not ready */ + if (val == 0) + return -EAGAIN; + + val *= 1000; + } + break; + case 2: + val = temp->fru_type; + break; + case 3: + val = temp->value == OCC_TEMP_SENSOR_FAULT; + break; + case 4: + val = temp->throttle * 1000; + break; + default: + return -EINVAL; + } + + return snprintf(buf, PAGE_SIZE - 1, "%u\n", val); +} + static ssize_t occ_show_freq_1(struct device *dev, struct device_attribute *attr, char *buf) { @@ -745,6 +807,9 @@ static int occ_setup_sensor_attrs(struct occ *occ) num_attrs += (sensors->temp.num_sensors * 4); show_temp = occ_show_temp_2; break; + case 0x10: + show_temp = occ_show_temp_10; + break; default: sensors->temp.num_sensors = 0; } diff --git a/drivers/hwmon/peci-cputemp.c b/drivers/hwmon/peci-cputemp.c new file mode 100644 index 000000000000..b9fe91281d58 --- /dev/null +++ b/drivers/hwmon/peci-cputemp.c @@ -0,0 +1,472 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018-2019 Intel Corporation + +#include <linux/hwmon.h> +#include <linux/jiffies.h> +#include <linux/mfd/intel-peci-client.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include "peci-hwmon.h" + +#define DEFAULT_CHANNEL_NUMS 5 +#define CORETEMP_CHANNEL_NUMS CORE_NUMS_MAX +#define CPUTEMP_CHANNEL_NUMS (DEFAULT_CHANNEL_NUMS + CORETEMP_CHANNEL_NUMS) + +struct temp_group { + struct peci_sensor_data die; + struct peci_sensor_data dts; + struct peci_sensor_data tcontrol; + struct peci_sensor_data tthrottle; + struct peci_sensor_data tjmax; + struct peci_sensor_data core[CORETEMP_CHANNEL_NUMS]; +}; + +struct peci_cputemp { + struct peci_client_manager *mgr; + struct device *dev; + char name[PECI_NAME_SIZE]; + const struct cpu_gen_info *gen_info; + struct temp_group temp; + u64 core_mask; + u32 temp_config[CPUTEMP_CHANNEL_NUMS + 1]; + uint config_idx; + struct hwmon_channel_info temp_info; + const struct hwmon_channel_info *info[2]; + struct hwmon_chip_info chip; + char **coretemp_label; +}; + +enum cputemp_channels { + channel_die, + channel_dts, + channel_tcontrol, + channel_tthrottle, + channel_tjmax, + channel_core, +}; + +static const u32 config_table[DEFAULT_CHANNEL_NUMS + 1] = { + /* Die temperature */ + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_CRIT_HYST, + + /* DTS margin */ + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_CRIT_HYST, + + /* Tcontrol temperature */ + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_CRIT, + + /* Tthrottle temperature */ + HWMON_T_LABEL | HWMON_T_INPUT, + + /* Tjmax temperature */ + HWMON_T_LABEL | HWMON_T_INPUT, + + /* Core temperature - for all core channels */ + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_CRIT_HYST, +}; + +static const char *cputemp_label[DEFAULT_CHANNEL_NUMS] = { + "Die", + "DTS", + "Tcontrol", + "Tthrottle", + "Tjmax" +}; + +static s32 ten_dot_six_to_millidegree(s32 val) +{ + return ((val ^ 0x8000) - 0x8000) * 1000 / 64; +} + +static int get_temp_targets(struct peci_cputemp *priv) +{ + s32 tthrottle_offset; + s32 tcontrol_margin; + u8 pkg_cfg[4]; + int ret; + + /* + * Just use only the tcontrol marker to determine if target values need + * update. + */ + if (!peci_sensor_need_update(&priv->temp.tcontrol)) + return 0; + + ret = peci_client_read_package_config(priv->mgr, + PECI_MBX_INDEX_TEMP_TARGET, 0, + pkg_cfg); + if (ret) + return ret; + + priv->temp.tjmax.value = pkg_cfg[2] * 1000; + + tcontrol_margin = pkg_cfg[1]; + tcontrol_margin = ((tcontrol_margin ^ 0x80) - 0x80) * 1000; + priv->temp.tcontrol.value = priv->temp.tjmax.value - tcontrol_margin; + + tthrottle_offset = (pkg_cfg[3] & 0x2f) * 1000; + priv->temp.tthrottle.value = priv->temp.tjmax.value - tthrottle_offset; + + peci_sensor_mark_updated(&priv->temp.tcontrol); + + return 0; +} + +static int get_die_temp(struct peci_cputemp *priv) +{ + struct peci_get_temp_msg msg; + int ret; + + if (!peci_sensor_need_update(&priv->temp.die)) + return 0; + + msg.addr = priv->mgr->client->addr; + + ret = peci_command(priv->mgr->client->adapter, PECI_CMD_GET_TEMP, &msg); + if (ret) + return ret; + + /* Note that the tjmax should be available before calling it */ + priv->temp.die.value = priv->temp.tjmax.value + + (msg.temp_raw * 1000 / 64); + + peci_sensor_mark_updated(&priv->temp.die); + + return 0; +} + +static int get_dts(struct peci_cputemp *priv) +{ + s32 dts_margin; + u8 pkg_cfg[4]; + int ret; + + if (!peci_sensor_need_update(&priv->temp.dts)) + return 0; + + ret = peci_client_read_package_config(priv->mgr, + PECI_MBX_INDEX_DTS_MARGIN, 0, + pkg_cfg); + + if (ret) + return ret; + + dts_margin = le16_to_cpup((__le16 *)pkg_cfg); + + /** + * Processors return a value of DTS reading in 10.6 format + * (10 bits signed decimal, 6 bits fractional). + * Error codes: + * 0x8000: General sensor error + * 0x8001: Reserved + * 0x8002: Underflow on reading value + * 0x8003-0x81ff: Reserved + */ + if (dts_margin >= 0x8000 && dts_margin <= 0x81ff) + return -EIO; + + dts_margin = ten_dot_six_to_millidegree(dts_margin); + + /* Note that the tcontrol should be available before calling it */ + priv->temp.dts.value = priv->temp.tcontrol.value - dts_margin; + + peci_sensor_mark_updated(&priv->temp.dts); + + return 0; +} + +static int get_core_temp(struct peci_cputemp *priv, int core_index) +{ + s32 core_dts_margin; + u8 pkg_cfg[4]; + int ret; + + if (!peci_sensor_need_update(&priv->temp.core[core_index])) + return 0; + + ret = peci_client_read_package_config(priv->mgr, + PECI_MBX_INDEX_PER_CORE_DTS_TEMP, + core_index, pkg_cfg); + if (ret) + return ret; + + core_dts_margin = le16_to_cpup((__le16 *)pkg_cfg); + + /* + * Processors return a value of the core DTS reading in 10.6 format + * (10 bits signed decimal, 6 bits fractional). + * Error codes: + * 0x8000: General sensor error + * 0x8001: Reserved + * 0x8002: Underflow on reading value + * 0x8003-0x81ff: Reserved + */ + if (core_dts_margin >= 0x8000 && core_dts_margin <= 0x81ff) + return -EIO; + + core_dts_margin = ten_dot_six_to_millidegree(core_dts_margin); + + /* Note that the tjmax should be available before calling it */ + priv->temp.core[core_index].value = priv->temp.tjmax.value + + core_dts_margin; + + peci_sensor_mark_updated(&priv->temp.core[core_index]); + + return 0; +} + +static int cputemp_read_string(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + struct peci_cputemp *priv = dev_get_drvdata(dev); + + if (attr != hwmon_temp_label) + return -EOPNOTSUPP; + + *str = (channel < DEFAULT_CHANNEL_NUMS) ? + cputemp_label[channel] : + (const char *)priv->coretemp_label[channel - + DEFAULT_CHANNEL_NUMS]; + + return 0; +} + +static int cputemp_read(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct peci_cputemp *priv = dev_get_drvdata(dev); + int ret, core_index; + + if (channel >= CPUTEMP_CHANNEL_NUMS || + !(priv->temp_config[channel] & BIT(attr))) + return -EOPNOTSUPP; + + ret = get_temp_targets(priv); + if (ret) + return ret; + + switch (attr) { + case hwmon_temp_input: + switch (channel) { + case channel_die: + ret = get_die_temp(priv); + if (ret) + break; + + *val = priv->temp.die.value; + break; + case channel_dts: + ret = get_dts(priv); + if (ret) + break; + + *val = priv->temp.dts.value; + break; + case channel_tcontrol: + *val = priv->temp.tcontrol.value; + break; + case channel_tthrottle: + *val = priv->temp.tthrottle.value; + break; + case channel_tjmax: + *val = priv->temp.tjmax.value; + break; + default: + core_index = channel - DEFAULT_CHANNEL_NUMS; + ret = get_core_temp(priv, core_index); + if (ret) + break; + + *val = priv->temp.core[core_index].value; + break; + } + break; + case hwmon_temp_max: + *val = priv->temp.tcontrol.value; + break; + case hwmon_temp_crit: + *val = priv->temp.tjmax.value; + break; + case hwmon_temp_crit_hyst: + *val = priv->temp.tjmax.value - priv->temp.tcontrol.value; + break; + default: + ret = -EOPNOTSUPP; + break; + } + + return ret; +} + +static umode_t cputemp_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct peci_cputemp *priv = data; + + if (channel < ARRAY_SIZE(priv->temp_config) && + (priv->temp_config[channel] & BIT(attr)) && + (channel < DEFAULT_CHANNEL_NUMS || + (channel >= DEFAULT_CHANNEL_NUMS && + (priv->core_mask & BIT(channel - DEFAULT_CHANNEL_NUMS))))) + return 0444; + + return 0; +} + +static const struct hwmon_ops cputemp_ops = { + .is_visible = cputemp_is_visible, + .read_string = cputemp_read_string, + .read = cputemp_read, +}; + +static int check_resolved_cores(struct peci_cputemp *priv) +{ + struct peci_rd_pci_cfg_local_msg msg; + int ret; + + /* Get the RESOLVED_CORES register value */ + msg.addr = priv->mgr->client->addr; + msg.device = 30; + msg.function = 3; + msg.rx_len = 4; + msg.bus = 1; + msg.reg = 0xb4; + + ret = peci_command(priv->mgr->client->adapter, + PECI_CMD_RD_PCI_CFG_LOCAL, &msg); + if (msg.cc != PECI_DEV_CC_SUCCESS) + ret = -EAGAIN; + if (ret) + return ret; + + priv->core_mask = le32_to_cpup((__le32 *)msg.pci_config); + if (!priv->core_mask) + return -EAGAIN; + + dev_dbg(priv->dev, "Scanned resolved cores: 0x%llx\n", priv->core_mask); + + return 0; +} + +static int create_core_temp_label(struct peci_cputemp *priv, int idx) +{ + priv->coretemp_label[idx] = devm_kzalloc(priv->dev, + PECI_HWMON_LABEL_STR_LEN, + GFP_KERNEL); + if (!priv->coretemp_label[idx]) + return -ENOMEM; + + sprintf(priv->coretemp_label[idx], "Core %d", idx + 1); + + return 0; +} + +static int create_core_temp_info(struct peci_cputemp *priv) +{ + int ret, i; + + ret = check_resolved_cores(priv); + if (ret) + return ret; + + priv->coretemp_label = devm_kzalloc(priv->dev, + priv->gen_info->core_max * + sizeof(char *), + GFP_KERNEL); + if (!priv->coretemp_label) + return -ENOMEM; + + for (i = 0; i < priv->gen_info->core_max; i++) + if (priv->core_mask & BIT(i)) { + while (priv->config_idx <= i + DEFAULT_CHANNEL_NUMS) + priv->temp_config[priv->config_idx++] = + config_table[channel_core]; + + ret = create_core_temp_label(priv, i); + if (ret) + return ret; + } + + return 0; +} + +static int peci_cputemp_probe(struct platform_device *pdev) +{ + struct peci_client_manager *mgr = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + struct peci_cputemp *priv; + struct device *hwmon_dev; + int ret; + + if ((mgr->client->adapter->cmd_mask & + (BIT(PECI_CMD_GET_TEMP) | BIT(PECI_CMD_RD_PKG_CFG))) != + (BIT(PECI_CMD_GET_TEMP) | BIT(PECI_CMD_RD_PKG_CFG))) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + priv->mgr = mgr; + priv->dev = dev; + priv->gen_info = mgr->gen_info; + + snprintf(priv->name, PECI_NAME_SIZE, "peci_cputemp.cpu%d", + mgr->client->addr - PECI_BASE_ADDR); + + priv->temp_config[priv->config_idx++] = config_table[channel_die]; + priv->temp_config[priv->config_idx++] = config_table[channel_dts]; + priv->temp_config[priv->config_idx++] = config_table[channel_tcontrol]; + priv->temp_config[priv->config_idx++] = config_table[channel_tthrottle]; + priv->temp_config[priv->config_idx++] = config_table[channel_tjmax]; + + ret = create_core_temp_info(priv); + if (ret) + dev_dbg(dev, "Skipped creating core temp info\n"); + + priv->chip.ops = &cputemp_ops; + priv->chip.info = priv->info; + + priv->info[0] = &priv->temp_info; + + priv->temp_info.type = hwmon_temp; + priv->temp_info.config = priv->temp_config; + + hwmon_dev = devm_hwmon_device_register_with_info(priv->dev, + priv->name, + priv, + &priv->chip, + NULL); + + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + dev_dbg(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), priv->name); + + return 0; +} + +static const struct platform_device_id peci_cputemp_ids[] = { + { .name = "peci-cputemp", .driver_data = 0 }, + { } +}; +MODULE_DEVICE_TABLE(platform, peci_cputemp_ids); + +static struct platform_driver peci_cputemp_driver = { + .probe = peci_cputemp_probe, + .id_table = peci_cputemp_ids, + .driver = { .name = KBUILD_MODNAME, }, +}; +module_platform_driver(peci_cputemp_driver); + +MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>"); +MODULE_DESCRIPTION("PECI cputemp driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/peci-dimmtemp.c b/drivers/hwmon/peci-dimmtemp.c new file mode 100644 index 000000000000..1555bfdefabd --- /dev/null +++ b/drivers/hwmon/peci-dimmtemp.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018-2019 Intel Corporation + +#include <linux/hwmon.h> +#include <linux/jiffies.h> +#include <linux/mfd/intel-peci-client.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/workqueue.h> +#include "peci-hwmon.h" + +#define DIMM_MASK_CHECK_DELAY_JIFFIES msecs_to_jiffies(5000) +#define DIMM_MASK_CHECK_RETRY_MAX 60 /* 60 x 5 secs = 5 minutes */ + +struct peci_dimmtemp { + struct peci_client_manager *mgr; + struct device *dev; + char name[PECI_NAME_SIZE]; + const struct cpu_gen_info *gen_info; + struct workqueue_struct *work_queue; + struct delayed_work work_handler; + struct peci_sensor_data temp[DIMM_NUMS_MAX]; + long temp_max[DIMM_NUMS_MAX]; + long temp_crit[DIMM_NUMS_MAX]; + u32 dimm_mask; + int retry_count; + u32 temp_config[DIMM_NUMS_MAX + 1]; + struct hwmon_channel_info temp_info; + const struct hwmon_channel_info *info[2]; + struct hwmon_chip_info chip; + char **dimmtemp_label; +}; + +static const u8 support_model[4] = { + INTEL_FAM6_HASWELL_X, + INTEL_FAM6_BROADWELL_X, + INTEL_FAM6_SKYLAKE_X, + INTEL_FAM6_SKYLAKE_XD, +}; + +static inline int read_ddr_dimm_temp_config(struct peci_dimmtemp *priv, + int chan_rank, + u8 *cfg_data) +{ + return peci_client_read_package_config(priv->mgr, + PECI_MBX_INDEX_DDR_DIMM_TEMP, + chan_rank, cfg_data); +} + +static int get_dimm_temp(struct peci_dimmtemp *priv, int dimm_no) +{ + int dimm_order = dimm_no % priv->gen_info->dimm_idx_max; + int chan_rank = dimm_no / priv->gen_info->dimm_idx_max; + struct peci_rd_pci_cfg_local_msg rp_msg; + u8 cfg_data[4]; + int ret; + + if (!peci_sensor_need_update(&priv->temp[dimm_no])) + return 0; + + ret = read_ddr_dimm_temp_config(priv, chan_rank, cfg_data); + if (ret) + return ret; + + priv->temp[dimm_no].value = cfg_data[dimm_order] * 1000; + + switch (priv->gen_info->model) { + case INTEL_FAM6_SKYLAKE_X: + rp_msg.addr = priv->mgr->client->addr; + rp_msg.bus = 2; + /* + * Device 10, Function 2: IMC 0 channel 0 -> rank 0 + * Device 10, Function 6: IMC 0 channel 1 -> rank 1 + * Device 11, Function 2: IMC 0 channel 2 -> rank 2 + * Device 12, Function 2: IMC 1 channel 0 -> rank 3 + * Device 12, Function 6: IMC 1 channel 1 -> rank 4 + * Device 13, Function 2: IMC 1 channel 2 -> rank 5 + */ + rp_msg.device = 10 + chan_rank / 3 * 2 + + (chan_rank % 3 == 2 ? 1 : 0); + rp_msg.function = chan_rank % 3 == 1 ? 6 : 2; + rp_msg.reg = 0x120 + dimm_order * 4; + rp_msg.rx_len = 4; + + ret = peci_command(priv->mgr->client->adapter, + PECI_CMD_RD_PCI_CFG_LOCAL, &rp_msg); + if (rp_msg.cc != PECI_DEV_CC_SUCCESS) + ret = -EAGAIN; + if (ret) + return ret; + + priv->temp_max[dimm_no] = rp_msg.pci_config[1] * 1000; + priv->temp_crit[dimm_no] = rp_msg.pci_config[2] * 1000; + break; + case INTEL_FAM6_SKYLAKE_XD: + rp_msg.addr = priv->mgr->client->addr; + rp_msg.bus = 2; + /* + * Device 10, Function 2: IMC 0 channel 0 -> rank 0 + * Device 10, Function 6: IMC 0 channel 1 -> rank 1 + * Device 12, Function 2: IMC 1 channel 0 -> rank 2 + * Device 12, Function 6: IMC 1 channel 1 -> rank 3 + */ + rp_msg.device = 10 + chan_rank / 2 * 2; + rp_msg.function = (chan_rank % 2) ? 6 : 2; + rp_msg.reg = 0x120 + dimm_order * 4; + rp_msg.rx_len = 4; + + ret = peci_command(priv->mgr->client->adapter, + PECI_CMD_RD_PCI_CFG_LOCAL, &rp_msg); + if (rp_msg.cc != PECI_DEV_CC_SUCCESS) + ret = -EAGAIN; + if (ret) + return ret; + + priv->temp_max[dimm_no] = rp_msg.pci_config[1] * 1000; + priv->temp_crit[dimm_no] = rp_msg.pci_config[2] * 1000; + break; + case INTEL_FAM6_HASWELL_X: + case INTEL_FAM6_BROADWELL_X: + rp_msg.addr = priv->mgr->client->addr; + rp_msg.bus = 1; + /* + * Device 20, Function 0: IMC 0 channel 0 -> rank 0 + * Device 20, Function 1: IMC 0 channel 1 -> rank 1 + * Device 21, Function 0: IMC 0 channel 2 -> rank 2 + * Device 21, Function 1: IMC 0 channel 3 -> rank 3 + * Device 23, Function 0: IMC 1 channel 0 -> rank 4 + * Device 23, Function 1: IMC 1 channel 1 -> rank 5 + * Device 24, Function 0: IMC 1 channel 2 -> rank 6 + * Device 24, Function 1: IMC 1 channel 3 -> rank 7 + */ + rp_msg.device = 20 + chan_rank / 2 + chan_rank / 4; + rp_msg.function = chan_rank % 2; + rp_msg.reg = 0x120 + dimm_order * 4; + rp_msg.rx_len = 4; + + ret = peci_command(priv->mgr->client->adapter, + PECI_CMD_RD_PCI_CFG_LOCAL, &rp_msg); + if (rp_msg.cc != PECI_DEV_CC_SUCCESS) + ret = -EAGAIN; + if (ret) + return ret; + + priv->temp_max[dimm_no] = rp_msg.pci_config[1] * 1000; + priv->temp_crit[dimm_no] = rp_msg.pci_config[2] * 1000; + break; + default: + return -EOPNOTSUPP; + } + + peci_sensor_mark_updated(&priv->temp[dimm_no]); + + return 0; +} + +static int dimmtemp_read_string(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + struct peci_dimmtemp *priv = dev_get_drvdata(dev); + + if (attr != hwmon_temp_label) + return -EOPNOTSUPP; + + *str = (const char *)priv->dimmtemp_label[channel]; + + return 0; +} + +static int dimmtemp_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct peci_dimmtemp *priv = dev_get_drvdata(dev); + int ret; + + ret = get_dimm_temp(priv, channel); + if (ret) + return ret; + + switch (attr) { + case hwmon_temp_input: + *val = priv->temp[channel].value; + break; + case hwmon_temp_max: + *val = priv->temp_max[channel]; + break; + case hwmon_temp_crit: + *val = priv->temp_crit[channel]; + break; + default: + ret = -EOPNOTSUPP; + break; + } + + return ret; +} + +static umode_t dimmtemp_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct peci_dimmtemp *priv = data; + + if (priv->temp_config[channel] & BIT(attr) && + priv->dimm_mask & BIT(channel)) + return 0444; + + return 0; +} + +static const struct hwmon_ops dimmtemp_ops = { + .is_visible = dimmtemp_is_visible, + .read_string = dimmtemp_read_string, + .read = dimmtemp_read, +}; + +static int check_populated_dimms(struct peci_dimmtemp *priv) +{ + u32 chan_rank_max = priv->gen_info->chan_rank_max; + u32 dimm_idx_max = priv->gen_info->dimm_idx_max; + int chan_rank, dimm_idx; + u8 cfg_data[4]; + + for (chan_rank = 0; chan_rank < chan_rank_max; chan_rank++) { + int ret; + + ret = read_ddr_dimm_temp_config(priv, chan_rank, cfg_data); + if (ret) { + priv->dimm_mask = 0; + return ret; + } + + for (dimm_idx = 0; dimm_idx < dimm_idx_max; dimm_idx++) + if (cfg_data[dimm_idx]) + priv->dimm_mask |= BIT(chan_rank * + dimm_idx_max + + dimm_idx); + } + + if (!priv->dimm_mask) + return -EAGAIN; + + dev_dbg(priv->dev, "Scanned populated DIMMs: 0x%x\n", priv->dimm_mask); + + return 0; +} + +static int create_dimm_temp_label(struct peci_dimmtemp *priv, int chan) +{ + int rank, idx; + + priv->dimmtemp_label[chan] = devm_kzalloc(priv->dev, + PECI_HWMON_LABEL_STR_LEN, + GFP_KERNEL); + if (!priv->dimmtemp_label[chan]) + return -ENOMEM; + + rank = chan / priv->gen_info->dimm_idx_max; + idx = chan % priv->gen_info->dimm_idx_max; + + sprintf(priv->dimmtemp_label[chan], "DIMM %c%d", 'A' + rank, idx + 1); + + return 0; +} + +static int create_dimm_temp_info(struct peci_dimmtemp *priv) +{ + int ret, i, config_idx, channels; + struct device *dev; + + ret = check_populated_dimms(priv); + if (ret) { + if (ret == -EAGAIN) { + if (priv->retry_count < DIMM_MASK_CHECK_RETRY_MAX) { + queue_delayed_work(priv->work_queue, + &priv->work_handler, + DIMM_MASK_CHECK_DELAY_JIFFIES); + priv->retry_count++; + dev_dbg(priv->dev, + "Deferred DIMM temp info creation\n"); + } else { + dev_err(priv->dev, + "Timeout DIMM temp info creation\n"); + ret = -ETIMEDOUT; + } + } + + return ret; + } + + channels = priv->gen_info->chan_rank_max * + priv->gen_info->dimm_idx_max; + + priv->dimmtemp_label = devm_kzalloc(priv->dev, + channels * sizeof(char *), + GFP_KERNEL); + if (!priv->dimmtemp_label) + return -ENOMEM; + + for (i = 0, config_idx = 0; i < channels; i++) + if (priv->dimm_mask & BIT(i)) { + while (i >= config_idx) + priv->temp_config[config_idx++] = + HWMON_T_LABEL | HWMON_T_INPUT | + HWMON_T_MAX | HWMON_T_CRIT; + + ret = create_dimm_temp_label(priv, i); + if (ret) + return ret; + } + + priv->chip.ops = &dimmtemp_ops; + priv->chip.info = priv->info; + + priv->info[0] = &priv->temp_info; + + priv->temp_info.type = hwmon_temp; + priv->temp_info.config = priv->temp_config; + + dev = devm_hwmon_device_register_with_info(priv->dev, + priv->name, + priv, + &priv->chip, + NULL); + if (IS_ERR(dev)) { + dev_err(priv->dev, "Failed to register hwmon device\n"); + return PTR_ERR(dev); + } + + dev_dbg(priv->dev, "%s: sensor '%s'\n", dev_name(dev), priv->name); + + return 0; +} + +static void create_dimm_temp_info_delayed(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct peci_dimmtemp *priv = container_of(dwork, struct peci_dimmtemp, + work_handler); + int ret; + + ret = create_dimm_temp_info(priv); + if (ret && ret != -EAGAIN) + dev_dbg(priv->dev, "Failed to create DIMM temp info\n"); +} + +static int peci_dimmtemp_probe(struct platform_device *pdev) +{ + struct peci_client_manager *mgr = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + struct peci_dimmtemp *priv; + int ret, i; + + if ((mgr->client->adapter->cmd_mask & + (BIT(PECI_CMD_GET_TEMP) | BIT(PECI_CMD_RD_PKG_CFG))) != + (BIT(PECI_CMD_GET_TEMP) | BIT(PECI_CMD_RD_PKG_CFG))) + return -ENODEV; + + for (i = 0; i < ARRAY_SIZE(support_model); i++) { + if (mgr->gen_info->model == support_model[i]) + break; + } + if (i == ARRAY_SIZE(support_model)) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + priv->mgr = mgr; + priv->dev = dev; + priv->gen_info = mgr->gen_info; + + snprintf(priv->name, PECI_NAME_SIZE, "peci_dimmtemp.cpu%d", + priv->mgr->client->addr - PECI_BASE_ADDR); + + priv->work_queue = alloc_ordered_workqueue(priv->name, 0); + if (!priv->work_queue) + return -ENOMEM; + + INIT_DELAYED_WORK(&priv->work_handler, create_dimm_temp_info_delayed); + + ret = create_dimm_temp_info(priv); + if (ret && ret != -EAGAIN) { + dev_dbg(dev, "Failed to create DIMM temp info\n"); + goto err_free_wq; + } + + return 0; + +err_free_wq: + destroy_workqueue(priv->work_queue); + return ret; +} + +static int peci_dimmtemp_remove(struct platform_device *pdev) +{ + struct peci_dimmtemp *priv = dev_get_drvdata(&pdev->dev); + + cancel_delayed_work_sync(&priv->work_handler); + destroy_workqueue(priv->work_queue); + + return 0; +} + +static const struct platform_device_id peci_dimmtemp_ids[] = { + { .name = "peci-dimmtemp", .driver_data = 0 }, + { } +}; +MODULE_DEVICE_TABLE(platform, peci_dimmtemp_ids); + +static struct platform_driver peci_dimmtemp_driver = { + .probe = peci_dimmtemp_probe, + .remove = peci_dimmtemp_remove, + .id_table = peci_dimmtemp_ids, + .driver = { .name = KBUILD_MODNAME, }, +}; +module_platform_driver(peci_dimmtemp_driver); + +MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>"); +MODULE_DESCRIPTION("PECI dimmtemp driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/peci-hwmon.h b/drivers/hwmon/peci-hwmon.h new file mode 100644 index 000000000000..4d78c528c4c8 --- /dev/null +++ b/drivers/hwmon/peci-hwmon.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2018-2019 Intel Corporation */ + +#ifndef __PECI_HWMON_H +#define __PECI_HWMON_H + +#include <linux/peci.h> + +#define TEMP_TYPE_PECI 6 /* Sensor type 6: Intel PECI */ +#define UPDATE_INTERVAL HZ + +#define PECI_HWMON_LABEL_STR_LEN 10 + +/** + * struct peci_sensor_data - PECI sensor information + * @valid: flag to indicate the sensor value is valid + * @value: sensor value in millidegree Celsius + * @last_updated: time of the last update in jiffies + */ +struct peci_sensor_data { + uint valid; + s32 value; + ulong last_updated; +}; + +/** + * peci_sensor_need_update - check whether sensor update is needed or not + * @sensor: pointer to sensor data struct + * + * Return: true if update is needed, false if not. + */ +static inline bool peci_sensor_need_update(struct peci_sensor_data *sensor) +{ + return !sensor->valid || + time_after(jiffies, sensor->last_updated + UPDATE_INTERVAL); +} + +/** + * peci_sensor_mark_updated - mark the sensor is updated + * @sensor: pointer to sensor data struct + */ +static inline void peci_sensor_mark_updated(struct peci_sensor_data *sensor) +{ + sensor->valid = 1; + sensor->last_updated = jiffies; +} + +#endif /* __PECI_HWMON_H */ diff --git a/drivers/hwmon/pmbus/max31785.c b/drivers/hwmon/pmbus/max31785.c index d9aa5c873d21..cbcd0b2301f4 100644 --- a/drivers/hwmon/pmbus/max31785.c +++ b/drivers/hwmon/pmbus/max31785.c @@ -12,40 +12,126 @@ enum max31785_regs { MFR_REVISION = 0x9b, + MFR_FAULT_RESPONSE = 0xd9, + MFR_TEMP_SENSOR_CONFIG = 0xf0, MFR_FAN_CONFIG = 0xf1, + MFR_FAN_FAULT_LIMIT = 0xf5, }; #define MAX31785 0x3030 #define MAX31785A 0x3040 #define MFR_FAN_CONFIG_DUAL_TACH BIT(12) +#define MFR_FAN_CONFIG_TSFO BIT(9) +#define MFR_FAN_CONFIG_TACHO BIT(8) +#define MFR_FAN_CONFIG_HEALTH BIT(4) +#define MFR_FAN_CONFIG_ROTOR_HI_LO BIT(3) +#define MFR_FAN_CONFIG_ROTOR BIT(2) + +#define MFR_FAULT_RESPONSE_MONITOR BIT(0) #define MAX31785_NR_PAGES 23 #define MAX31785_NR_FAN_PAGES 6 +/* + * MAX31785 dragons ahead + * + * We see weird issues where some transfers fail. There doesn't appear to be + * any pattern to the problem, so below we wrap all the read/write calls with a + * retry. The device provides no indication of this besides NACK'ing master + * Txs; no bits are set in STATUS_BYTE to suggest anything has gone wrong. + */ + +#define max31785_retry(_func, ...) ({ \ + /* All relevant functions return int, sue me */ \ + int _ret = _func(__VA_ARGS__); \ + if (_ret == -EIO) \ + _ret = _func(__VA_ARGS__); \ + _ret; \ +}) + +static int max31785_i2c_smbus_read_byte_data(struct i2c_client *client, + int command) +{ + return max31785_retry(i2c_smbus_read_byte_data, client, command); +} + + +static int max31785_i2c_smbus_write_byte_data(struct i2c_client *client, + int command, u16 data) +{ + return max31785_retry(i2c_smbus_write_byte_data, client, command, data); +} + +static int max31785_i2c_smbus_read_word_data(struct i2c_client *client, + int command) +{ + return max31785_retry(i2c_smbus_read_word_data, client, command); +} + +static int max31785_i2c_smbus_write_word_data(struct i2c_client *client, + int command, u16 data) +{ + return max31785_retry(i2c_smbus_write_word_data, client, command, data); +} + +static int max31785_pmbus_write_byte(struct i2c_client *client, int page, + u8 value) +{ + return max31785_retry(pmbus_write_byte, client, page, value); +} + +static int max31785_pmbus_read_byte_data(struct i2c_client *client, int page, + int command) +{ + return max31785_retry(pmbus_read_byte_data, client, page, command); +} + +static int max31785_pmbus_write_byte_data(struct i2c_client *client, int page, + int command, u16 data) +{ + return max31785_retry(pmbus_write_byte_data, client, page, command, + data); +} + +static int max31785_pmbus_read_word_data(struct i2c_client *client, int page, + int phase, int command) +{ + return max31785_retry(pmbus_read_word_data, client, page, phase, command); +} + +static int max31785_pmbus_write_word_data(struct i2c_client *client, int page, + int command, u16 data) +{ + return max31785_retry(pmbus_write_word_data, client, page, command, + data); +} + static int max31785_read_byte_data(struct i2c_client *client, int page, int reg) { - if (page < MAX31785_NR_PAGES) - return -ENODATA; - switch (reg) { case PMBUS_VOUT_MODE: - return -ENOTSUPP; + if (page >= MAX31785_NR_PAGES) + return -ENOTSUPP; + break; case PMBUS_FAN_CONFIG_12: - return pmbus_read_byte_data(client, page - MAX31785_NR_PAGES, - reg); + if (page >= MAX31785_NR_PAGES) + return max31785_pmbus_read_byte_data(client, + page - MAX31785_NR_PAGES, + reg); + break; } - return -ENODATA; + return max31785_pmbus_read_byte_data(client, page, reg); } static int max31785_write_byte(struct i2c_client *client, int page, u8 value) { - if (page < MAX31785_NR_PAGES) - return -ENODATA; + if (page >= MAX31785_NR_PAGES) + return -ENOTSUPP; - return -ENOTSUPP; + return max31785_pmbus_write_byte(client, page, value); } static int max31785_read_long_data(struct i2c_client *client, int page, @@ -106,11 +192,13 @@ static int max31785_get_pwm_mode(struct i2c_client *client, int page) int config; int command; - config = pmbus_read_byte_data(client, page, PMBUS_FAN_CONFIG_12); + config = max31785_pmbus_read_byte_data(client, page, + PMBUS_FAN_CONFIG_12); if (config < 0) return config; - command = pmbus_read_word_data(client, page, 0xff, PMBUS_FAN_COMMAND_1); + command = max31785_pmbus_read_word_data(client, page, 0xff, + PMBUS_FAN_COMMAND_1); if (command < 0) return command; @@ -134,15 +222,14 @@ static int max31785_read_word_data(struct i2c_client *client, int page, switch (reg) { case PMBUS_READ_FAN_SPEED_1: if (page < MAX31785_NR_PAGES) - return -ENODATA; + return max31785_pmbus_read_word_data(client, page, 0xff, reg); rv = max31785_read_long_data(client, page - MAX31785_NR_PAGES, reg, &val); if (rv < 0) return rv; - rv = (val >> 16) & 0xffff; - break; + return (val >> 16) & 0xffff; case PMBUS_FAN_COMMAND_1: /* * PMBUS_FAN_COMMAND_x is probed to judge whether or not to @@ -150,20 +237,28 @@ static int max31785_read_word_data(struct i2c_client *client, int page, * * Don't expose fan_target attribute for virtual pages. */ - rv = (page >= MAX31785_NR_PAGES) ? -ENOTSUPP : -ENODATA; + if (page >= MAX31785_NR_PAGES) + return -ENOTSUPP; break; + case PMBUS_VIRT_FAN_TARGET_1: + if (page >= MAX31785_NR_PAGES) + return -ENOTSUPP; + + return -ENODATA; case PMBUS_VIRT_PWM_1: - rv = max31785_get_pwm(client, page); - break; + return max31785_get_pwm(client, page); case PMBUS_VIRT_PWM_ENABLE_1: - rv = max31785_get_pwm_mode(client, page); - break; + return max31785_get_pwm_mode(client, page); default: - rv = -ENODATA; + if (page >= MAX31785_NR_PAGES) + return -ENXIO; break; } - return rv; + if (reg >= PMBUS_VIRT_BASE) + return -ENXIO; + + return max31785_pmbus_read_word_data(client, page, 0xff, reg); } static inline u32 max31785_scale_pwm(u32 sensor_val) @@ -187,6 +282,31 @@ static inline u32 max31785_scale_pwm(u32 sensor_val) return (sensor_val * 100) / 255; } +static int max31785_update_fan(struct i2c_client *client, int page, + u8 config, u8 mask, u16 command) +{ + int from, rv; + u8 to; + + from = max31785_pmbus_read_byte_data(client, page, PMBUS_FAN_CONFIG_12); + if (from < 0) + return from; + + to = (from & ~mask) | (config & mask); + + if (to != from) { + rv = max31785_pmbus_write_byte_data(client, page, + PMBUS_FAN_CONFIG_12, to); + if (rv < 0) + return rv; + } + + rv = max31785_pmbus_write_word_data(client, page, PMBUS_FAN_COMMAND_1, + command); + + return rv; +} + static int max31785_pwm_enable(struct i2c_client *client, int page, u16 word) { @@ -216,15 +336,18 @@ static int max31785_pwm_enable(struct i2c_client *client, int page, return -EINVAL; } - return pmbus_update_fan(client, page, 0, config, PB_FAN_1_RPM, rate); + return max31785_update_fan(client, page, config, PB_FAN_1_RPM, rate); } static int max31785_write_word_data(struct i2c_client *client, int page, int reg, u16 word) { switch (reg) { + case PMBUS_VIRT_FAN_TARGET_1: + return max31785_update_fan(client, page, PB_FAN_1_RPM, + PB_FAN_1_RPM, word); case PMBUS_VIRT_PWM_1: - return pmbus_update_fan(client, page, 0, 0, PB_FAN_1_RPM, + return max31785_update_fan(client, page, 0, PB_FAN_1_RPM, max31785_scale_pwm(word)); case PMBUS_VIRT_PWM_ENABLE_1: return max31785_pwm_enable(client, page, word); @@ -232,7 +355,279 @@ static int max31785_write_word_data(struct i2c_client *client, int page, break; } - return -ENODATA; + if (reg < PMBUS_VIRT_BASE) + return max31785_pmbus_write_word_data(client, page, reg, word); + + return -ENXIO; +} + +/* + * Returns negative error codes if an unrecoverable problem is detected, 0 if a + * recoverable problem is detected, or a positive value on success. + */ +static int max31785_of_fan_config(struct i2c_client *client, + struct pmbus_driver_info *info, + struct device_node *child) +{ + int mfr_cfg = 0, mfr_fault_resp = 0, pb_cfg; + struct device *dev = &client->dev; + char *lock_polarity = NULL; + const char *sval; + u32 page; + u32 uval; + int ret; + + if (!of_device_is_compatible(child, "pmbus-fan")) + return 0; + + ret = of_property_read_u32(child, "reg", &page); + if (ret < 0) { + dev_err(&client->dev, "Missing valid reg property\n"); + return ret; + } + + if (!(info->func[page] & PMBUS_HAVE_FAN12)) { + dev_err(dev, "Page %d does not have fan capabilities\n", page); + return -ENXIO; + } + + ret = max31785_i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + if (ret < 0) + return ret; + + pb_cfg = max31785_i2c_smbus_read_byte_data(client, PMBUS_FAN_CONFIG_12); + if (pb_cfg < 0) + return pb_cfg; + + if (of_property_read_bool(child->parent, "use-stored-presence")) { + if (!(pb_cfg & PB_FAN_1_INSTALLED)) + dev_info(dev, "Fan %d is configured but not installed\n", + page); + } else { + pb_cfg |= PB_FAN_1_INSTALLED; + } + + ret = of_property_read_string(child, "maxim,fan-rotor-input", &sval); + if (ret < 0) { + dev_err(dev, "Missing valid maxim,fan-rotor-input property for fan %d\n", + page); + return ret; + } + + if (strcmp("tach", sval) && strcmp("lock", sval)) { + dev_err(dev, "maxim,fan-rotor-input has invalid value for fan %d: %s\n", + page, sval); + return -EINVAL; + } else if (!strcmp("lock", sval)) { + mfr_cfg |= MFR_FAN_CONFIG_ROTOR; + + ret = max31785_i2c_smbus_write_word_data(client, + MFR_FAN_FAULT_LIMIT, + 1); + if (ret < 0) + return ret; + + ret = of_property_read_string(child, "maxim,fan-lock-polarity", + &sval); + if (ret < 0) { + dev_err(dev, "Missing valid maxim,fan-lock-polarity property for fan %d\n", + page); + return ret; + } + + if (strcmp("low", sval) && strcmp("high", sval)) { + dev_err(dev, "maxim,fan-lock-polarity has invalid value for fan %d: %s\n", + page, lock_polarity); + return -EINVAL; + } else if (!strcmp("high", sval)) + mfr_cfg |= MFR_FAN_CONFIG_ROTOR_HI_LO; + } + + if (!of_property_read_string(child, "fan-mode", &sval)) { + if (!strcmp("rpm", sval)) + pb_cfg |= PB_FAN_1_RPM; + else if (!strcmp("pwm", sval)) + pb_cfg &= ~PB_FAN_1_RPM; + else { + dev_err(dev, "fan-mode has invalid value for fan %d: %s\n", + page, sval); + return -EINVAL; + } + } + + ret = of_property_read_u32(child, "tach-pulses", &uval); + if (ret < 0) { + pb_cfg &= ~PB_FAN_1_PULSE_MASK; + } else if (uval && (uval - 1) < 4) { + pb_cfg = ((pb_cfg & ~PB_FAN_1_PULSE_MASK) | ((uval - 1) << 4)); + } else { + dev_err(dev, "tach-pulses has invalid value for fan %d: %u\n", + page, uval); + return -EINVAL; + } + + if (of_property_read_bool(child, "maxim,fan-health")) + mfr_cfg |= MFR_FAN_CONFIG_HEALTH; + + if (of_property_read_bool(child, "maxim,fan-no-watchdog") || + of_property_read_bool(child, "maxim,tmp-no-fault-ramp")) + mfr_cfg |= MFR_FAN_CONFIG_TSFO; + + if (of_property_read_bool(child, "maxim,fan-dual-tach")) + mfr_cfg |= MFR_FAN_CONFIG_DUAL_TACH; + + if (of_property_read_bool(child, "maxim,fan-no-fault-ramp")) + mfr_cfg |= MFR_FAN_CONFIG_TACHO; + + if (!of_property_read_u32(child, "maxim,fan-startup", &uval)) { + uval /= 2; + if (uval < 5) { + mfr_cfg |= uval; + } else { + dev_err(dev, "maxim,fan-startup has invalid value for fan %d: %u\n", + page, uval); + return -EINVAL; + } + } + + if (!of_property_read_u32(child, "maxim,fan-ramp", &uval)) { + if (uval < 8) { + mfr_cfg |= uval << 5; + } else { + dev_err(dev, "maxim,fan-ramp has invalid value for fan %d: %u\n", + page, uval); + return -EINVAL; + } + } + + if (!of_property_read_u32(child, "maxim,tmp-hysteresis", &uval)) { + uval /= 2; + uval -= 1; + if (uval < 4) { + mfr_cfg |= uval << 10; + } else { + dev_err(dev, "maxim,tmp-hysteresis has invalid value for fan %d, %u\n", + page, uval); + return -EINVAL; + } + } + + if (!of_property_read_u32(child, "maxim,fan-pwm-freq", &uval)) { + u16 val; + + if (uval == 30) { + val = 0; + } else if (uval == 50) { + val = 1; + } else if (uval == 100) { + val = 2; + } else if (uval == 150) { + val = 3; + } else if (uval == 25000) { + val = 7; + } else { + dev_err(dev, "maxim,fan-pwm-freq has invalid value for fan %d: %u\n", + page, uval); + return -EINVAL; + } + + mfr_cfg |= val << 13; + } + + if (of_property_read_bool(child, "maxim,fan-fault-pin-mon")) + mfr_fault_resp |= MFR_FAULT_RESPONSE_MONITOR; + + ret = max31785_i2c_smbus_write_byte_data(client, PMBUS_FAN_CONFIG_12, + pb_cfg & ~PB_FAN_1_INSTALLED); + if (ret < 0) + return ret; + + ret = max31785_i2c_smbus_write_word_data(client, MFR_FAN_CONFIG, + mfr_cfg); + if (ret < 0) + return ret; + + ret = max31785_i2c_smbus_write_byte_data(client, MFR_FAULT_RESPONSE, + mfr_fault_resp); + if (ret < 0) + return ret; + + ret = max31785_i2c_smbus_write_byte_data(client, PMBUS_FAN_CONFIG_12, + pb_cfg); + if (ret < 0) + return ret; + + /* + * Fans are on pages 0 - 5. If the page property of a fan node is + * greater than 5 we will have errored in checks above out above. + * Therefore we don't need to cope with values up to 31, and the int + * return type is enough. + * + * The bit mask return value is used to populate a bitfield of fans + * who are both configured in the devicetree _and_ reported as + * installed by the hardware. Any fans that are not configured in the + * devicetree but are reported as installed by the hardware will have + * their hardware configuration updated to unset the installed bit. + */ + return BIT(page); +} + +static int max31785_of_tmp_config(struct i2c_client *client, + struct pmbus_driver_info *info, + struct device_node *child) +{ + struct device *dev = &client->dev; + struct device_node *np; + u16 mfr_tmp_cfg = 0; + u32 page; + u32 uval; + int ret; + int i; + + if (!of_device_is_compatible(child, "pmbus-temperature")) + return 0; + + ret = of_property_read_u32(child, "reg", &page); + if (ret < 0) { + dev_err(&client->dev, "Missing valid reg property\n"); + return ret; + } + + if (!(info->func[page] & PMBUS_HAVE_TEMP)) { + dev_err(dev, "Page %d does not have temp capabilities\n", page); + return -ENXIO; + } + + ret = max31785_i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + if (ret < 0) + return ret; + + if (!of_property_read_u32(child, "maxim,tmp-offset", &uval)) { + if (uval < 32) + mfr_tmp_cfg |= uval << 10; + } + + i = 0; + while ((np = of_parse_phandle(child, "maxim,tmp-fans", i))) { + if (of_property_read_u32(np, "reg", &uval)) { + dev_err(&client->dev, "Failed to read fan reg property for phandle index %d\n", + i); + } else { + if (uval < 6) + mfr_tmp_cfg |= BIT(uval); + else + dev_warn(&client->dev, "Invalid fan page: %d\n", + uval); + } + i++; + } + + ret = max31785_i2c_smbus_write_word_data(client, MFR_TEMP_SENSOR_CONFIG, + mfr_tmp_cfg); + if (ret < 0) + return ret; + + return 0; } #define MAX31785_FAN_FUNCS \ @@ -304,11 +699,11 @@ static int max31785_configure_dual_tach(struct i2c_client *client, int i; for (i = 0; i < MAX31785_NR_FAN_PAGES; i++) { - ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); + ret = max31785_i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); if (ret < 0) return ret; - ret = i2c_smbus_read_word_data(client, MFR_FAN_CONFIG); + ret = max31785_i2c_smbus_read_word_data(client, MFR_FAN_CONFIG); if (ret < 0) return ret; @@ -328,9 +723,12 @@ static int max31785_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct device *dev = &client->dev; + struct device_node *child; struct pmbus_driver_info *info; bool dual_tach = false; + u32 fans; s64 ret; + int i; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA | @@ -343,7 +741,7 @@ static int max31785_probe(struct i2c_client *client, *info = max31785_info; - ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 255); + ret = max31785_i2c_smbus_write_byte_data(client, PMBUS_PAGE, 255); if (ret < 0) return ret; @@ -360,6 +758,49 @@ static int max31785_probe(struct i2c_client *client, return -ENODEV; } + fans = 0; + for_each_child_of_node(dev->of_node, child) { + ret = max31785_of_fan_config(client, info, child); + if (ret < 0) { + of_node_put(child); + return ret; + } + + if (ret) + fans |= ret; + + ret = max31785_of_tmp_config(client, info, child); + if (ret < 0) { + of_node_put(child); + return ret; + } + } + + for (i = 0; i < MAX31785_NR_PAGES; i++) { + bool have_fan = !!(info->func[i] & PMBUS_HAVE_FAN12); + bool fan_configured = !!(fans & BIT(i)); + + if (!have_fan || fan_configured) + continue; + + ret = max31785_i2c_smbus_write_byte_data(client, PMBUS_PAGE, + i); + if (ret < 0) + return ret; + + ret = max31785_i2c_smbus_read_byte_data(client, + PMBUS_FAN_CONFIG_12); + if (ret < 0) + return ret; + + ret &= ~PB_FAN_1_INSTALLED; + ret = max31785_i2c_smbus_write_word_data(client, + PMBUS_FAN_CONFIG_12, + ret); + if (ret < 0) + return ret; + } + if (dual_tach) { ret = max31785_configure_dual_tach(client, info); if (ret < 0) diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 2191575a448b..702c25010369 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -158,9 +158,19 @@ int pmbus_set_page(struct i2c_client *client, int page, int phase) if (!(data->info->func[page] & PMBUS_PAGE_VIRTUAL) && data->info->pages > 1 && page != data->currpage) { + dev_dbg(&client->dev, "Want page %u, %u cached\n", page, + data->currpage); + rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); - if (rv < 0) - return rv; + if (rv < 0) { + rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, + page); + dev_dbg(&client->dev, + "Failed to set page %u, performed one-shot retry %s: %d\n", + page, rv ? "and failed" : "with success", rv); + if (rv < 0) + return rv; + } rv = i2c_smbus_read_byte_data(client, PMBUS_PAGE); if (rv < 0) @@ -451,15 +461,15 @@ static int pmbus_get_fan_rate(struct i2c_client *client, int page, int id, return s->data; } - config = pmbus_read_byte_data(client, page, - pmbus_fan_config_registers[id]); + config = _pmbus_read_byte_data(client, page, + pmbus_fan_config_registers[id]); if (config < 0) return config; have_rpm = !!(config & pmbus_fan_rpm_mask[id]); if (want_rpm == have_rpm) - return pmbus_read_word_data(client, page, 0xff, - pmbus_fan_command_registers[id]); + return _pmbus_read_word_data(client, page, 0xff, + pmbus_fan_command_registers[id]); /* Can't sensibly map between RPM and PWM, just return zero */ return 0; diff --git a/drivers/i2c/busses/i2c-fsi.c b/drivers/i2c/busses/i2c-fsi.c index 977d6f524649..95b6b6bc1d78 100644 --- a/drivers/i2c/busses/i2c-fsi.c +++ b/drivers/i2c/busses/i2c-fsi.c @@ -703,7 +703,12 @@ static int fsi_i2c_probe(struct device *dev) for (port_no = 0; port_no < ports; port_no++) { np = fsi_i2c_find_port_of_node(dev->of_node, port_no); - if (np && !of_device_is_available(np)) + /* Do not add port if it is not described in the device tree */ + if (!np) + continue; + + /* Do not add port if it is described as disabled */ + if (!of_device_is_available(np)) continue; port = kzalloc(sizeof(*port), GFP_KERNEL); diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c index 4037c504589c..bf7ead45f66b 100644 --- a/drivers/leds/leds-pca955x.c +++ b/drivers/leds/leds-pca955x.c @@ -65,6 +65,7 @@ enum pca955x_type { pca9550, pca9551, pca9552, + pca9552_ibm, pca9553, }; @@ -90,6 +91,11 @@ static struct pca955x_chipdef pca955x_chipdefs[] = { .slv_addr = /* 1100xxx */ 0x60, .slv_addr_shift = 3, }, + [pca9552_ibm] = { + .bits = 16, + .slv_addr = /* 0110xxx */ 0x30, + .slv_addr_shift = 3, + }, [pca9553] = { .bits = 4, .slv_addr = /* 110001x */ 0x62, @@ -101,6 +107,7 @@ static const struct i2c_device_id pca955x_id[] = { { "pca9550", pca9550 }, { "pca9551", pca9551 }, { "pca9552", pca9552 }, + { "pca9552-ibm", pca9552_ibm }, { "pca9553", pca9553 }, { } }; @@ -412,6 +419,7 @@ static const struct of_device_id of_pca955x_match[] = { { .compatible = "nxp,pca9550", .data = (void *)pca9550 }, { .compatible = "nxp,pca9551", .data = (void *)pca9551 }, { .compatible = "nxp,pca9552", .data = (void *)pca9552 }, + { .compatible = "nxp,pca9552-ibm", .data = (void *)pca9552_ibm }, { .compatible = "nxp,pca9553", .data = (void *)pca9553 }, {}, }; diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index a37d7d171382..a311541bce2c 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -670,6 +670,23 @@ config MFD_INTEL_PMC_BXT Register and P-unit access. In addition this creates devices for iTCO watchdog and telemetry that are part of the PMC. +config MFD_INTEL_PECI_CLIENT + tristate "Intel PECI client" + depends on (PECI || COMPILE_TEST) + select MFD_CORE + help + If you say yes to this option, support will be included for the + Intel PECI (Platform Environment Control Interface) client. PECI is a + one-wire bus interface that provides a communication channel from PECI + clients in Intel processors and chipset components to external + monitoring or control devices. + + Additional drivers must be enabled in order to use the functionality + of the device. + + This driver can also be built as a module. If so, the module + will be called intel-peci-client. + config MFD_IPAQ_MICRO bool "Atmel Micro ASIC (iPAQ h3100/h3600/h3700) Support" depends on SA1100_H3100 || SA1100_H3600 diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 9367a92f795a..e288e586b687 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -216,6 +216,7 @@ obj-$(CONFIG_MFD_INTEL_LPSS_PCI) += intel-lpss-pci.o obj-$(CONFIG_MFD_INTEL_LPSS_ACPI) += intel-lpss-acpi.o obj-$(CONFIG_MFD_INTEL_MSIC) += intel_msic.o obj-$(CONFIG_MFD_INTEL_PMC_BXT) += intel_pmc_bxt.o +obj-$(CONFIG_MFD_INTEL_PECI_CLIENT) += intel-peci-client.o obj-$(CONFIG_MFD_PALMAS) += palmas.o obj-$(CONFIG_MFD_VIPERBOARD) += viperboard.o obj-$(CONFIG_MFD_RC5T583) += rc5t583.o rc5t583-irq.o diff --git a/drivers/mfd/intel-peci-client.c b/drivers/mfd/intel-peci-client.c new file mode 100644 index 000000000000..24f15438634c --- /dev/null +++ b/drivers/mfd/intel-peci-client.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018-2019 Intel Corporation + +#include <linux/bitfield.h> +#include <linux/mfd/core.h> +#include <linux/mfd/intel-peci-client.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/peci.h> + +#define CPU_ID_MODEL_MASK GENMASK(7, 4) +#define CPU_ID_FAMILY_MASK GENMASK(11, 8) +#define CPU_ID_EXT_MODEL_MASK GENMASK(19, 16) +#define CPU_ID_EXT_FAMILY_MASK GENMASK(27, 20) + +#define LOWER_NIBBLE_MASK GENMASK(3, 0) +#define UPPER_NIBBLE_MASK GENMASK(7, 4) +#define LOWER_BYTE_MASK GENMASK(7, 0) +#define UPPER_BYTE_MASK GENMASK(16, 8) + +static struct mfd_cell peci_functions[] = { + { .name = "peci-cputemp", }, + { .name = "peci-dimmtemp", }, +}; + +static const struct cpu_gen_info cpu_gen_info_table[] = { + { /* Haswell Xeon */ + .family = INTEL_FAM6, + .model = INTEL_FAM6_HASWELL_X, + .core_max = CORE_MAX_ON_HSX, + .chan_rank_max = CHAN_RANK_MAX_ON_HSX, + .dimm_idx_max = DIMM_IDX_MAX_ON_HSX }, + { /* Broadwell Xeon */ + .family = INTEL_FAM6, + .model = INTEL_FAM6_BROADWELL_X, + .core_max = CORE_MAX_ON_BDX, + .chan_rank_max = CHAN_RANK_MAX_ON_BDX, + .dimm_idx_max = DIMM_IDX_MAX_ON_BDX }, + { /* Skylake Xeon */ + .family = INTEL_FAM6, + .model = INTEL_FAM6_SKYLAKE_X, + .core_max = CORE_MAX_ON_SKX, + .chan_rank_max = CHAN_RANK_MAX_ON_SKX, + .dimm_idx_max = DIMM_IDX_MAX_ON_SKX }, + { /* Skylake Xeon D */ + .family = INTEL_FAM6, + .model = INTEL_FAM6_SKYLAKE_XD, + .core_max = CORE_MAX_ON_SKXD, + .chan_rank_max = CHAN_RANK_MAX_ON_SKXD, + .dimm_idx_max = DIMM_IDX_MAX_ON_SKXD }, +}; + +static int peci_client_get_cpu_gen_info(struct peci_client_manager *priv) +{ + struct device *dev = &priv->client->dev; + u32 cpu_id; + u16 family; + u8 model; + int ret; + int i; + + ret = peci_get_cpu_id(priv->client->adapter, priv->client->addr, + &cpu_id); + if (ret) + return ret; + + family = FIELD_PREP(LOWER_BYTE_MASK, + FIELD_GET(CPU_ID_FAMILY_MASK, cpu_id)) | + FIELD_PREP(UPPER_BYTE_MASK, + FIELD_GET(CPU_ID_EXT_FAMILY_MASK, cpu_id)); + model = FIELD_PREP(LOWER_NIBBLE_MASK, + FIELD_GET(CPU_ID_MODEL_MASK, cpu_id)) | + FIELD_PREP(UPPER_NIBBLE_MASK, + FIELD_GET(CPU_ID_EXT_MODEL_MASK, cpu_id)); + + for (i = 0; i < ARRAY_SIZE(cpu_gen_info_table); i++) { + const struct cpu_gen_info *cpu_info = &cpu_gen_info_table[i]; + + if (family == cpu_info->family && model == cpu_info->model) { + priv->gen_info = cpu_info; + break; + } + } + + if (!priv->gen_info) { + dev_err(dev, "Can't support this CPU: 0x%x\n", cpu_id); + ret = -ENODEV; + } + + return ret; +} + +static int peci_client_probe(struct peci_client *client) +{ + struct device *dev = &client->dev; + struct peci_client_manager *priv; + uint cpu_no; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + priv->client = client; + cpu_no = client->addr - PECI_BASE_ADDR; + + ret = peci_client_get_cpu_gen_info(priv); + if (ret) + return ret; + + ret = devm_mfd_add_devices(dev, cpu_no, peci_functions, + ARRAY_SIZE(peci_functions), NULL, 0, NULL); + if (ret < 0) { + dev_err(dev, "Failed to register child devices: %d\n", ret); + return ret; + } + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id peci_client_of_table[] = { + { .compatible = "intel,peci-client" }, + { } +}; +MODULE_DEVICE_TABLE(of, peci_client_of_table); +#endif + +static const struct peci_device_id peci_client_ids[] = { + { .name = "peci-client" }, + { } +}; +MODULE_DEVICE_TABLE(peci, peci_client_ids); + +static struct peci_driver peci_client_driver = { + .probe = peci_client_probe, + .id_table = peci_client_ids, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = of_match_ptr(peci_client_of_table), + }, +}; +module_peci_driver(peci_client_driver); + +MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>"); +MODULE_DESCRIPTION("PECI client driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index e1b1ba5e2b92..d8626a0d3e31 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -444,6 +444,13 @@ config XILINX_SDFEC If unsure, say N. +config MCTP_LPC + tristate "MCTP LPC binding implementation for ASPEED BMCs" + depends on REGMAP + help + Implements the MCTP LPC binding via KCS LPC IO cycles for control and + LPC FWH cycles for data + config MISC_RTSX tristate default MISC_RTSX_PCI || MISC_RTSX_USB @@ -456,6 +463,21 @@ config PVPANIC a paravirtualized device provided by QEMU; it lets a virtual machine (guest) communicate panic events to the host. +config NPCM7XX_LPC_BPC + tristate "NPCM7xx LPC BIOS Post Code support" + depends on (ARCH_NPCM7XX || COMPILE_TEST) + help + Provides a NPCM7xx driver to control the LPC BIOS Post Code + interface which allows the BMC to monitoring and save + the data written by the host to an arbitrary LPC I/O port. + +config NPCM7XX_PCI_MBOX + tristate "NPCM7xx PCI Mailbox Controller" + depends on (ARCH_NPCM7XX || COMPILE_TEST) && REGMAP && MFD_SYSCON + help + Expose the NPCM750/730/715/705 PCI MBOX registers found on + Nuvoton SOCs to userspace. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index c7bd01ac6291..183970192ced 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -57,3 +57,6 @@ obj-$(CONFIG_PVPANIC) += pvpanic.o obj-$(CONFIG_HABANA_AI) += habanalabs/ obj-$(CONFIG_UACCE) += uacce/ obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o +obj-$(CONFIG_NPCM7XX_LPC_BPC) += npcm7xx-lpc-bpc.o +obj-$(CONFIG_NPCM7XX_PCI_MBOX) += npcm7xx-pci-mbox.o +obj-$(CONFIG_MCTP_LPC) += mctp-lpc.o diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c index cde9a2fc1325..f3863929f8e3 100644 --- a/drivers/misc/eeprom/at25.c +++ b/drivers/misc/eeprom/at25.c @@ -64,12 +64,17 @@ static int at25_ee_read(void *priv, unsigned int offset, { struct at25_data *at25 = priv; char *buf = val; + size_t max_chunk = spi_max_transfer_size(at25->spi); + size_t num_msgs = DIV_ROUND_UP(count, max_chunk); + size_t nr_bytes = 0; u8 command[EE_MAXADDRLEN + 1]; u8 *cp; ssize_t status; struct spi_transfer t[2]; struct spi_message m; u8 instr; + unsigned int msg_offset; + size_t msg_count; if (unlikely(offset >= at25->chip.byte_len)) return -EINVAL; @@ -78,57 +83,64 @@ static int at25_ee_read(void *priv, unsigned int offset, if (unlikely(!count)) return -EINVAL; - cp = command; - - instr = AT25_READ; - if (at25->chip.flags & EE_INSTR_BIT3_IS_ADDR) - if (offset >= (1U << (at25->addrlen * 8))) - instr |= AT25_INSTR_BIT3; - *cp++ = instr; - - /* 8/16/24-bit address is written MSB first */ - switch (at25->addrlen) { - default: /* case 3 */ - *cp++ = offset >> 16; - /* fall through */ - case 2: - *cp++ = offset >> 8; - /* fall through */ - case 1: - case 0: /* can't happen: for better codegen */ - *cp++ = offset >> 0; - } + msg_offset = (unsigned int)offset; + msg_count = min(count, max_chunk); + while (num_msgs) { + cp = command; - spi_message_init(&m); - memset(t, 0, sizeof(t)); + instr = AT25_READ; + if (at25->chip.flags & EE_INSTR_BIT3_IS_ADDR) + if (msg_offset >= (1U << (at25->addrlen * 8))) + instr |= AT25_INSTR_BIT3; + *cp++ = instr; - t[0].tx_buf = command; - t[0].len = at25->addrlen + 1; - spi_message_add_tail(&t[0], &m); + /* 8/16/24-bit address is written MSB first */ + switch (at25->addrlen) { + default: /* case 3 */ + *cp++ = msg_offset >> 16; + /* fall through */ + case 2: + *cp++ = msg_offset >> 8; + /* fall through */ + case 1: + case 0: /* can't happen: for better codegen */ + *cp++ = msg_offset >> 0; + } - t[1].rx_buf = buf; - t[1].len = count; - spi_message_add_tail(&t[1], &m); + spi_message_init(&m); + memset(t, 0, sizeof(t)); - mutex_lock(&at25->lock); + t[0].tx_buf = command; + t[0].len = at25->addrlen + 1; + spi_message_add_tail(&t[0], &m); - /* Read it all at once. - * - * REVISIT that's potentially a problem with large chips, if - * other devices on the bus need to be accessed regularly or - * this chip is clocked very slowly - */ - status = spi_sync(at25->spi, &m); - dev_dbg(&at25->spi->dev, "read %zu bytes at %d --> %zd\n", - count, offset, status); + t[1].rx_buf = buf + nr_bytes; + t[1].len = msg_count; + spi_message_add_tail(&t[1], &m); - mutex_unlock(&at25->lock); - return status; + mutex_lock(&at25->lock); + + status = spi_sync(at25->spi, &m); + + mutex_unlock(&at25->lock); + + if (status) + return status; + + --num_msgs; + msg_offset += msg_count; + nr_bytes += msg_count; + } + + dev_dbg(&at25->spi->dev, "read %zu bytes at %d\n", + count, offset); + return 0; } static int at25_ee_write(void *priv, unsigned int off, void *val, size_t count) { struct at25_data *at25 = priv; + size_t maxsz = spi_max_transfer_size(at25->spi); const char *buf = val; int status = 0; unsigned buf_size; @@ -191,6 +203,8 @@ static int at25_ee_write(void *priv, unsigned int off, void *val, size_t count) segment = buf_size - (offset % buf_size); if (segment > count) segment = count; + if (segment > maxsz) + segment = maxsz; memcpy(cp, buf, segment); status = spi_write(at25->spi, bounce, segment + at25->addrlen + 1); diff --git a/drivers/misc/mctp-lpc.c b/drivers/misc/mctp-lpc.c new file mode 100644 index 000000000000..71fc4ae69de7 --- /dev/null +++ b/drivers/misc/mctp-lpc.c @@ -0,0 +1,443 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2019, IBM Corp. + */ + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/regmap.h> +#include <linux/sched/signal.h> +#include <linux/uaccess.h> +#include <linux/wait.h> + +#define LPC_HICRB 0x080 +#define LPC_HICRB_IBFIF4 BIT(1) +#define LPC_HICRB_LPC4E BIT(0) +#define LPC_HICRC 0x084 +#define LPC_KCS4_IRQSEL_MASK GENMASK(7, 4) +#define LPC_KCS4_IRQSEL_SHIFT 4 +#define LPC_KCS4_IRQTYPE_MASK GENMASK(3, 2) +#define LPC_KCS4_IRQTYPE_SHIFT 2 +#define LPC_KCS4_IRQTYPE_LOW 0b00 +#define LPC_KCS4_IRQTYPE_HIGH 0b01 +#define LPC_KCS4_IRQTYPE_RSVD 0b10 +#define LPC_KCS4_IRQTYPE_RISING 0b11 +#define LPC_KCS4_OBF4_AUTO_CLR BIT(1) +#define LPC_KCS4_IRQ_HOST BIT(0) +#define LPC_LADR4 0x090 +#define LPC_IDR4 0x094 +#define LPC_ODR4 0x098 +#define LPC_STR4 0x09C +#define STR4_IBF (1 << 1) +#define STR4_OBF (1 << 0) + +#define HOST_ODR 0xca2 +#define HOST_STR 0xca3 +#define HOST_SERIRQ_ID 11 +#define HOST_SERIRQ_TYPE LPC_KCS4_IRQTYPE_LOW + +#define RX_BUF_SIZE 1024 + +struct mctp_lpc { + struct miscdevice miscdev; + struct regmap *map; + + wait_queue_head_t rx; + bool pending; + u8 idr; +}; + +static irqreturn_t mctp_lpc_irq(int irq, void *data) +{ + struct mctp_lpc *priv = data; + unsigned long flags; + unsigned int hicrb; + struct device *dev; + unsigned int str; + irqreturn_t ret; + + dev = priv->miscdev.this_device; + + spin_lock_irqsave(&priv->rx.lock, flags); + + regmap_read(priv->map, LPC_STR4, &str); + regmap_read(priv->map, LPC_HICRB, &hicrb); + + if ((str & STR4_IBF) && (hicrb & LPC_HICRB_IBFIF4)) { + unsigned int val; + + if (priv->pending) + dev_err(dev, "Storm brewing!"); + + /* Mask the IRQ / Enter polling mode */ + dev_dbg(dev, "Received IRQ %d, disabling to provide back-pressure\n", + irq); + regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_IBFIF4, 0); + + /* + * Extract the IDR4 value to ack the IRQ. Reading IDR clears + * IBF and allows the host to write another value, however as + * we have disabled IRQs the back-pressure is still applied + * until userspace starts servicing the interface. + */ + regmap_read(priv->map, LPC_IDR4, &val); + priv->idr = val & 0xff; + priv->pending = true; + + dev_dbg(dev, "Set pending, waking waiters\n"); + wake_up_locked(&priv->rx); + ret = IRQ_HANDLED; + } else { + dev_dbg(dev, "LPC IRQ triggered, but not for us (str=0x%x, hicrb=0x%x)\n", + str, hicrb); + ret = IRQ_NONE; + } + + spin_unlock_irqrestore(&priv->rx.lock, flags); + + return ret; +} + +static inline struct mctp_lpc *to_mctp_lpc(struct file *filp) +{ + return container_of(filp->private_data, struct mctp_lpc, miscdev); +} + +static ssize_t mctp_lpc_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct mctp_lpc *priv; + struct device *dev; + size_t remaining; + ssize_t rc; + + priv = to_mctp_lpc(filp); + dev = priv->miscdev.this_device; + + if (!count) + return 0; + + if (count > 2 || *ppos > 1) + return -EINVAL; + + remaining = count; + + spin_lock_irq(&priv->rx.lock); + if (*ppos == 0) { + unsigned int val; + u8 str; + + /* YOLO blocking, non-block not supported */ + dev_dbg(dev, "Waiting for IBF\n"); + regmap_read(priv->map, LPC_STR4, &val); + str = val & 0xff; + rc = wait_event_interruptible_locked(priv->rx, (priv->pending || str & STR4_IBF)); + if (rc < 0) + goto out; + + if (signal_pending(current)) { + dev_dbg(dev, "Interrupted waiting for IBF\n"); + rc = -EINTR; + goto out; + } + + /* + * Re-enable IRQs prior to possible read of IDR (which clears + * IBF) to ensure we receive interrupts for subsequent writes + * to IDR. Writes to IDR by the host should not occur while IBF + * is set. + */ + dev_dbg(dev, "Woken by IBF, enabling IRQ\n"); + regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_IBFIF4, + LPC_HICRB_IBFIF4); + + /* Read data out of IDR into internal storage if necessary */ + if (!priv->pending) { + WARN(!(str & STR4_IBF), "Unknown reason for wakeup!"); + + /* Extract the IDR4 value to ack the IRQ */ + regmap_read(priv->map, LPC_IDR4, &val); + priv->idr = val & 0xff; + } + + /* Copy data from internal storage to userspace */ + if (copy_to_user(buf, &priv->idr, sizeof(priv->idr))) { + rc = -EFAULT; + goto out; + } + + /* We're done consuming the internally stored value */ + priv->pending = false; + + remaining--; + buf++; + } + + if (remaining) { + /* Either: + * + * 1. (count == 1 && *ppos == 1) + * 2. (count == 2 && *ppos == 0) + */ + unsigned int val; + u8 str; + + regmap_read(priv->map, LPC_STR4, &val); + str = val & 0xff; + if (*ppos == 0 || priv->pending) + /* + * If we got this far with `*ppos == 0` then we've read + * data out of IDR, so set IBF when reporting back to + * userspace so userspace knows the IDR value is valid. + */ + str |= STR4_IBF; + + dev_dbg(dev, "Read status 0x%x\n", str); + if (copy_to_user(buf, &str, sizeof(str))) { + rc = -EFAULT; + goto out; + } + + remaining--; + } + + WARN_ON(remaining); + + rc = count; + +out: + spin_unlock_irq(&priv->rx.lock); + + return rc; +} + +static ssize_t mctp_lpc_write(struct file *filp, const char __user *buf, + size_t count, loff_t *ppos) +{ + uint8_t _data[2], *data = &_data[0]; + struct mctp_lpc *priv; + struct device *dev; + size_t remaining; + unsigned int str; + + priv = to_mctp_lpc(filp); + dev = priv->miscdev.this_device; + + if (!count) + return count; + + if (count > 2) + return -EINVAL; + + if (*ppos >= 2) + return -EINVAL; + + if (*ppos + count > 2) + return -EINVAL; + + if (copy_from_user(data, buf, count)) + return -EFAULT; + + remaining = count; + + if (*ppos == 0) { + /* Wait until OBF is clear - we don't get an IRQ */ + dev_dbg(dev, "Waiting for OBF to clear\n"); + for (;;) { + if (signal_pending(current)) + return -EINTR; + + regmap_read(priv->map, LPC_STR4, &str); + if (!(str & STR4_OBF)) + break; + + msleep(1); + } + + dev_dbg(dev, "Writing 0x%x to ODR\n", *data); + regmap_write(priv->map, LPC_ODR4, *data); + remaining--; + data++; + } + + if (remaining) { + if (!(*data & STR4_OBF)) + dev_err(dev, "Clearing OBF with status write: 0x%x\n", + *data); + dev_dbg(dev, "Writing status 0x%x\n", *data); + regmap_write(priv->map, LPC_STR4, *data); + remaining--; + } + + WARN_ON(remaining); + + regmap_read(priv->map, LPC_STR4, &str); + dev_dbg(dev, "Triggering SerIRQ (current str=0x%x)\n", str); + + /* + * Trigger Host IRQ on ODR write. Do this after any STR write in case + * we need to write ODR to indicate an STR update (which we do). + */ + if (*ppos == 0) + regmap_update_bits(priv->map, LPC_HICRC, LPC_KCS4_IRQ_HOST, + LPC_KCS4_IRQ_HOST); + + return count; +} + +static __poll_t mctp_lpc_poll(struct file *filp, poll_table *wait) +{ + struct mctp_lpc *priv; + struct device *dev; + unsigned int val; + bool ibf; + + priv = to_mctp_lpc(filp); + dev = priv->miscdev.this_device; + + regmap_read(priv->map, LPC_STR4, &val); + + spin_lock_irq(&priv->rx.lock); + + ibf = priv->pending || val & STR4_IBF; + + if (!ibf) { + dev_dbg(dev, "Polling on IBF\n"); + + spin_unlock_irq(&priv->rx.lock); + + poll_wait(filp, &priv->rx, wait); + if (signal_pending(current)) { + dev_dbg(dev, "Polling IBF was interrupted\n"); + goto out; + } + + spin_lock_irq(&priv->rx.lock); + + regmap_read(priv->map, LPC_STR4, &val); + + ibf = priv->pending || val & STR4_IBF; + } + + spin_unlock_irq(&priv->rx.lock); + +out: + dev_dbg(dev, "Polled IBF state: %s\n", ibf ? "set" : "clear"); + + return ibf ? EPOLLIN : 0; +} + +static const struct file_operations mctp_lpc_fops = { + .owner = THIS_MODULE, + .llseek = no_seek_end_llseek, + .read = mctp_lpc_read, + .write = mctp_lpc_write, + .poll = mctp_lpc_poll, +}; + +static int mctp_lpc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + unsigned int mask, val; + struct mctp_lpc *priv; + int irq; + int rc; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->map = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(priv->map)) { + dev_err(dev, "Couldn't get regmap\n"); + return -ENODEV; + } + + /* + * Set the LPC address. Simultaneously, test our MMIO regmap works. All + * subsequent accesses are assumed to work + */ + rc = regmap_write(priv->map, LPC_LADR4, ((HOST_STR) << 16) | HOST_ODR); + if (rc < 0) + return rc; + + /* Set up the SerIRQ */ + mask = LPC_KCS4_IRQSEL_MASK + | LPC_KCS4_IRQTYPE_MASK + | LPC_KCS4_OBF4_AUTO_CLR; + val = (HOST_SERIRQ_ID << LPC_KCS4_IRQSEL_SHIFT) + | (HOST_SERIRQ_TYPE << LPC_KCS4_IRQTYPE_SHIFT); + val &= ~LPC_KCS4_OBF4_AUTO_CLR; /* Unnecessary, just documentation */ + regmap_update_bits(priv->map, LPC_HICRC, mask, val); + + /* Trigger waiters from IRQ */ + init_waitqueue_head(&priv->rx); + + dev_set_drvdata(dev, priv); + + /* Set up the miscdevice */ + priv->miscdev.minor = MISC_DYNAMIC_MINOR; + priv->miscdev.name = "mctp0"; + priv->miscdev.fops = &mctp_lpc_fops; + + /* Configure the IRQ handler */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + rc = devm_request_irq(dev, irq, mctp_lpc_irq, IRQF_SHARED, + dev_name(dev), priv); + if (rc < 0) + return rc; + + /* Register the device */ + rc = misc_register(&priv->miscdev); + if (rc) { + dev_err(dev, "Unable to register device\n"); + return rc; + } + + /* Enable the channel */ + regmap_update_bits(priv->map, LPC_HICRB, + LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E, + LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E); + + return 0; +} + +static int mctp_lpc_remove(struct platform_device *pdev) +{ + struct mctp_lpc *ctx = dev_get_drvdata(&pdev->dev); + + misc_deregister(&ctx->miscdev); + + return 0; +} + +static const struct of_device_id mctp_lpc_match[] = { + { .compatible = "openbmc,mctp-lpc" }, + { } +}; +MODULE_DEVICE_TABLE(of, mctp_lpc_match); + +static struct platform_driver mctp_lpc = { + .driver = { + .name = "mctp-lpc", + .of_match_table = mctp_lpc_match, + }, + .probe = mctp_lpc_probe, + .remove = mctp_lpc_remove, +}; +module_platform_driver(mctp_lpc); + +MODULE_LICENSE("GPL v2+"); +MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>"); +MODULE_DESCRIPTION("OpenBMC MCTP LPC binding on ASPEED KCS"); diff --git a/drivers/misc/npcm7xx-lpc-bpc.c b/drivers/misc/npcm7xx-lpc-bpc.c new file mode 100644 index 000000000000..e014e07cd4a4 --- /dev/null +++ b/drivers/misc/npcm7xx-lpc-bpc.c @@ -0,0 +1,394 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2014-2018 Nuvoton Technology corporation. + +#include <linux/fs.h> +#include <linux/bitops.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/miscdevice.h> +#include <linux/poll.h> + +#define DEVICE_NAME "npcm7xx-lpc-bpc" + +#define NUM_BPC_CHANNELS 2 +#define DW_PAD_SIZE 3 + +/* BIOS POST Code FIFO Registers */ +#define NPCM7XX_BPCFA2L_REG 0x2 //BIOS POST Code FIFO Address 2 LSB +#define NPCM7XX_BPCFA2M_REG 0x4 //BIOS POST Code FIFO Address 2 MSB +#define NPCM7XX_BPCFEN_REG 0x6 //BIOS POST Code FIFO Enable +#define NPCM7XX_BPCFSTAT_REG 0x8 //BIOS POST Code FIFO Status +#define NPCM7XX_BPCFDATA_REG 0xA //BIOS POST Code FIFO Data +#define NPCM7XX_BPCFMSTAT_REG 0xC //BIOS POST Code FIFO Miscellaneous Status +#define NPCM7XX_BPCFA1L_REG 0x10 //BIOS POST Code FIFO Address 1 LSB +#define NPCM7XX_BPCFA1M_REG 0x12 //BIOS POST Code FIFO Address 1 MSB + +/*BIOS regiser data*/ +#define FIFO_IOADDR1_ENABLE 0x80 +#define FIFO_IOADDR2_ENABLE 0x40 + +/* BPC interface package and structure definition */ +#define BPC_KFIFO_SIZE 0x400 + +/*BPC regiser data*/ +#define FIFO_DATA_VALID 0x80 +#define FIFO_OVERFLOW 0x20 +#define FIFO_READY_INT_ENABLE 0x8 +#define FIFO_DWCAPTURE 0x4 +#define FIFO_ADDR_DECODE 0x1 + +/*Host Reset*/ +#define HOST_RESET_INT_ENABLE 0x10 +#define HOST_RESET_CHANGED 0x40 + +struct npcm7xx_bpc_channel { + struct npcm7xx_bpc *data; + struct kfifo fifo; + wait_queue_head_t wq; + bool host_reset; + struct miscdevice miscdev; +}; + +struct npcm7xx_bpc { + void __iomem *base; + int irq; + bool en_dwcap; + struct npcm7xx_bpc_channel ch[NUM_BPC_CHANNELS]; +}; + +static struct npcm7xx_bpc_channel *npcm7xx_file_to_ch(struct file *file) +{ + return container_of(file->private_data, struct npcm7xx_bpc_channel, + miscdev); +} + +static ssize_t npcm7xx_bpc_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct npcm7xx_bpc_channel *chan = npcm7xx_file_to_ch(file); + struct npcm7xx_bpc *lpc_bpc = chan->data; + unsigned int copied; + int ret = 0; + int cond_size = 1; + + if (lpc_bpc->en_dwcap) + cond_size = 3; + + if (kfifo_len(&chan->fifo) < cond_size) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + ret = wait_event_interruptible + (chan->wq, kfifo_len(&chan->fifo) > cond_size); + if (ret == -ERESTARTSYS) + return -EINTR; + } + + ret = kfifo_to_user(&chan->fifo, buffer, count, &copied); + + return ret ? ret : copied; +} + +static __poll_t npcm7xx_bpc_poll(struct file *file, + struct poll_table_struct *pt) +{ + struct npcm7xx_bpc_channel *chan = npcm7xx_file_to_ch(file); + __poll_t mask = 0; + + poll_wait(file, &chan->wq, pt); + if (!kfifo_is_empty(&chan->fifo)) + mask |= POLLIN; + + if (chan->host_reset) { + mask |= POLLHUP; + chan->host_reset = false; + } + + return mask; +} + +static const struct file_operations npcm7xx_bpc_fops = { + .owner = THIS_MODULE, + .read = npcm7xx_bpc_read, + .poll = npcm7xx_bpc_poll, + .llseek = noop_llseek, +}; + +static irqreturn_t npcm7xx_bpc_irq(int irq, void *arg) +{ + struct npcm7xx_bpc *lpc_bpc = arg; + u8 fifo_st; + u8 host_st; + u8 addr_index = 0; + u8 Data; + u8 padzero[3] = {0}; + u8 last_addr_bit = 0; + bool isr_flag = false; + + fifo_st = ioread8(lpc_bpc->base + NPCM7XX_BPCFSTAT_REG); + while (FIFO_DATA_VALID & fifo_st) { + /* If dwcapture enabled only channel 0 (FIFO 0) used */ + if (!lpc_bpc->en_dwcap) + addr_index = fifo_st & FIFO_ADDR_DECODE; + else + last_addr_bit = fifo_st & FIFO_ADDR_DECODE; + + /*Read data from FIFO to clear interrupt*/ + Data = ioread8(lpc_bpc->base + NPCM7XX_BPCFDATA_REG); + if (kfifo_is_full(&lpc_bpc->ch[addr_index].fifo)) + kfifo_skip(&lpc_bpc->ch[addr_index].fifo); + kfifo_put(&lpc_bpc->ch[addr_index].fifo, Data); + if (fifo_st & FIFO_OVERFLOW) + pr_info("BIOS Post Codes FIFO Overflow!!!\n"); + + fifo_st = ioread8(lpc_bpc->base + NPCM7XX_BPCFSTAT_REG); + if (lpc_bpc->en_dwcap && last_addr_bit) { + if ((fifo_st & FIFO_ADDR_DECODE) || + ((FIFO_DATA_VALID & fifo_st) == 0)) { + while (kfifo_avail(&lpc_bpc->ch[addr_index].fifo) < DW_PAD_SIZE) + kfifo_skip(&lpc_bpc->ch[addr_index].fifo); + kfifo_in(&lpc_bpc->ch[addr_index].fifo, + padzero, DW_PAD_SIZE); + } + } + isr_flag = true; + } + + host_st = ioread8(lpc_bpc->base + NPCM7XX_BPCFMSTAT_REG); + if (host_st & HOST_RESET_CHANGED) { + iowrite8(HOST_RESET_CHANGED, + lpc_bpc->base + NPCM7XX_BPCFMSTAT_REG); + lpc_bpc->ch[addr_index].host_reset = true; + isr_flag = true; + } + + if (isr_flag) { + wake_up_interruptible(&lpc_bpc->ch[addr_index].wq); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int npcm7xx_bpc_config_irq(struct npcm7xx_bpc *lpc_bpc, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int rc; + + lpc_bpc->irq = platform_get_irq(pdev, 0); + if (lpc_bpc->irq < 0) { + dev_err(dev, "get IRQ failed\n"); + return lpc_bpc->irq; + } + + rc = devm_request_irq(dev, lpc_bpc->irq, + npcm7xx_bpc_irq, IRQF_SHARED, + DEVICE_NAME, lpc_bpc); + if (rc < 0) { + dev_warn(dev, "Unable to request IRQ %d\n", lpc_bpc->irq); + return rc; + } + + return 0; +} + +static int npcm7xx_enable_bpc(struct npcm7xx_bpc *lpc_bpc, struct device *dev, + int channel, u16 lpc_port) +{ + int rc; + u8 addr_en, reg_en; + + init_waitqueue_head(&lpc_bpc->ch[channel].wq); + + rc = kfifo_alloc(&lpc_bpc->ch[channel].fifo, + BPC_KFIFO_SIZE, GFP_KERNEL); + if (rc) + return rc; + + lpc_bpc->ch[channel].miscdev.minor = MISC_DYNAMIC_MINOR; + lpc_bpc->ch[channel].miscdev.name = + devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, channel); + lpc_bpc->ch[channel].miscdev.fops = &npcm7xx_bpc_fops; + lpc_bpc->ch[channel].miscdev.parent = dev; + rc = misc_register(&lpc_bpc->ch[channel].miscdev); + if (rc) + return rc; + + lpc_bpc->ch[channel].data = lpc_bpc; + lpc_bpc->ch[channel].host_reset = false; + + /* Enable LPC snoop channel at requested port */ + switch (channel) { + case 0: + addr_en = FIFO_IOADDR1_ENABLE; + iowrite8((u8)lpc_port & 0xFF, + lpc_bpc->base + NPCM7XX_BPCFA1L_REG); + iowrite8((u8)(lpc_port >> 8), + lpc_bpc->base + NPCM7XX_BPCFA1M_REG); + break; + case 1: + addr_en = FIFO_IOADDR2_ENABLE; + iowrite8((u8)lpc_port & 0xFF, + lpc_bpc->base + NPCM7XX_BPCFA2L_REG); + iowrite8((u8)(lpc_port >> 8), + lpc_bpc->base + NPCM7XX_BPCFA2M_REG); + break; + default: + return -EINVAL; + } + + if (lpc_bpc->en_dwcap) + addr_en = FIFO_DWCAPTURE; + + /* + * Enable FIFO Ready Interrupt, FIFO Capture of I/O addr, + * and Host Reset + */ + reg_en = ioread8(lpc_bpc->base + NPCM7XX_BPCFEN_REG); + iowrite8(reg_en | addr_en | FIFO_READY_INT_ENABLE | + HOST_RESET_INT_ENABLE, lpc_bpc->base + NPCM7XX_BPCFEN_REG); + + return 0; +} + +static void npcm7xx_disable_bpc(struct npcm7xx_bpc *lpc_bpc, int channel) +{ + u8 reg_en; + + switch (channel) { + case 0: + reg_en = ioread8(lpc_bpc->base + NPCM7XX_BPCFEN_REG); + if (lpc_bpc->en_dwcap) + iowrite8(reg_en & ~FIFO_DWCAPTURE, + lpc_bpc->base + NPCM7XX_BPCFEN_REG); + else + iowrite8(reg_en & ~FIFO_IOADDR1_ENABLE, + lpc_bpc->base + NPCM7XX_BPCFEN_REG); + break; + case 1: + reg_en = ioread8(lpc_bpc->base + NPCM7XX_BPCFEN_REG); + iowrite8(reg_en & ~FIFO_IOADDR2_ENABLE, + lpc_bpc->base + NPCM7XX_BPCFEN_REG); + break; + default: + return; + } + + if (!(reg_en & (FIFO_IOADDR1_ENABLE | FIFO_IOADDR2_ENABLE))) + iowrite8(reg_en & + ~(FIFO_READY_INT_ENABLE | HOST_RESET_INT_ENABLE), + lpc_bpc->base + NPCM7XX_BPCFEN_REG); + + kfifo_free(&lpc_bpc->ch[channel].fifo); + misc_deregister(&lpc_bpc->ch[channel].miscdev); +} + +static int npcm7xx_bpc_probe(struct platform_device *pdev) +{ + struct npcm7xx_bpc *lpc_bpc; + struct resource *res; + struct device *dev; + u32 port; + int rc; + + dev = &pdev->dev; + + lpc_bpc = devm_kzalloc(dev, sizeof(*lpc_bpc), GFP_KERNEL); + if (!lpc_bpc) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "BIOS post code reg resource not found\n"); + return -ENODEV; + } + + dev_dbg(dev, "BIOS post code base resource is %pR\n", res); + lpc_bpc->base = devm_ioremap_resource(dev, res); + if (IS_ERR(lpc_bpc->base)) + return PTR_ERR(lpc_bpc->base); + + dev_set_drvdata(&pdev->dev, lpc_bpc); + + rc = of_property_read_u32_index(dev->of_node, "monitor-ports", 0, + &port); + if (rc) { + dev_err(dev, "no monitor ports configured\n"); + return -ENODEV; + } + + lpc_bpc->en_dwcap = + of_property_read_bool(dev->of_node, "bpc-en-dwcapture"); + + rc = npcm7xx_bpc_config_irq(lpc_bpc, pdev); + if (rc) + return rc; + + rc = npcm7xx_enable_bpc(lpc_bpc, dev, 0, port); + if (rc) { + dev_err(dev, "Enable BIOS post code I/O port 0 failed\n"); + return rc; + } + + /* + * Configuration of second BPC channel port is optional + * Double-Word Capture ignoring address 2 + */ + if (!lpc_bpc->en_dwcap) { + if (of_property_read_u32_index(dev->of_node, "monitor-ports", + 1, &port) == 0) { + rc = npcm7xx_enable_bpc(lpc_bpc, dev, 1, port); + if (rc) { + dev_err(dev, "Enable BIOS post code I/O port 1 failed, disable I/O port 0\n"); + npcm7xx_disable_bpc(lpc_bpc, 0); + return rc; + } + } + } + + pr_info("npcm7xx BIOS post code probe\n"); + + return rc; +} + +static int npcm7xx_bpc_remove(struct platform_device *pdev) +{ + struct npcm7xx_bpc *lpc_bpc = dev_get_drvdata(&pdev->dev); + u8 reg_en; + + reg_en = ioread8(lpc_bpc->base + NPCM7XX_BPCFEN_REG); + + if (reg_en & FIFO_IOADDR1_ENABLE) + npcm7xx_disable_bpc(lpc_bpc, 0); + if (reg_en & FIFO_IOADDR2_ENABLE) + npcm7xx_disable_bpc(lpc_bpc, 1); + + return 0; +} + +static const struct of_device_id npcm7xx_bpc_match[] = { + { .compatible = "nuvoton,npcm750-lpc-bpc" }, + { }, +}; + +static struct platform_driver npcm7xx_bpc_driver = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = npcm7xx_bpc_match, + }, + .probe = npcm7xx_bpc_probe, + .remove = npcm7xx_bpc_remove, +}; + +module_platform_driver(npcm7xx_bpc_driver); + +MODULE_DEVICE_TABLE(of, npcm7xx_bpc_match); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Tomer Maimon <tomer.maimon@nuvoton.com>"); +MODULE_DESCRIPTION("Linux driver to control NPCM7XX LPC BIOS post code monitoring"); diff --git a/drivers/misc/npcm7xx-pci-mbox.c b/drivers/misc/npcm7xx-pci-mbox.c new file mode 100644 index 000000000000..1a80661a4296 --- /dev/null +++ b/drivers/misc/npcm7xx-pci-mbox.c @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2014-2018 Nuvoton Technology corporation. + +#include <linux/interrupt.h> +#include <linux/mfd/syscon.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#define DEVICE_NAME "npcm7xx-pci-mbox" + +#define NPCM7XX_MBOX_BMBXSTAT 0x0 +#define NPCM7XX_MBOX_BMBXCTL 0x4 +#define NPCM7XX_MBOX_BMBXCMD 0x8 + +#define NPCM7XX_MBOX_CIF_0 BIT(0) +#define NPCM7XX_MBOX_CIE_0 BIT(0) +#define NPCM7XX_MBOX_HIF_0 BIT(0) + +#define NPCM7XX_MBOX_ALL_CIF GENMASK(7, 0) +#define NPCM7XX_MBOX_ALL_CIE GENMASK(7, 0) +#define NPCM7XX_MBOX_ALL_HIF GENMASK(7, 0) + +struct npcm7xx_mbox { + struct miscdevice miscdev; + struct regmap *regmap; + void __iomem *memory; + wait_queue_head_t queue; + spinlock_t lock; /* mbox access mutex */ + bool cif0; + u32 max_buf_size; +}; + +static atomic_t npcm7xx_mbox_open_count = ATOMIC_INIT(0); + +static struct npcm7xx_mbox *file_mbox(struct file *file) +{ + return container_of(file->private_data, struct npcm7xx_mbox, miscdev); +} + +static int npcm7xx_mbox_open(struct inode *inode, struct file *file) +{ + struct npcm7xx_mbox *mbox = file_mbox(file); + + if (atomic_inc_return(&npcm7xx_mbox_open_count) == 1) { + /* enable mailbox interrupt */ + regmap_update_bits(mbox->regmap, NPCM7XX_MBOX_BMBXCTL, + NPCM7XX_MBOX_ALL_CIE, NPCM7XX_MBOX_CIE_0); + return 0; + } + + atomic_dec(&npcm7xx_mbox_open_count); + return -EBUSY; +} + +static ssize_t npcm7xx_mbox_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct npcm7xx_mbox *mbox = file_mbox(file); + unsigned long flags; + + if (!access_ok(buf, count)) + return -EFAULT; + + if ((*ppos + count) > mbox->max_buf_size) + return -EINVAL; + + if (file->f_flags & O_NONBLOCK) { + if (!mbox->cif0) + return -EAGAIN; + } else if (wait_event_interruptible(mbox->queue, mbox->cif0)) { + return -ERESTARTSYS; + } + + spin_lock_irqsave(&mbox->lock, flags); + + if (copy_to_user((void __user *)buf, + (const void *)(mbox->memory + *ppos), count)) { + spin_unlock_irqrestore(&mbox->lock, flags); + return -EFAULT; + } + + mbox->cif0 = false; + spin_unlock_irqrestore(&mbox->lock, flags); + return count; +} + +static ssize_t npcm7xx_mbox_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct npcm7xx_mbox *mbox = file_mbox(file); + unsigned long flags; + + if (!access_ok(buf, count)) + return -EFAULT; + + if ((*ppos + count) > mbox->max_buf_size) + return -EINVAL; + + spin_lock_irqsave(&mbox->lock, flags); + + if (copy_from_user((void *)(mbox->memory + *ppos), + (void __user *)buf, count)) { + spin_unlock_irqrestore(&mbox->lock, flags); + return -EFAULT; + } + + regmap_update_bits(mbox->regmap, NPCM7XX_MBOX_BMBXCMD, + NPCM7XX_MBOX_ALL_HIF, NPCM7XX_MBOX_HIF_0); + + spin_unlock_irqrestore(&mbox->lock, flags); + return count; +} + +static unsigned int npcm7xx_mbox_poll(struct file *file, poll_table *wait) +{ + struct npcm7xx_mbox *mbox = file_mbox(file); + unsigned int mask = 0; + + poll_wait(file, &mbox->queue, wait); + if (mbox->cif0) + mask |= POLLIN; + + return mask; +} + +static int npcm7xx_mbox_release(struct inode *inode, struct file *file) +{ + atomic_dec(&npcm7xx_mbox_open_count); + return 0; +} + +static const struct file_operations npcm7xx_mbox_fops = { + .owner = THIS_MODULE, + .llseek = no_seek_end_llseek, + .read = npcm7xx_mbox_read, + .write = npcm7xx_mbox_write, + .open = npcm7xx_mbox_open, + .release = npcm7xx_mbox_release, + .poll = npcm7xx_mbox_poll, +}; + +static irqreturn_t npcm7xx_mbox_irq(int irq, void *arg) +{ + struct npcm7xx_mbox *mbox = arg; + u32 val; + + regmap_read(mbox->regmap, NPCM7XX_MBOX_BMBXSTAT, &val); + if ((val & NPCM7XX_MBOX_CIF_0) != NPCM7XX_MBOX_CIF_0) + return IRQ_NONE; + + /* + * Leave the status bit set so that we know the data is for us, + * clear it once it has been read. + */ + mbox->cif0 = true; + + /* Mask it off, we'll clear it when we the data gets read */ + regmap_write_bits(mbox->regmap, NPCM7XX_MBOX_BMBXSTAT, + NPCM7XX_MBOX_ALL_CIF, NPCM7XX_MBOX_CIF_0); + + wake_up(&mbox->queue); + + return IRQ_HANDLED; +} + +static int npcm7xx_mbox_config_irq(struct npcm7xx_mbox *mbox, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int rc, irq; + u32 val; + + /* Disable all register based interrupts */ + regmap_update_bits(mbox->regmap, NPCM7XX_MBOX_BMBXCTL, + NPCM7XX_MBOX_ALL_CIE, 0); +/* + * These registers are write one to clear. Clear them. + * Per spec, cleared bits should not be re-cleared. + * Need to read and clear needed bits only, instead of blindly clearing all. + */ + regmap_read(mbox->regmap, NPCM7XX_MBOX_BMBXSTAT, &val); + val &= NPCM7XX_MBOX_ALL_CIF; + + /* If any bit is set, write back to clear */ + if (val) + regmap_write_bits(mbox->regmap, NPCM7XX_MBOX_BMBXSTAT, + NPCM7XX_MBOX_ALL_CIF, val); + + irq = irq_of_parse_and_map(dev->of_node, 0); + if (!irq) + return -ENODEV; + + rc = devm_request_irq(dev, irq, npcm7xx_mbox_irq, 0, DEVICE_NAME, mbox); + if (rc < 0) { + dev_err(dev, "Unable to request IRQ %d\n", irq); + return rc; + } + + return 0; +} + +static int npcm7xx_mbox_probe(struct platform_device *pdev) +{ + struct npcm7xx_mbox *mbox; + struct device *dev; + struct resource *res; + int rc; + + dev = &pdev->dev; + + mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, mbox); + + mbox->regmap = syscon_node_to_regmap(dev->of_node); + if (IS_ERR(mbox->regmap)) { + dev_err(dev, "Couldn't get regmap\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + mbox->memory = devm_ioremap_resource(dev, res); + if (IS_ERR(mbox->memory)) + return PTR_ERR(mbox->memory); + mbox->max_buf_size = resource_size(res); + + spin_lock_init(&mbox->lock); + init_waitqueue_head(&mbox->queue); + + mbox->miscdev.minor = MISC_DYNAMIC_MINOR; + mbox->miscdev.name = DEVICE_NAME; + mbox->miscdev.fops = &npcm7xx_mbox_fops; + mbox->miscdev.parent = dev; + mbox->cif0 = false; + rc = misc_register(&mbox->miscdev); + if (rc) { + dev_err(dev, "Unable to register device\n"); + return rc; + } + + rc = npcm7xx_mbox_config_irq(mbox, pdev); + if (rc) { + dev_err(dev, "Failed to configure IRQ\n"); + misc_deregister(&mbox->miscdev); + return rc; + } + + pr_info("NPCM7xx PCI Mailbox probed\n"); + + return 0; +} + +static int npcm7xx_mbox_remove(struct platform_device *pdev) +{ + struct npcm7xx_mbox *mbox = dev_get_drvdata(&pdev->dev); + + misc_deregister(&mbox->miscdev); + + return 0; +} + +static const struct of_device_id npcm7xx_mbox_match[] = { + { .compatible = "nuvoton,npcm750-pci-mbox" }, + { }, +}; + +static struct platform_driver npcm7xx_mbox_driver = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = npcm7xx_mbox_match, + }, + .probe = npcm7xx_mbox_probe, + .remove = npcm7xx_mbox_remove, +}; + +module_platform_driver(npcm7xx_mbox_driver); + +MODULE_DEVICE_TABLE(of, npcm7xx_mbox_match); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Tomer Maimon <tomer.maimon@nuvoton.com>"); +MODULE_DESCRIPTION("NPCM7XX mailbox device driver"); diff --git a/drivers/mtd/spi-nor/controllers/aspeed-smc.c b/drivers/mtd/spi-nor/controllers/aspeed-smc.c index 7225870e8b18..a591ce1d24c5 100644 --- a/drivers/mtd/spi-nor/controllers/aspeed-smc.c +++ b/drivers/mtd/spi-nor/controllers/aspeed-smc.c @@ -6,6 +6,7 @@ */ #include <linux/bug.h> +#include <linux/clk.h> #include <linux/device.h> #include <linux/io.h> #include <linux/module.h> @@ -16,10 +17,13 @@ #include <linux/of.h> #include <linux/of_platform.h> #include <linux/sizes.h> +#include <linux/slab.h> #include <linux/sysfs.h> #define DEVICE_NAME "aspeed-smc" +#define SNOR_F_4B_OPCODES BIT(6) + /* * The driver only support SPI flash */ @@ -30,6 +34,7 @@ enum aspeed_smc_flash_type { }; struct aspeed_smc_chip; +struct aspeed_smc_controller; struct aspeed_smc_info { u32 maxsize; /* maximum size of chip window */ @@ -37,12 +42,34 @@ struct aspeed_smc_info { bool hastype; /* flash type field exists in config reg */ u8 we0; /* shift for write enable bit for CE0 */ u8 ctl0; /* offset in regs of ctl for CE0 */ + u8 timing; /* offset in regs of timing */ + u32 hclk_mask; /* clock frequency mask in CEx Control reg */ + u32 hdiv_max; /* Max HCLK divisor on read timing reg */ void (*set_4b)(struct aspeed_smc_chip *chip); + int (*optimize_read)(struct aspeed_smc_chip *chip, u32 max_freq); + int (*calibrate)(struct aspeed_smc_chip *chip, u32 hdiv, + const u8 *golden_buf, u8 *test_buf); + + u32 (*segment_start)(struct aspeed_smc_controller *controller, u32 reg); + u32 (*segment_end)(struct aspeed_smc_controller *controller, u32 reg); + u32 (*segment_reg)(struct aspeed_smc_controller *controller, + u32 start, u32 end); }; static void aspeed_smc_chip_set_4b_spi_2400(struct aspeed_smc_chip *chip); static void aspeed_smc_chip_set_4b(struct aspeed_smc_chip *chip); +static int aspeed_smc_optimize_read(struct aspeed_smc_chip *chip, + u32 max_freq); +static int aspeed_smc_calibrate_reads(struct aspeed_smc_chip *chip, u32 hdiv, + const u8 *golden_buf, u8 *test_buf); + +static u32 aspeed_smc_segment_start( + struct aspeed_smc_controller *controller, u32 reg); +static u32 aspeed_smc_segment_end( + struct aspeed_smc_controller *controller, u32 reg); +static u32 aspeed_smc_segment_reg( + struct aspeed_smc_controller *controller, u32 start, u32 end); static const struct aspeed_smc_info fmc_2400_info = { .maxsize = 64 * 1024 * 1024, @@ -50,7 +77,15 @@ static const struct aspeed_smc_info fmc_2400_info = { .hastype = true, .we0 = 16, .ctl0 = 0x10, + .timing = 0x94, + .hclk_mask = 0xfffff0ff, + .hdiv_max = 1, .set_4b = aspeed_smc_chip_set_4b, + .optimize_read = aspeed_smc_optimize_read, + .calibrate = aspeed_smc_calibrate_reads, + .segment_start = aspeed_smc_segment_start, + .segment_end = aspeed_smc_segment_end, + .segment_reg = aspeed_smc_segment_reg, }; static const struct aspeed_smc_info spi_2400_info = { @@ -59,7 +94,13 @@ static const struct aspeed_smc_info spi_2400_info = { .hastype = false, .we0 = 0, .ctl0 = 0x04, + .timing = 0x14, + .hclk_mask = 0xfffff0ff, + .hdiv_max = 1, .set_4b = aspeed_smc_chip_set_4b_spi_2400, + .optimize_read = aspeed_smc_optimize_read, + .calibrate = aspeed_smc_calibrate_reads, + /* No segment registers */ }; static const struct aspeed_smc_info fmc_2500_info = { @@ -68,7 +109,15 @@ static const struct aspeed_smc_info fmc_2500_info = { .hastype = true, .we0 = 16, .ctl0 = 0x10, + .timing = 0x94, + .hclk_mask = 0xfffff0ff, + .hdiv_max = 1, .set_4b = aspeed_smc_chip_set_4b, + .optimize_read = aspeed_smc_optimize_read, + .calibrate = aspeed_smc_calibrate_reads, + .segment_start = aspeed_smc_segment_start, + .segment_end = aspeed_smc_segment_end, + .segment_reg = aspeed_smc_segment_reg, }; static const struct aspeed_smc_info spi_2500_info = { @@ -77,7 +126,59 @@ static const struct aspeed_smc_info spi_2500_info = { .hastype = false, .we0 = 16, .ctl0 = 0x10, + .timing = 0x94, + .hclk_mask = 0xfffff0ff, + .hdiv_max = 1, + .set_4b = aspeed_smc_chip_set_4b, + .optimize_read = aspeed_smc_optimize_read, + .calibrate = aspeed_smc_calibrate_reads, + .segment_start = aspeed_smc_segment_start, + .segment_end = aspeed_smc_segment_end, + .segment_reg = aspeed_smc_segment_reg, +}; + +static u32 aspeed_smc_segment_start_ast2600( + struct aspeed_smc_controller *controller, u32 reg); +static u32 aspeed_smc_segment_end_ast2600( + struct aspeed_smc_controller *controller, u32 reg); +static u32 aspeed_smc_segment_reg_ast2600( + struct aspeed_smc_controller *controller, u32 start, u32 end); + +static int aspeed_smc_calibrate_reads_ast2600(struct aspeed_smc_chip *chip, + u32 hdiv, const u8 *golden_buf, u8 *test_buf); + +static const struct aspeed_smc_info fmc_2600_info = { + .maxsize = 256 * 1024 * 1024, + .nce = 3, + .hastype = false, /* SPI Only */ + .we0 = 16, + .ctl0 = 0x10, + .timing = 0x94, + .hclk_mask = 0xf0fff0ff, + .hdiv_max = 2, + .set_4b = aspeed_smc_chip_set_4b, + .optimize_read = aspeed_smc_optimize_read, + .calibrate = aspeed_smc_calibrate_reads_ast2600, + .segment_start = aspeed_smc_segment_start_ast2600, + .segment_end = aspeed_smc_segment_end_ast2600, + .segment_reg = aspeed_smc_segment_reg_ast2600, +}; + +static const struct aspeed_smc_info spi_2600_info = { + .maxsize = 256 * 1024 * 1024, + .nce = 2, + .hastype = false, + .we0 = 16, + .ctl0 = 0x10, + .timing = 0x94, + .hclk_mask = 0xf0fff0ff, + .hdiv_max = 2, .set_4b = aspeed_smc_chip_set_4b, + .optimize_read = aspeed_smc_optimize_read, + .calibrate = aspeed_smc_calibrate_reads_ast2600, + .segment_start = aspeed_smc_segment_start_ast2600, + .segment_end = aspeed_smc_segment_end_ast2600, + .segment_reg = aspeed_smc_segment_reg_ast2600, }; enum aspeed_smc_ctl_reg_value { @@ -98,6 +199,7 @@ struct aspeed_smc_chip { u32 ctl_val[smc_max]; /* control settings */ enum aspeed_smc_flash_type type; /* what type of flash */ struct spi_nor nor; + u32 clk_rate; }; struct aspeed_smc_controller { @@ -106,12 +208,17 @@ struct aspeed_smc_controller { struct mutex mutex; /* controller access mutex */ const struct aspeed_smc_info *info; /* type info of controller */ void __iomem *regs; /* controller registers */ - void __iomem *ahb_base; /* per-chip windows resource */ + void __iomem *ahb_base; /* per-chip window resource */ + u32 ahb_base_phy; /* phys addr of AHB window */ u32 ahb_window_size; /* full mapping window size */ + unsigned long clk_frequency; + struct aspeed_smc_chip *chips[]; /* pointers to attached chips */ }; +#define ASPEED_SPI_DEFAULT_FREQ 50000000 + /* * SPI Flash Configuration Register (AST2500 SPI) * or @@ -181,23 +288,82 @@ struct aspeed_smc_controller { (CONTROL_AAF_MODE | CONTROL_CE_INACTIVE_MASK | CONTROL_CLK_DIV4 | \ CONTROL_CLOCK_FREQ_SEL_MASK | CONTROL_LSB_FIRST | CONTROL_CLOCK_MODE_3) -/* - * The Segment Register uses a 8MB unit to encode the start address - * and the end address of the mapping window of a flash SPI slave : - * - * | byte 1 | byte 2 | byte 3 | byte 4 | - * +--------+--------+--------+--------+ - * | end | start | 0 | 0 | - */ #define SEGMENT_ADDR_REG0 0x30 -#define SEGMENT_ADDR_START(_r) ((((_r) >> 16) & 0xFF) << 23) -#define SEGMENT_ADDR_END(_r) ((((_r) >> 24) & 0xFF) << 23) -#define SEGMENT_ADDR_VALUE(start, end) \ - (((((start) >> 23) & 0xFF) << 16) | ((((end) >> 23) & 0xFF) << 24)) #define SEGMENT_ADDR_REG(controller, cs) \ ((controller)->regs + SEGMENT_ADDR_REG0 + (cs) * 4) /* + * The Segment Registers of the AST2400 and AST2500 have a 8MB + * unit. The address range of a flash SPI slave is encoded with + * absolute addresses which should be part of the overall controller + * window. + */ +static u32 aspeed_smc_segment_start( + struct aspeed_smc_controller *controller, u32 reg) +{ + return ((reg >> 16) & 0xFF) << 23; +} + +static u32 aspeed_smc_segment_end( + struct aspeed_smc_controller *controller, u32 reg) +{ + return ((reg >> 24) & 0xFF) << 23; +} + +static u32 aspeed_smc_segment_reg( + struct aspeed_smc_controller *controller, u32 start, u32 end) +{ + return (((start >> 23) & 0xFF) << 16) | (((end >> 23) & 0xFF) << 24); +} + +/* + * The Segment Registers of the AST2600 have a 1MB unit. The address + * range of a flash SPI slave is encoded with offsets in the overall + * controller window. The previous SoC AST2400 and AST2500 used + * absolute addresses. Only bits [27:20] are relevant and the end + * address is an upper bound limit. + */ + +#define AST2600_SEG_ADDR_MASK 0x0ff00000 + +static u32 aspeed_smc_segment_start_ast2600( + struct aspeed_smc_controller *controller, u32 reg) +{ + uint32_t start_offset = (reg << 16) & AST2600_SEG_ADDR_MASK; + + return controller->ahb_base_phy + start_offset; +} + +static u32 aspeed_smc_segment_end_ast2600( + struct aspeed_smc_controller *controller, u32 reg) +{ + uint32_t end_offset = reg & AST2600_SEG_ADDR_MASK; + + /* segment is disabled */ + if (!end_offset) + return controller->ahb_base_phy; + + return controller->ahb_base_phy + end_offset + 0x100000; +} + +static u32 aspeed_smc_segment_reg_ast2600( + struct aspeed_smc_controller *controller, u32 start, u32 end) +{ + /* disable zero size segments */ + if (start == end) + return 0; + + return ((start & AST2600_SEG_ADDR_MASK) >> 16) | + ((end - 1) & AST2600_SEG_ADDR_MASK); +} + +/* + * Switch to turn off read optimisation if needed + */ +static bool optimize_read = true; +module_param(optimize_read, bool, 0644); + +/* * In user mode all data bytes read or written to the chip decode address * range are transferred to or from the SPI bus. The range is treated as a * fifo of arbitratry 1, 2, or 4 byte width but each write has to be aligned @@ -370,18 +536,49 @@ static void aspeed_smc_send_cmd_addr(struct spi_nor *nor, u8 cmd, u32 addr) } } +static int aspeed_smc_get_io_mode(struct aspeed_smc_chip *chip) +{ + switch (chip->nor.read_proto) { + case SNOR_PROTO_1_1_1: + return 0; + case SNOR_PROTO_1_1_2: + return CONTROL_IO_DUAL_DATA; + case SNOR_PROTO_1_2_2: + return CONTROL_IO_DUAL_ADDR_DATA; + default: + dev_err(chip->nor.dev, "unsupported SPI read mode\n"); + return -EINVAL; + } +} + +static void aspeed_smc_set_io_mode(struct aspeed_smc_chip *chip, u32 io_mode) +{ + u32 ctl; + + if (io_mode > 0) { + ctl = readl(chip->ctl) & ~CONTROL_IO_MODE_MASK; + ctl |= io_mode; + writel(ctl, chip->ctl); + } +} + static ssize_t aspeed_smc_read_user(struct spi_nor *nor, loff_t from, size_t len, u_char *read_buf) { struct aspeed_smc_chip *chip = nor->priv; int i; u8 dummy = 0xFF; + int io_mode = aspeed_smc_get_io_mode(chip); aspeed_smc_start_user(nor); aspeed_smc_send_cmd_addr(nor, nor->read_opcode, from); for (i = 0; i < chip->nor.read_dummy / 8; i++) aspeed_smc_write_to_ahb(chip->ahb_base, &dummy, sizeof(dummy)); + /* Set IO mode only for data */ + if (io_mode == CONTROL_IO_DUAL_DATA) + aspeed_smc_set_io_mode(chip, io_mode); + aspeed_smc_read_from_ahb(read_buf, chip->ahb_base, len); aspeed_smc_stop_user(nor); return len; @@ -399,6 +596,31 @@ static ssize_t aspeed_smc_write_user(struct spi_nor *nor, loff_t to, return len; } +static ssize_t aspeed_smc_read(struct spi_nor *nor, loff_t from, size_t len, + u_char *read_buf) +{ + struct aspeed_smc_chip *chip = nor->priv; + + /* + * The AHB window configured for the chip is too small for the + * read offset. Use the "User mode" of the controller to + * perform the read. + */ + if (from >= chip->ahb_window_size) { + aspeed_smc_read_user(nor, from, len, read_buf); + goto out; + } + + /* + * Use the "Command mode" to do a direct read from the AHB + * window configured for the chip. This should be the default. + */ + memcpy_fromio(read_buf, chip->ahb_base + from, len); + +out: + return len; +} + static int aspeed_smc_unregister(struct aspeed_smc_controller *controller) { struct aspeed_smc_chip *chip; @@ -423,6 +645,8 @@ static const struct of_device_id aspeed_smc_matches[] = { { .compatible = "aspeed,ast2400-spi", .data = &spi_2400_info }, { .compatible = "aspeed,ast2500-fmc", .data = &fmc_2500_info }, { .compatible = "aspeed,ast2500-spi", .data = &spi_2500_info }, + { .compatible = "aspeed,ast2600-fmc", .data = &fmc_2600_info }, + { .compatible = "aspeed,ast2600-spi", .data = &spi_2600_info }, { } }; MODULE_DEVICE_TABLE(of, aspeed_smc_matches); @@ -438,36 +662,32 @@ static void __iomem *aspeed_smc_chip_base(struct aspeed_smc_chip *chip, struct resource *res) { struct aspeed_smc_controller *controller = chip->controller; + const struct aspeed_smc_info *info = controller->info; u32 offset = 0; u32 reg; - if (controller->info->nce > 1) { + if (info->nce > 1) { reg = readl(SEGMENT_ADDR_REG(controller, chip->cs)); - if (SEGMENT_ADDR_START(reg) >= SEGMENT_ADDR_END(reg)) + if (info->segment_start(controller, reg) >= + info->segment_end(controller, reg)) { return NULL; + } - offset = SEGMENT_ADDR_START(reg) - res->start; + offset = info->segment_start(controller, reg) - res->start; } return controller->ahb_base + offset; } -static u32 aspeed_smc_ahb_base_phy(struct aspeed_smc_controller *controller) -{ - u32 seg0_val = readl(SEGMENT_ADDR_REG(controller, 0)); - - return SEGMENT_ADDR_START(seg0_val); -} - static u32 chip_set_segment(struct aspeed_smc_chip *chip, u32 cs, u32 start, u32 size) { struct aspeed_smc_controller *controller = chip->controller; + const struct aspeed_smc_info *info = controller->info; void __iomem *seg_reg; - u32 seg_oldval, seg_newval, ahb_base_phy, end; - - ahb_base_phy = aspeed_smc_ahb_base_phy(controller); + u32 seg_oldval, seg_newval, end; + u32 ahb_base_phy = controller->ahb_base_phy; seg_reg = SEGMENT_ADDR_REG(controller, cs); seg_oldval = readl(seg_reg); @@ -477,8 +697,15 @@ static u32 chip_set_segment(struct aspeed_smc_chip *chip, u32 cs, u32 start, * size, but take into account the possible overlap with the * previous segment */ - if (!size) - size = SEGMENT_ADDR_END(seg_oldval) - start; + if (!size) { + end = info->segment_end(controller, seg_oldval); + + /* + * Check for disabled segment (AST2600). + */ + if (end != ahb_base_phy) + size = end - start; + } /* * The segment cannot exceed the maximum window size of the @@ -491,7 +718,7 @@ static u32 chip_set_segment(struct aspeed_smc_chip *chip, u32 cs, u32 start, } end = start + size; - seg_newval = SEGMENT_ADDR_VALUE(start, end); + seg_newval = info->segment_reg(controller, start, end); writel(seg_newval, seg_reg); /* @@ -502,13 +729,13 @@ static u32 chip_set_segment(struct aspeed_smc_chip *chip, u32 cs, u32 start, if (seg_newval != readl(seg_reg)) { dev_err(chip->nor.dev, "CE%d window invalid", cs); writel(seg_oldval, seg_reg); - start = SEGMENT_ADDR_START(seg_oldval); - end = SEGMENT_ADDR_END(seg_oldval); + start = info->segment_start(controller, seg_oldval); + end = info->segment_end(controller, seg_oldval); size = end - start; } - dev_info(chip->nor.dev, "CE%d window [ 0x%.8x - 0x%.8x ] %dMB", - cs, start, end, size >> 20); + dev_info(chip->nor.dev, "CE%d window [ 0x%.8x - 0x%.8x ] %dMB%s", + cs, start, end, size >> 20, size ? "" : " (disabled)"); return size; } @@ -556,7 +783,7 @@ static u32 aspeed_smc_chip_set_segment(struct aspeed_smc_chip *chip) chip->cs, size >> 20); } - ahb_base_phy = aspeed_smc_ahb_base_phy(controller); + ahb_base_phy = controller->ahb_base_phy; /* * As a start address for the current segment, use the default @@ -566,7 +793,7 @@ static u32 aspeed_smc_chip_set_segment(struct aspeed_smc_chip *chip) if (chip->cs) { u32 prev = readl(SEGMENT_ADDR_REG(controller, chip->cs - 1)); - start = SEGMENT_ADDR_END(prev); + start = controller->info->segment_end(controller, prev); } else { start = ahb_base_phy; } @@ -703,10 +930,258 @@ static int aspeed_smc_chip_setup_init(struct aspeed_smc_chip *chip, return 0; } + +#define CALIBRATE_BUF_SIZE 16384 + +static bool aspeed_smc_check_reads(struct aspeed_smc_chip *chip, + const u8 *golden_buf, u8 *test_buf) +{ + int i; + + for (i = 0; i < 10; i++) { + memcpy_fromio(test_buf, chip->ahb_base, CALIBRATE_BUF_SIZE); + if (memcmp(test_buf, golden_buf, CALIBRATE_BUF_SIZE) != 0) + return false; + } + return true; +} + +static int aspeed_smc_calibrate_reads(struct aspeed_smc_chip *chip, u32 hdiv, + const u8 *golden_buf, u8 *test_buf) +{ + struct aspeed_smc_controller *controller = chip->controller; + const struct aspeed_smc_info *info = controller->info; + int i; + int good_pass = -1, pass_count = 0; + u32 shift = (hdiv - 1) << 2; + u32 mask = ~(0xfu << shift); + u32 fread_timing_val = 0; + +#define FREAD_TPASS(i) (((i) / 2) | (((i) & 1) ? 0 : 8)) + + /* Try HCLK delay 0..5, each one with/without delay and look for a + * good pair. + */ + for (i = 0; i < 12; i++) { + bool pass; + + fread_timing_val &= mask; + fread_timing_val |= FREAD_TPASS(i) << shift; + + writel(fread_timing_val, controller->regs + info->timing); + pass = aspeed_smc_check_reads(chip, golden_buf, test_buf); + dev_dbg(chip->nor.dev, + " * [%08x] %d HCLK delay, %dns DI delay : %s", + fread_timing_val, i/2, (i & 1) ? 0 : 4, + pass ? "PASS" : "FAIL"); + if (pass) { + pass_count++; + if (pass_count == 3) { + good_pass = i - 1; + break; + } + } else + pass_count = 0; + } + + /* No good setting for this frequency */ + if (good_pass < 0) + return -1; + + /* We have at least one pass of margin, let's use first pass */ + fread_timing_val &= mask; + fread_timing_val |= FREAD_TPASS(good_pass) << shift; + writel(fread_timing_val, controller->regs + info->timing); + dev_dbg(chip->nor.dev, " * -> good is pass %d [0x%08x]", + good_pass, fread_timing_val); + return 0; +} + +static bool aspeed_smc_check_calib_data(const u8 *test_buf, u32 size) +{ + const u32 *tb32 = (const u32 *) test_buf; + u32 i, cnt = 0; + + /* We check if we have enough words that are neither all 0 + * nor all 1's so the calibration can be considered valid. + * + * I use an arbitrary threshold for now of 64 + */ + size >>= 2; + for (i = 0; i < size; i++) { + if (tb32[i] != 0 && tb32[i] != 0xffffffff) + cnt++; + } + return cnt >= 64; +} + +static const uint32_t aspeed_smc_hclk_divs[] = { + 0xf, /* HCLK */ + 0x7, /* HCLK/2 */ + 0xe, /* HCLK/3 */ + 0x6, /* HCLK/4 */ + 0xd, /* HCLK/5 */ +}; +#define ASPEED_SMC_HCLK_DIV(i) \ + (aspeed_smc_hclk_divs[(i) - 1] << CONTROL_CLOCK_FREQ_SEL_SHIFT) + +static u32 aspeed_smc_default_read(struct aspeed_smc_chip *chip) +{ + /* + * Keep the 4Byte address mode on the AST2400 SPI controller. + * Other controllers set the 4Byte mode in the CE Control + * Register + */ + u32 ctl_mask = chip->controller->info == &spi_2400_info ? + CONTROL_IO_ADDRESS_4B : 0; + u8 cmd = chip->nor.addr_width == 4 ? SPINOR_OP_READ_4B : + SPINOR_OP_READ; + + /* + * Use the "read command" mode to customize the opcode. In + * normal command mode, the value is necessarily READ (0x3) on + * the AST2400/2500 SoCs. + */ + return (chip->ctl_val[smc_read] & ctl_mask) | + (0x00 << 28) | /* Single bit */ + (0x00 << 24) | /* CE# max */ + (cmd << 16) | /* use read mode to support 4B opcode */ + (0x00 << 8) | /* HCLK/16 */ + (0x00 << 6) | /* no dummy cycle */ + (0x01); /* read mode */ +} + +static int aspeed_smc_optimize_read(struct aspeed_smc_chip *chip, + u32 max_freq) +{ + struct aspeed_smc_controller *controller = chip->controller; + const struct aspeed_smc_info *info = controller->info; + u8 *golden_buf, *test_buf; + int i, rc, best_div = -1; + u32 save_read_val = chip->ctl_val[smc_read]; + u32 ahb_freq = chip->controller->clk_frequency; + + dev_dbg(chip->nor.dev, "AHB frequency: %d MHz", ahb_freq / 1000000); + + test_buf = kmalloc(CALIBRATE_BUF_SIZE * 2, GFP_KERNEL); + golden_buf = test_buf + CALIBRATE_BUF_SIZE; + + /* We start with the dumbest setting (keep 4Byte bit) and read + * some data + */ + chip->ctl_val[smc_read] = aspeed_smc_default_read(chip); + + writel(chip->ctl_val[smc_read], chip->ctl); + + memcpy_fromio(golden_buf, chip->ahb_base, CALIBRATE_BUF_SIZE); + + /* Establish our read mode with freq field set to 0 (HCLK/16) */ + chip->ctl_val[smc_read] = save_read_val & info->hclk_mask; + + /* Check if calibration data is suitable */ + if (!aspeed_smc_check_calib_data(golden_buf, CALIBRATE_BUF_SIZE)) { + dev_info(chip->nor.dev, + "Calibration area too uniform, using low speed"); + writel(chip->ctl_val[smc_read], chip->ctl); + kfree(test_buf); + return 0; + } + + /* Now we iterate the HCLK dividers until we find our breaking point */ + for (i = ARRAY_SIZE(aspeed_smc_hclk_divs); i > info->hdiv_max - 1; i--) { + u32 tv, freq; + + /* Compare timing to max */ + freq = ahb_freq / i; + if (freq > max_freq) + continue; + + /* Set the timing */ + tv = chip->ctl_val[smc_read] | ASPEED_SMC_HCLK_DIV(i); + writel(tv, chip->ctl); + dev_dbg(chip->nor.dev, "Trying HCLK/%d [%08x] ...", i, tv); + rc = info->calibrate(chip, i, golden_buf, test_buf); + if (rc == 0) + best_div = i; + } + kfree(test_buf); + + /* Nothing found ? */ + if (best_div < 0) + dev_warn(chip->nor.dev, "No good frequency, using dumb slow"); + else { + dev_dbg(chip->nor.dev, "Found good read timings at HCLK/%d", + best_div); + chip->ctl_val[smc_read] |= ASPEED_SMC_HCLK_DIV(best_div); + } + + writel(chip->ctl_val[smc_read], chip->ctl); + return 0; +} + +#define TIMING_DELAY_DI BIT(3) +#define TIMING_DELAY_HCYCLE_MAX 5 +#define TIMING_REG_AST2600(chip) \ + ((chip)->controller->regs + (chip)->controller->info->timing + \ + (chip)->cs * 4) + +static int aspeed_smc_calibrate_reads_ast2600(struct aspeed_smc_chip *chip, u32 hdiv, + const u8 *golden_buf, u8 *test_buf) +{ + int hcycle; + u32 shift = (hdiv - 2) << 3; + u32 mask = ~(0xfu << shift); + u32 fread_timing_val = 0; + + for (hcycle = 0; hcycle <= TIMING_DELAY_HCYCLE_MAX; hcycle++) { + int delay_ns; + bool pass = false; + + fread_timing_val &= mask; + fread_timing_val |= hcycle << shift; + + /* no DI input delay first */ + writel(fread_timing_val, TIMING_REG_AST2600(chip)); + pass = aspeed_smc_check_reads(chip, golden_buf, test_buf); + dev_dbg(chip->nor.dev, + " * [%08x] %d HCLK delay, DI delay none : %s", + fread_timing_val, hcycle, pass ? "PASS" : "FAIL"); + if (pass) + return 0; + + /* Add DI input delays */ + fread_timing_val &= mask; + fread_timing_val |= (TIMING_DELAY_DI | hcycle) << shift; + + for (delay_ns = 0; delay_ns < 0x10; delay_ns++) { + fread_timing_val &= ~(0xf << (4 + shift)); + fread_timing_val |= delay_ns << (4 + shift); + + writel(fread_timing_val, TIMING_REG_AST2600(chip)); + pass = aspeed_smc_check_reads(chip, golden_buf, test_buf); + dev_dbg(chip->nor.dev, + " * [%08x] %d HCLK delay, DI delay %d.%dns : %s", + fread_timing_val, hcycle, (delay_ns + 1)/2, + (delay_ns + 1) & 1 ? 5 : 5, pass ? "PASS" : "FAIL"); + /* + * TODO: This is optimistic. We should look + * for a working interval and save the middle + * value in the read timing register. + */ + if (pass) + return 0; + } + } + + /* No good setting for this frequency */ + return -1; +} + static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip) { struct aspeed_smc_controller *controller = chip->controller; const struct aspeed_smc_info *info = controller->info; + int io_mode; u32 cmd; if (chip->nor.addr_width == 4 && info->set_4b) @@ -729,21 +1204,24 @@ static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip) * TODO: Adjust clocks if fast read is supported and interpret * SPI NOR flags to adjust controller settings. */ - if (chip->nor.read_proto == SNOR_PROTO_1_1_1) { - if (chip->nor.read_dummy == 0) - cmd = CONTROL_COMMAND_MODE_NORMAL; - else - cmd = CONTROL_COMMAND_MODE_FREAD; - } else { - dev_err(chip->nor.dev, "unsupported SPI read mode\n"); - return -EINVAL; - } + io_mode = aspeed_smc_get_io_mode(chip); + if (io_mode < 0) + return io_mode; + + if (chip->nor.read_dummy == 0) + cmd = CONTROL_COMMAND_MODE_NORMAL; + else + cmd = CONTROL_COMMAND_MODE_FREAD; - chip->ctl_val[smc_read] |= cmd | + chip->ctl_val[smc_read] |= cmd | io_mode | + chip->nor.read_opcode << CONTROL_COMMAND_SHIFT | CONTROL_IO_DUMMY_SET(chip->nor.read_dummy / 8); - dev_dbg(controller->dev, "base control register: %08x\n", + dev_info(controller->dev, "read control register: %08x\n", chip->ctl_val[smc_read]); + + if (optimize_read && info->optimize_read) + info->optimize_read(chip, chip->clk_rate); return 0; } @@ -752,7 +1230,7 @@ static const struct spi_nor_controller_ops aspeed_smc_controller_ops = { .unprepare = aspeed_smc_unprep, .read_reg = aspeed_smc_read_reg, .write_reg = aspeed_smc_write_reg, - .read = aspeed_smc_read_user, + .read = aspeed_smc_read, .write = aspeed_smc_write_user, }; @@ -762,6 +1240,7 @@ static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller, const struct spi_nor_hwcaps hwcaps = { .mask = SNOR_HWCAPS_READ | SNOR_HWCAPS_READ_FAST | + SNOR_HWCAPS_READ_1_1_2 | SNOR_HWCAPS_PP, }; const struct aspeed_smc_info *info = controller->info; @@ -805,6 +1284,13 @@ static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller, break; } + if (of_property_read_u32(child, "spi-max-frequency", + &chip->clk_rate)) { + chip->clk_rate = ASPEED_SPI_DEFAULT_FREQ; + } + dev_info(dev, "Using %d MHz SPI frequency\n", + chip->clk_rate / 1000000); + chip->controller = controller; chip->ctl = controller->regs + info->ctl0 + cs * 4; chip->cs = cs; @@ -856,6 +1342,7 @@ static int aspeed_smc_probe(struct platform_device *pdev) struct aspeed_smc_controller *controller; const struct of_device_id *match; const struct aspeed_smc_info *info; + struct clk *clk; struct resource *res; int ret; @@ -881,12 +1368,19 @@ static int aspeed_smc_probe(struct platform_device *pdev) return PTR_ERR(controller->regs); res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + controller->ahb_base_phy = res->start; controller->ahb_base = devm_ioremap_resource(dev, res); if (IS_ERR(controller->ahb_base)) return PTR_ERR(controller->ahb_base); controller->ahb_window_size = resource_size(res); + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + controller->clk_frequency = clk_get_rate(clk); + devm_clk_put(&pdev->dev, clk); + ret = aspeed_smc_setup_flash(controller, np, res); if (ret) dev_err(dev, "Aspeed SMC probe failed %d\n", ret); diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index 55c0c508464b..9db07182e9c8 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -456,7 +456,6 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, /* Number of address bytes. */ switch (bfpt.dwords[BFPT_DWORD(1)] & BFPT_DWORD1_ADDRESS_BYTES_MASK) { case BFPT_DWORD1_ADDRESS_BYTES_3_ONLY: - case BFPT_DWORD1_ADDRESS_BYTES_3_OR_4: nor->addr_width = 3; break; diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c new file mode 100644 index 000000000000..99ce971ebc6d --- /dev/null +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -0,0 +1,5155 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Based on m25p80.c, by Mike Lavender (mike@steroidmicros.com), with + * influence from lart.c (Abraham Van Der Merwe) and mtd_dataflash.c + * + * Copyright (C) 2005, Intec Automation Inc. + * Copyright (C) 2014, Freescale Semiconductor, Inc. + */ + +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/math64.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/sort.h> + +#include <linux/mtd/mtd.h> +#include <linux/of_platform.h> +#include <linux/sched/task_stack.h> +#include <linux/spi/flash.h> +#include <linux/mtd/spi-nor.h> + +/* Define max times to check status register before we give up. */ + +/* + * For everything but full-chip erase; probably could be much smaller, but kept + * around for safety for now + */ +#define DEFAULT_READY_WAIT_JIFFIES (40UL * HZ) + +/* + * For full-chip erase, calibrated to a 2MB flash (M25P16); should be scaled up + * for larger flash + */ +#define CHIP_ERASE_2MB_READY_WAIT_JIFFIES (40UL * HZ) + +#define SPI_NOR_MAX_ID_LEN 6 +#define SPI_NOR_MAX_ADDR_WIDTH 4 + +struct sfdp_parameter_header { + u8 id_lsb; + u8 minor; + u8 major; + u8 length; /* in double words */ + u8 parameter_table_pointer[3]; /* byte address */ + u8 id_msb; +}; + +#define SFDP_PARAM_HEADER_ID(p) (((p)->id_msb << 8) | (p)->id_lsb) +#define SFDP_PARAM_HEADER_PTP(p) \ + (((p)->parameter_table_pointer[2] << 16) | \ + ((p)->parameter_table_pointer[1] << 8) | \ + ((p)->parameter_table_pointer[0] << 0)) + +#define SFDP_BFPT_ID 0xff00 /* Basic Flash Parameter Table */ +#define SFDP_SECTOR_MAP_ID 0xff81 /* Sector Map Table */ +#define SFDP_4BAIT_ID 0xff84 /* 4-byte Address Instruction Table */ + +#define SFDP_SIGNATURE 0x50444653U +#define SFDP_JESD216_MAJOR 1 +#define SFDP_JESD216_MINOR 0 +#define SFDP_JESD216A_MINOR 5 +#define SFDP_JESD216B_MINOR 6 + +struct sfdp_header { + u32 signature; /* Ox50444653U <=> "SFDP" */ + u8 minor; + u8 major; + u8 nph; /* 0-base number of parameter headers */ + u8 unused; + + /* Basic Flash Parameter Table. */ + struct sfdp_parameter_header bfpt_header; +}; + +/* Basic Flash Parameter Table */ + +/* + * JESD216 rev B defines a Basic Flash Parameter Table of 16 DWORDs. + * They are indexed from 1 but C arrays are indexed from 0. + */ +#define BFPT_DWORD(i) ((i) - 1) +#define BFPT_DWORD_MAX 16 + +/* The first version of JESB216 defined only 9 DWORDs. */ +#define BFPT_DWORD_MAX_JESD216 9 + +/* 1st DWORD. */ +#define BFPT_DWORD1_FAST_READ_1_1_2 BIT(16) +#define BFPT_DWORD1_ADDRESS_BYTES_MASK GENMASK(18, 17) +#define BFPT_DWORD1_ADDRESS_BYTES_3_ONLY (0x0UL << 17) +#define BFPT_DWORD1_ADDRESS_BYTES_3_OR_4 (0x1UL << 17) +#define BFPT_DWORD1_ADDRESS_BYTES_4_ONLY (0x2UL << 17) +#define BFPT_DWORD1_DTR BIT(19) +#define BFPT_DWORD1_FAST_READ_1_2_2 BIT(20) +#define BFPT_DWORD1_FAST_READ_1_4_4 BIT(21) +#define BFPT_DWORD1_FAST_READ_1_1_4 BIT(22) + +/* 5th DWORD. */ +#define BFPT_DWORD5_FAST_READ_2_2_2 BIT(0) +#define BFPT_DWORD5_FAST_READ_4_4_4 BIT(4) + +/* 11th DWORD. */ +#define BFPT_DWORD11_PAGE_SIZE_SHIFT 4 +#define BFPT_DWORD11_PAGE_SIZE_MASK GENMASK(7, 4) + +/* 15th DWORD. */ + +/* + * (from JESD216 rev B) + * Quad Enable Requirements (QER): + * - 000b: Device does not have a QE bit. Device detects 1-1-4 and 1-4-4 + * reads based on instruction. DQ3/HOLD# functions are hold during + * instruction phase. + * - 001b: QE is bit 1 of status register 2. It is set via Write Status with + * two data bytes where bit 1 of the second byte is one. + * [...] + * Writing only one byte to the status register has the side-effect of + * clearing status register 2, including the QE bit. The 100b code is + * used if writing one byte to the status register does not modify + * status register 2. + * - 010b: QE is bit 6 of status register 1. It is set via Write Status with + * one data byte where bit 6 is one. + * [...] + * - 011b: QE is bit 7 of status register 2. It is set via Write status + * register 2 instruction 3Eh with one data byte where bit 7 is one. + * [...] + * The status register 2 is read using instruction 3Fh. + * - 100b: QE is bit 1 of status register 2. It is set via Write Status with + * two data bytes where bit 1 of the second byte is one. + * [...] + * In contrast to the 001b code, writing one byte to the status + * register does not modify status register 2. + * - 101b: QE is bit 1 of status register 2. Status register 1 is read using + * Read Status instruction 05h. Status register2 is read using + * instruction 35h. QE is set via Write Status instruction 01h with + * two data bytes where bit 1 of the second byte is one. + * [...] + */ +#define BFPT_DWORD15_QER_MASK GENMASK(22, 20) +#define BFPT_DWORD15_QER_NONE (0x0UL << 20) /* Micron */ +#define BFPT_DWORD15_QER_SR2_BIT1_BUGGY (0x1UL << 20) +#define BFPT_DWORD15_QER_SR1_BIT6 (0x2UL << 20) /* Macronix */ +#define BFPT_DWORD15_QER_SR2_BIT7 (0x3UL << 20) +#define BFPT_DWORD15_QER_SR2_BIT1_NO_RD (0x4UL << 20) +#define BFPT_DWORD15_QER_SR2_BIT1 (0x5UL << 20) /* Spansion */ + +struct sfdp_bfpt { + u32 dwords[BFPT_DWORD_MAX]; +}; + +/** + * struct spi_nor_fixups - SPI NOR fixup hooks + * @default_init: called after default flash parameters init. Used to tweak + * flash parameters when information provided by the flash_info + * table is incomplete or wrong. + * @post_bfpt: called after the BFPT table has been parsed + * @post_sfdp: called after SFDP has been parsed (is also called for SPI NORs + * that do not support RDSFDP). Typically used to tweak various + * parameters that could not be extracted by other means (i.e. + * when information provided by the SFDP/flash_info tables are + * incomplete or wrong). + * + * Those hooks can be used to tweak the SPI NOR configuration when the SFDP + * table is broken or not available. + */ +struct spi_nor_fixups { + void (*default_init)(struct spi_nor *nor); + int (*post_bfpt)(struct spi_nor *nor, + const struct sfdp_parameter_header *bfpt_header, + const struct sfdp_bfpt *bfpt, + struct spi_nor_flash_parameter *params); + void (*post_sfdp)(struct spi_nor *nor); +}; + +struct flash_info { + char *name; + + /* + * This array stores the ID bytes. + * The first three bytes are the JEDIC ID. + * JEDEC ID zero means "no ID" (mostly older chips). + */ + u8 id[SPI_NOR_MAX_ID_LEN]; + u8 id_len; + + /* The size listed here is what works with SPINOR_OP_SE, which isn't + * necessarily called a "sector" by the vendor. + */ + unsigned sector_size; + u16 n_sectors; + + u16 page_size; + u16 addr_width; + + u16 flags; +#define SECT_4K BIT(0) /* SPINOR_OP_BE_4K works uniformly */ +#define SPI_NOR_NO_ERASE BIT(1) /* No erase command needed */ +#define SST_WRITE BIT(2) /* use SST byte programming */ +#define SPI_NOR_NO_FR BIT(3) /* Can't do fastread */ +#define SECT_4K_PMC BIT(4) /* SPINOR_OP_BE_4K_PMC works uniformly */ +#define SPI_NOR_DUAL_READ BIT(5) /* Flash supports Dual Read */ +#define SPI_NOR_QUAD_READ BIT(6) /* Flash supports Quad Read */ +#define USE_FSR BIT(7) /* use flag status register */ +#define SPI_NOR_HAS_LOCK BIT(8) /* Flash supports lock/unlock via SR */ +#define SPI_NOR_HAS_TB BIT(9) /* + * Flash SR has Top/Bottom (TB) protect + * bit. Must be used with + * SPI_NOR_HAS_LOCK. + */ +#define SPI_NOR_XSR_RDY BIT(10) /* + * S3AN flashes have specific opcode to + * read the status register. + * Flags SPI_NOR_XSR_RDY and SPI_S3AN + * use the same bit as one implies the + * other, but we will get rid of + * SPI_S3AN soon. + */ +#define SPI_S3AN BIT(10) /* + * Xilinx Spartan 3AN In-System Flash + * (MFR cannot be used for probing + * because it has the same value as + * ATMEL flashes) + */ +#define SPI_NOR_4B_OPCODES BIT(11) /* + * Use dedicated 4byte address op codes + * to support memory size above 128Mib. + */ +#define NO_CHIP_ERASE BIT(12) /* Chip does not support chip erase */ +#define SPI_NOR_SKIP_SFDP BIT(13) /* Skip parsing of SFDP tables */ +#define USE_CLSR BIT(14) /* use CLSR command */ +#define SPI_NOR_OCTAL_READ BIT(15) /* Flash supports Octal Read */ + + /* Part specific fixup hooks. */ + const struct spi_nor_fixups *fixups; +}; + +#define JEDEC_MFR(info) ((info)->id[0]) + +/** + * spi_nor_spimem_xfer_data() - helper function to read/write data to + * flash's memory region + * @nor: pointer to 'struct spi_nor' + * @op: pointer to 'struct spi_mem_op' template for transfer + * + * Return: number of bytes transferred on success, -errno otherwise + */ +static ssize_t spi_nor_spimem_xfer_data(struct spi_nor *nor, + struct spi_mem_op *op) +{ + bool usebouncebuf = false; + void *rdbuf = NULL; + const void *buf; + int ret; + + if (op->data.dir == SPI_MEM_DATA_IN) + buf = op->data.buf.in; + else + buf = op->data.buf.out; + + if (object_is_on_stack(buf) || !virt_addr_valid(buf)) + usebouncebuf = true; + + if (usebouncebuf) { + if (op->data.nbytes > nor->bouncebuf_size) + op->data.nbytes = nor->bouncebuf_size; + + if (op->data.dir == SPI_MEM_DATA_IN) { + rdbuf = op->data.buf.in; + op->data.buf.in = nor->bouncebuf; + } else { + op->data.buf.out = nor->bouncebuf; + memcpy(nor->bouncebuf, buf, + op->data.nbytes); + } + } + + ret = spi_mem_adjust_op_size(nor->spimem, op); + if (ret) + return ret; + + ret = spi_mem_exec_op(nor->spimem, op); + if (ret) + return ret; + + if (usebouncebuf && op->data.dir == SPI_MEM_DATA_IN) + memcpy(rdbuf, nor->bouncebuf, op->data.nbytes); + + return op->data.nbytes; +} + +/** + * spi_nor_spimem_read_data() - read data from flash's memory region via + * spi-mem + * @nor: pointer to 'struct spi_nor' + * @from: offset to read from + * @len: number of bytes to read + * @buf: pointer to dst buffer + * + * Return: number of bytes read successfully, -errno otherwise + */ +static ssize_t spi_nor_spimem_read_data(struct spi_nor *nor, loff_t from, + size_t len, u8 *buf) +{ + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 1), + SPI_MEM_OP_ADDR(nor->addr_width, from, 1), + SPI_MEM_OP_DUMMY(nor->read_dummy, 1), + SPI_MEM_OP_DATA_IN(len, buf, 1)); + + /* get transfer protocols. */ + op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->read_proto); + op.addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->read_proto); + op.dummy.buswidth = op.addr.buswidth; + op.data.buswidth = spi_nor_get_protocol_data_nbits(nor->read_proto); + + /* convert the dummy cycles to the number of bytes */ + op.dummy.nbytes = (nor->read_dummy * op.dummy.buswidth) / 8; + + return spi_nor_spimem_xfer_data(nor, &op); +} + +/** + * spi_nor_read_data() - read data from flash memory + * @nor: pointer to 'struct spi_nor' + * @from: offset to read from + * @len: number of bytes to read + * @buf: pointer to dst buffer + * + * Return: number of bytes read successfully, -errno otherwise + */ +static ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len, + u8 *buf) +{ + if (nor->spimem) + return spi_nor_spimem_read_data(nor, from, len, buf); + + return nor->read(nor, from, len, buf); +} + +/** + * spi_nor_spimem_write_data() - write data to flash memory via + * spi-mem + * @nor: pointer to 'struct spi_nor' + * @to: offset to write to + * @len: number of bytes to write + * @buf: pointer to src buffer + * + * Return: number of bytes written successfully, -errno otherwise + */ +static ssize_t spi_nor_spimem_write_data(struct spi_nor *nor, loff_t to, + size_t len, const u8 *buf) +{ + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(nor->program_opcode, 1), + SPI_MEM_OP_ADDR(nor->addr_width, to, 1), + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(len, buf, 1)); + + op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->write_proto); + op.addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->write_proto); + op.data.buswidth = spi_nor_get_protocol_data_nbits(nor->write_proto); + + if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second) + op.addr.nbytes = 0; + + return spi_nor_spimem_xfer_data(nor, &op); +} + +/** + * spi_nor_write_data() - write data to flash memory + * @nor: pointer to 'struct spi_nor' + * @to: offset to write to + * @len: number of bytes to write + * @buf: pointer to src buffer + * + * Return: number of bytes written successfully, -errno otherwise + */ +static ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len, + const u8 *buf) +{ + if (nor->spimem) + return spi_nor_spimem_write_data(nor, to, len, buf); + + return nor->write(nor, to, len, buf); +} + +/* + * Read the status register, returning its value in the location + * Return the status register value. + * Returns negative if error occurred. + */ +static int read_sr(struct spi_nor *nor) +{ + int ret; + + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 1)); + + ret = spi_mem_exec_op(nor->spimem, &op); + } else { + ret = nor->read_reg(nor, SPINOR_OP_RDSR, nor->bouncebuf, 1); + } + + if (ret < 0) { + pr_err("error %d reading SR\n", (int) ret); + return ret; + } + + return nor->bouncebuf[0]; +} + +/* + * Read the flag status register, returning its value in the location + * Return the status register value. + * Returns negative if error occurred. + */ +static int read_fsr(struct spi_nor *nor) +{ + int ret; + + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDFSR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 1)); + + ret = spi_mem_exec_op(nor->spimem, &op); + } else { + ret = nor->read_reg(nor, SPINOR_OP_RDFSR, nor->bouncebuf, 1); + } + + if (ret < 0) { + pr_err("error %d reading FSR\n", ret); + return ret; + } + + return nor->bouncebuf[0]; +} + +/* + * Read configuration register, returning its value in the + * location. Return the configuration register value. + * Returns negative if error occurred. + */ +static int read_cr(struct spi_nor *nor) +{ + int ret; + + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDCR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 1)); + + ret = spi_mem_exec_op(nor->spimem, &op); + } else { + ret = nor->read_reg(nor, SPINOR_OP_RDCR, nor->bouncebuf, 1); + } + + if (ret < 0) { + dev_err(nor->dev, "error %d reading CR\n", ret); + return ret; + } + + return nor->bouncebuf[0]; +} + +/* + * Write status register 1 byte + * Returns negative if error occurred. + */ +static int write_sr(struct spi_nor *nor, u8 val) +{ + nor->bouncebuf[0] = val; + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1)); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->write_reg(nor, SPINOR_OP_WRSR, nor->bouncebuf, 1); +} + +/* + * Set write enable latch with Write Enable command. + * Returns negative if error occurred. + */ +static int write_enable(struct spi_nor *nor) +{ + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREN, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->write_reg(nor, SPINOR_OP_WREN, NULL, 0); +} + +/* + * Send write disable instruction to the chip. + */ +static int write_disable(struct spi_nor *nor) +{ + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRDI, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->write_reg(nor, SPINOR_OP_WRDI, NULL, 0); +} + +static struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd) +{ + return mtd->priv; +} + + +static u8 spi_nor_convert_opcode(u8 opcode, const u8 table[][2], size_t size) +{ + size_t i; + + for (i = 0; i < size; i++) + if (table[i][0] == opcode) + return table[i][1]; + + /* No conversion found, keep input op code. */ + return opcode; +} + +static u8 spi_nor_convert_3to4_read(u8 opcode) +{ + static const u8 spi_nor_3to4_read[][2] = { + { SPINOR_OP_READ, SPINOR_OP_READ_4B }, + { SPINOR_OP_READ_FAST, SPINOR_OP_READ_FAST_4B }, + { SPINOR_OP_READ_1_1_2, SPINOR_OP_READ_1_1_2_4B }, + { SPINOR_OP_READ_1_2_2, SPINOR_OP_READ_1_2_2_4B }, + { SPINOR_OP_READ_1_1_4, SPINOR_OP_READ_1_1_4_4B }, + { SPINOR_OP_READ_1_4_4, SPINOR_OP_READ_1_4_4_4B }, + { SPINOR_OP_READ_1_1_8, SPINOR_OP_READ_1_1_8_4B }, + { SPINOR_OP_READ_1_8_8, SPINOR_OP_READ_1_8_8_4B }, + + { SPINOR_OP_READ_1_1_1_DTR, SPINOR_OP_READ_1_1_1_DTR_4B }, + { SPINOR_OP_READ_1_2_2_DTR, SPINOR_OP_READ_1_2_2_DTR_4B }, + { SPINOR_OP_READ_1_4_4_DTR, SPINOR_OP_READ_1_4_4_DTR_4B }, + }; + + return spi_nor_convert_opcode(opcode, spi_nor_3to4_read, + ARRAY_SIZE(spi_nor_3to4_read)); +} + +static u8 spi_nor_convert_3to4_program(u8 opcode) +{ + static const u8 spi_nor_3to4_program[][2] = { + { SPINOR_OP_PP, SPINOR_OP_PP_4B }, + { SPINOR_OP_PP_1_1_4, SPINOR_OP_PP_1_1_4_4B }, + { SPINOR_OP_PP_1_4_4, SPINOR_OP_PP_1_4_4_4B }, + { SPINOR_OP_PP_1_1_8, SPINOR_OP_PP_1_1_8_4B }, + { SPINOR_OP_PP_1_8_8, SPINOR_OP_PP_1_8_8_4B }, + }; + + return spi_nor_convert_opcode(opcode, spi_nor_3to4_program, + ARRAY_SIZE(spi_nor_3to4_program)); +} + +static u8 spi_nor_convert_3to4_erase(u8 opcode) +{ + static const u8 spi_nor_3to4_erase[][2] = { + { SPINOR_OP_BE_4K, SPINOR_OP_BE_4K_4B }, + { SPINOR_OP_BE_32K, SPINOR_OP_BE_32K_4B }, + { SPINOR_OP_SE, SPINOR_OP_SE_4B }, + }; + + return spi_nor_convert_opcode(opcode, spi_nor_3to4_erase, + ARRAY_SIZE(spi_nor_3to4_erase)); +} + +static void spi_nor_set_4byte_opcodes(struct spi_nor *nor) +{ + nor->read_opcode = spi_nor_convert_3to4_read(nor->read_opcode); + nor->program_opcode = spi_nor_convert_3to4_program(nor->program_opcode); + nor->erase_opcode = spi_nor_convert_3to4_erase(nor->erase_opcode); + + if (!spi_nor_has_uniform_erase(nor)) { + struct spi_nor_erase_map *map = &nor->params.erase_map; + struct spi_nor_erase_type *erase; + int i; + + for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) { + erase = &map->erase_type[i]; + erase->opcode = + spi_nor_convert_3to4_erase(erase->opcode); + } + } +} + +static int macronix_set_4byte(struct spi_nor *nor, bool enable) +{ + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(enable ? + SPINOR_OP_EN4B : + SPINOR_OP_EX4B, + 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->write_reg(nor, enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B, + NULL, 0); +} + +static int st_micron_set_4byte(struct spi_nor *nor, bool enable) +{ + int ret; + + write_enable(nor); + ret = macronix_set_4byte(nor, enable); + write_disable(nor); + + return ret; +} + +static int spansion_set_4byte(struct spi_nor *nor, bool enable) +{ + nor->bouncebuf[0] = enable << 7; + + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_BRWR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1)); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->write_reg(nor, SPINOR_OP_BRWR, nor->bouncebuf, 1); +} + +static int spi_nor_write_ear(struct spi_nor *nor, u8 ear) +{ + nor->bouncebuf[0] = ear; + + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREAR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1)); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->write_reg(nor, SPINOR_OP_WREAR, nor->bouncebuf, 1); +} + +static int winbond_set_4byte(struct spi_nor *nor, bool enable) +{ + int ret; + + ret = macronix_set_4byte(nor, enable); + if (ret || enable) + return ret; + + /* + * On Winbond W25Q256FV, leaving 4byte mode causes the Extended Address + * Register to be set to 1, so all 3-byte-address reads come from the + * second 16M. We must clear the register to enable normal behavior. + */ + write_enable(nor); + ret = spi_nor_write_ear(nor, 0); + write_disable(nor); + + return ret; +} + +static int spi_nor_xread_sr(struct spi_nor *nor, u8 *sr) +{ + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_XRDSR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(1, sr, 1)); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->read_reg(nor, SPINOR_OP_XRDSR, sr, 1); +} + +static int s3an_sr_ready(struct spi_nor *nor) +{ + int ret; + + ret = spi_nor_xread_sr(nor, nor->bouncebuf); + if (ret < 0) { + dev_err(nor->dev, "error %d reading XRDSR\n", (int) ret); + return ret; + } + + return !!(nor->bouncebuf[0] & XSR_RDY); +} + +static int spi_nor_clear_sr(struct spi_nor *nor) +{ + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLSR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->write_reg(nor, SPINOR_OP_CLSR, NULL, 0); +} + +static int spi_nor_sr_ready(struct spi_nor *nor) +{ + int sr = read_sr(nor); + if (sr < 0) + return sr; + + if (nor->flags & SNOR_F_USE_CLSR && sr & (SR_E_ERR | SR_P_ERR)) { + if (sr & SR_E_ERR) + dev_err(nor->dev, "Erase Error occurred\n"); + else + dev_err(nor->dev, "Programming Error occurred\n"); + + spi_nor_clear_sr(nor); + return -EIO; + } + + return !(sr & SR_WIP); +} + +static int spi_nor_clear_fsr(struct spi_nor *nor) +{ + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLFSR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->write_reg(nor, SPINOR_OP_CLFSR, NULL, 0); +} + +static int spi_nor_fsr_ready(struct spi_nor *nor) +{ + int fsr = read_fsr(nor); + if (fsr < 0) + return fsr; + + if (fsr & (FSR_E_ERR | FSR_P_ERR)) { + if (fsr & FSR_E_ERR) + dev_err(nor->dev, "Erase operation failed.\n"); + else + dev_err(nor->dev, "Program operation failed.\n"); + + if (fsr & FSR_PT_ERR) + dev_err(nor->dev, + "Attempted to modify a protected sector.\n"); + + spi_nor_clear_fsr(nor); + return -EIO; + } + + return fsr & FSR_READY; +} + +static int spi_nor_ready(struct spi_nor *nor) +{ + int sr, fsr; + + if (nor->flags & SNOR_F_READY_XSR_RDY) + sr = s3an_sr_ready(nor); + else + sr = spi_nor_sr_ready(nor); + if (sr < 0) + return sr; + fsr = nor->flags & SNOR_F_USE_FSR ? spi_nor_fsr_ready(nor) : 1; + if (fsr < 0) + return fsr; + return sr && fsr; +} + +/* + * Service routine to read status register until ready, or timeout occurs. + * Returns non-zero if error. + */ +static int spi_nor_wait_till_ready_with_timeout(struct spi_nor *nor, + unsigned long timeout_jiffies) +{ + unsigned long deadline; + int timeout = 0, ret; + + deadline = jiffies + timeout_jiffies; + + while (!timeout) { + if (time_after_eq(jiffies, deadline)) + timeout = 1; + + ret = spi_nor_ready(nor); + if (ret < 0) + return ret; + if (ret) + return 0; + + cond_resched(); + } + + dev_err(nor->dev, "flash operation timed out\n"); + + return -ETIMEDOUT; +} + +static int spi_nor_wait_till_ready(struct spi_nor *nor) +{ + return spi_nor_wait_till_ready_with_timeout(nor, + DEFAULT_READY_WAIT_JIFFIES); +} + +/* + * Erase the whole flash memory + * + * Returns 0 if successful, non-zero otherwise. + */ +static int erase_chip(struct spi_nor *nor) +{ + dev_dbg(nor->dev, " %lldKiB\n", (long long)(nor->mtd.size >> 10)); + + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CHIP_ERASE, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->write_reg(nor, SPINOR_OP_CHIP_ERASE, NULL, 0); +} + +static int spi_nor_lock_and_prep(struct spi_nor *nor, enum spi_nor_ops ops) +{ + int ret = 0; + + mutex_lock(&nor->lock); + + if (nor->prepare) { + ret = nor->prepare(nor, ops); + if (ret) { + dev_err(nor->dev, "failed in the preparation.\n"); + mutex_unlock(&nor->lock); + return ret; + } + } + return ret; +} + +static void spi_nor_unlock_and_unprep(struct spi_nor *nor, enum spi_nor_ops ops) +{ + if (nor->unprepare) + nor->unprepare(nor, ops); + mutex_unlock(&nor->lock); +} + +/* + * This code converts an address to the Default Address Mode, that has non + * power of two page sizes. We must support this mode because it is the default + * mode supported by Xilinx tools, it can access the whole flash area and + * changing over to the Power-of-two mode is irreversible and corrupts the + * original data. + * Addr can safely be unsigned int, the biggest S3AN device is smaller than + * 4 MiB. + */ +static u32 s3an_convert_addr(struct spi_nor *nor, u32 addr) +{ + u32 offset, page; + + offset = addr % nor->page_size; + page = addr / nor->page_size; + page <<= (nor->page_size > 512) ? 10 : 9; + + return page | offset; +} + +static u32 spi_nor_convert_addr(struct spi_nor *nor, loff_t addr) +{ + if (!nor->params.convert_addr) + return addr; + + return nor->params.convert_addr(nor, addr); +} + +/* + * Initiate the erasure of a single sector + */ +static int spi_nor_erase_sector(struct spi_nor *nor, u32 addr) +{ + int i; + + addr = spi_nor_convert_addr(nor, addr); + + if (nor->erase) + return nor->erase(nor, addr); + + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(nor->erase_opcode, 1), + SPI_MEM_OP_ADDR(nor->addr_width, addr, 1), + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + + return spi_mem_exec_op(nor->spimem, &op); + } + + /* + * Default implementation, if driver doesn't have a specialized HW + * control + */ + for (i = nor->addr_width - 1; i >= 0; i--) { + nor->bouncebuf[i] = addr & 0xff; + addr >>= 8; + } + + return nor->write_reg(nor, nor->erase_opcode, nor->bouncebuf, + nor->addr_width); +} + +/** + * spi_nor_div_by_erase_size() - calculate remainder and update new dividend + * @erase: pointer to a structure that describes a SPI NOR erase type + * @dividend: dividend value + * @remainder: pointer to u32 remainder (will be updated) + * + * Return: the result of the division + */ +static u64 spi_nor_div_by_erase_size(const struct spi_nor_erase_type *erase, + u64 dividend, u32 *remainder) +{ + /* JEDEC JESD216B Standard imposes erase sizes to be power of 2. */ + *remainder = (u32)dividend & erase->size_mask; + return dividend >> erase->size_shift; +} + +/** + * spi_nor_find_best_erase_type() - find the best erase type for the given + * offset in the serial flash memory and the + * number of bytes to erase. The region in + * which the address fits is expected to be + * provided. + * @map: the erase map of the SPI NOR + * @region: pointer to a structure that describes a SPI NOR erase region + * @addr: offset in the serial flash memory + * @len: number of bytes to erase + * + * Return: a pointer to the best fitted erase type, NULL otherwise. + */ +static const struct spi_nor_erase_type * +spi_nor_find_best_erase_type(const struct spi_nor_erase_map *map, + const struct spi_nor_erase_region *region, + u64 addr, u32 len) +{ + const struct spi_nor_erase_type *erase; + u32 rem; + int i; + u8 erase_mask = region->offset & SNOR_ERASE_TYPE_MASK; + + /* + * Erase types are ordered by size, with the smallest erase type at + * index 0. + */ + for (i = SNOR_ERASE_TYPE_MAX - 1; i >= 0; i--) { + /* Does the erase region support the tested erase type? */ + if (!(erase_mask & BIT(i))) + continue; + + erase = &map->erase_type[i]; + + /* Don't erase more than what the user has asked for. */ + if (erase->size > len) + continue; + + /* Alignment is not mandatory for overlaid regions */ + if (region->offset & SNOR_OVERLAID_REGION) + return erase; + + spi_nor_div_by_erase_size(erase, addr, &rem); + if (rem) + continue; + else + return erase; + } + + return NULL; +} + +/** + * spi_nor_region_next() - get the next spi nor region + * @region: pointer to a structure that describes a SPI NOR erase region + * + * Return: the next spi nor region or NULL if last region. + */ +static struct spi_nor_erase_region * +spi_nor_region_next(struct spi_nor_erase_region *region) +{ + if (spi_nor_region_is_last(region)) + return NULL; + region++; + return region; +} + +/** + * spi_nor_find_erase_region() - find the region of the serial flash memory in + * which the offset fits + * @map: the erase map of the SPI NOR + * @addr: offset in the serial flash memory + * + * Return: a pointer to the spi_nor_erase_region struct, ERR_PTR(-errno) + * otherwise. + */ +static struct spi_nor_erase_region * +spi_nor_find_erase_region(const struct spi_nor_erase_map *map, u64 addr) +{ + struct spi_nor_erase_region *region = map->regions; + u64 region_start = region->offset & ~SNOR_ERASE_FLAGS_MASK; + u64 region_end = region_start + region->size; + + while (addr < region_start || addr >= region_end) { + region = spi_nor_region_next(region); + if (!region) + return ERR_PTR(-EINVAL); + + region_start = region->offset & ~SNOR_ERASE_FLAGS_MASK; + region_end = region_start + region->size; + } + + return region; +} + +/** + * spi_nor_init_erase_cmd() - initialize an erase command + * @region: pointer to a structure that describes a SPI NOR erase region + * @erase: pointer to a structure that describes a SPI NOR erase type + * + * Return: the pointer to the allocated erase command, ERR_PTR(-errno) + * otherwise. + */ +static struct spi_nor_erase_command * +spi_nor_init_erase_cmd(const struct spi_nor_erase_region *region, + const struct spi_nor_erase_type *erase) +{ + struct spi_nor_erase_command *cmd; + + cmd = kmalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&cmd->list); + cmd->opcode = erase->opcode; + cmd->count = 1; + + if (region->offset & SNOR_OVERLAID_REGION) + cmd->size = region->size; + else + cmd->size = erase->size; + + return cmd; +} + +/** + * spi_nor_destroy_erase_cmd_list() - destroy erase command list + * @erase_list: list of erase commands + */ +static void spi_nor_destroy_erase_cmd_list(struct list_head *erase_list) +{ + struct spi_nor_erase_command *cmd, *next; + + list_for_each_entry_safe(cmd, next, erase_list, list) { + list_del(&cmd->list); + kfree(cmd); + } +} + +/** + * spi_nor_init_erase_cmd_list() - initialize erase command list + * @nor: pointer to a 'struct spi_nor' + * @erase_list: list of erase commands to be executed once we validate that the + * erase can be performed + * @addr: offset in the serial flash memory + * @len: number of bytes to erase + * + * Builds the list of best fitted erase commands and verifies if the erase can + * be performed. + * + * Return: 0 on success, -errno otherwise. + */ +static int spi_nor_init_erase_cmd_list(struct spi_nor *nor, + struct list_head *erase_list, + u64 addr, u32 len) +{ + const struct spi_nor_erase_map *map = &nor->params.erase_map; + const struct spi_nor_erase_type *erase, *prev_erase = NULL; + struct spi_nor_erase_region *region; + struct spi_nor_erase_command *cmd = NULL; + u64 region_end; + int ret = -EINVAL; + + region = spi_nor_find_erase_region(map, addr); + if (IS_ERR(region)) + return PTR_ERR(region); + + region_end = spi_nor_region_end(region); + + while (len) { + erase = spi_nor_find_best_erase_type(map, region, addr, len); + if (!erase) + goto destroy_erase_cmd_list; + + if (prev_erase != erase || + region->offset & SNOR_OVERLAID_REGION) { + cmd = spi_nor_init_erase_cmd(region, erase); + if (IS_ERR(cmd)) { + ret = PTR_ERR(cmd); + goto destroy_erase_cmd_list; + } + + list_add_tail(&cmd->list, erase_list); + } else { + cmd->count++; + } + + addr += cmd->size; + len -= cmd->size; + + if (len && addr >= region_end) { + region = spi_nor_region_next(region); + if (!region) + goto destroy_erase_cmd_list; + region_end = spi_nor_region_end(region); + } + + prev_erase = erase; + } + + return 0; + +destroy_erase_cmd_list: + spi_nor_destroy_erase_cmd_list(erase_list); + return ret; +} + +/** + * spi_nor_erase_multi_sectors() - perform a non-uniform erase + * @nor: pointer to a 'struct spi_nor' + * @addr: offset in the serial flash memory + * @len: number of bytes to erase + * + * Build a list of best fitted erase commands and execute it once we validate + * that the erase can be performed. + * + * Return: 0 on success, -errno otherwise. + */ +static int spi_nor_erase_multi_sectors(struct spi_nor *nor, u64 addr, u32 len) +{ + LIST_HEAD(erase_list); + struct spi_nor_erase_command *cmd, *next; + int ret; + + ret = spi_nor_init_erase_cmd_list(nor, &erase_list, addr, len); + if (ret) + return ret; + + list_for_each_entry_safe(cmd, next, &erase_list, list) { + nor->erase_opcode = cmd->opcode; + while (cmd->count) { + write_enable(nor); + + ret = spi_nor_erase_sector(nor, addr); + if (ret) + goto destroy_erase_cmd_list; + + addr += cmd->size; + cmd->count--; + + ret = spi_nor_wait_till_ready(nor); + if (ret) + goto destroy_erase_cmd_list; + } + list_del(&cmd->list); + kfree(cmd); + } + + return 0; + +destroy_erase_cmd_list: + spi_nor_destroy_erase_cmd_list(&erase_list); + return ret; +} + +/* + * Erase an address range on the nor chip. The address range may extend + * one or more erase sectors. Return an error is there is a problem erasing. + */ +static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + struct spi_nor *nor = mtd_to_spi_nor(mtd); + u32 addr, len; + uint32_t rem; + int ret; + + dev_dbg(nor->dev, "at 0x%llx, len %lld\n", (long long)instr->addr, + (long long)instr->len); + + if (spi_nor_has_uniform_erase(nor)) { + div_u64_rem(instr->len, mtd->erasesize, &rem); + if (rem) + return -EINVAL; + } + + addr = instr->addr; + len = instr->len; + + ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_ERASE); + if (ret) + return ret; + + /* whole-chip erase? */ + if (len == mtd->size && !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) { + unsigned long timeout; + + write_enable(nor); + + if (erase_chip(nor)) { + ret = -EIO; + goto erase_err; + } + + /* + * Scale the timeout linearly with the size of the flash, with + * a minimum calibrated to an old 2MB flash. We could try to + * pull these from CFI/SFDP, but these values should be good + * enough for now. + */ + timeout = max(CHIP_ERASE_2MB_READY_WAIT_JIFFIES, + CHIP_ERASE_2MB_READY_WAIT_JIFFIES * + (unsigned long)(mtd->size / SZ_2M)); + ret = spi_nor_wait_till_ready_with_timeout(nor, timeout); + if (ret) + goto erase_err; + + /* REVISIT in some cases we could speed up erasing large regions + * by using SPINOR_OP_SE instead of SPINOR_OP_BE_4K. We may have set up + * to use "small sector erase", but that's not always optimal. + */ + + /* "sector"-at-a-time erase */ + } else if (spi_nor_has_uniform_erase(nor)) { + while (len) { + write_enable(nor); + + ret = spi_nor_erase_sector(nor, addr); + if (ret) + goto erase_err; + + addr += mtd->erasesize; + len -= mtd->erasesize; + + ret = spi_nor_wait_till_ready(nor); + if (ret) + goto erase_err; + } + + /* erase multiple sectors */ + } else { + ret = spi_nor_erase_multi_sectors(nor, addr, len); + if (ret) + goto erase_err; + } + + write_disable(nor); + +erase_err: + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_ERASE); + + return ret; +} + +/* Write status register and ensure bits in mask match written values */ +static int write_sr_and_check(struct spi_nor *nor, u8 status_new, u8 mask) +{ + int ret; + + write_enable(nor); + ret = write_sr(nor, status_new); + if (ret) + return ret; + + ret = spi_nor_wait_till_ready(nor); + if (ret) + return ret; + + ret = read_sr(nor); + if (ret < 0) + return ret; + + return ((ret & mask) != (status_new & mask)) ? -EIO : 0; +} + +static void stm_get_locked_range(struct spi_nor *nor, u8 sr, loff_t *ofs, + uint64_t *len) +{ + struct mtd_info *mtd = &nor->mtd; + u8 mask = SR_BP2 | SR_BP1 | SR_BP0; + int shift = ffs(mask) - 1; + int pow; + + if (!(sr & mask)) { + /* No protection */ + *ofs = 0; + *len = 0; + } else { + pow = ((sr & mask) ^ mask) >> shift; + *len = mtd->size >> pow; + if (nor->flags & SNOR_F_HAS_SR_TB && sr & SR_TB) + *ofs = 0; + else + *ofs = mtd->size - *len; + } +} + +/* + * Return 1 if the entire region is locked (if @locked is true) or unlocked (if + * @locked is false); 0 otherwise + */ +static int stm_check_lock_status_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr, bool locked) +{ + loff_t lock_offs; + uint64_t lock_len; + + if (!len) + return 1; + + stm_get_locked_range(nor, sr, &lock_offs, &lock_len); + + if (locked) + /* Requested range is a sub-range of locked range */ + return (ofs + len <= lock_offs + lock_len) && (ofs >= lock_offs); + else + /* Requested range does not overlap with locked range */ + return (ofs >= lock_offs + lock_len) || (ofs + len <= lock_offs); +} + +static int stm_is_locked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr) +{ + return stm_check_lock_status_sr(nor, ofs, len, sr, true); +} + +static int stm_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr) +{ + return stm_check_lock_status_sr(nor, ofs, len, sr, false); +} + +/* + * Lock a region of the flash. Compatible with ST Micro and similar flash. + * Supports the block protection bits BP{0,1,2} in the status register + * (SR). Does not support these features found in newer SR bitfields: + * - SEC: sector/block protect - only handle SEC=0 (block protect) + * - CMP: complement protect - only support CMP=0 (range is not complemented) + * + * Support for the following is provided conditionally for some flash: + * - TB: top/bottom protect + * + * Sample table portion for 8MB flash (Winbond w25q64fw): + * + * SEC | TB | BP2 | BP1 | BP0 | Prot Length | Protected Portion + * -------------------------------------------------------------------------- + * X | X | 0 | 0 | 0 | NONE | NONE + * 0 | 0 | 0 | 0 | 1 | 128 KB | Upper 1/64 + * 0 | 0 | 0 | 1 | 0 | 256 KB | Upper 1/32 + * 0 | 0 | 0 | 1 | 1 | 512 KB | Upper 1/16 + * 0 | 0 | 1 | 0 | 0 | 1 MB | Upper 1/8 + * 0 | 0 | 1 | 0 | 1 | 2 MB | Upper 1/4 + * 0 | 0 | 1 | 1 | 0 | 4 MB | Upper 1/2 + * X | X | 1 | 1 | 1 | 8 MB | ALL + * ------|-------|-------|-------|-------|---------------|------------------- + * 0 | 1 | 0 | 0 | 1 | 128 KB | Lower 1/64 + * 0 | 1 | 0 | 1 | 0 | 256 KB | Lower 1/32 + * 0 | 1 | 0 | 1 | 1 | 512 KB | Lower 1/16 + * 0 | 1 | 1 | 0 | 0 | 1 MB | Lower 1/8 + * 0 | 1 | 1 | 0 | 1 | 2 MB | Lower 1/4 + * 0 | 1 | 1 | 1 | 0 | 4 MB | Lower 1/2 + * + * Returns negative on errors, 0 on success. + */ +static int stm_lock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + struct mtd_info *mtd = &nor->mtd; + int status_old, status_new; + u8 mask = SR_BP2 | SR_BP1 | SR_BP0; + u8 shift = ffs(mask) - 1, pow, val; + loff_t lock_len; + bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB; + bool use_top; + + status_old = read_sr(nor); + if (status_old < 0) + return status_old; + + /* If nothing in our range is unlocked, we don't need to do anything */ + if (stm_is_locked_sr(nor, ofs, len, status_old)) + return 0; + + /* If anything below us is unlocked, we can't use 'bottom' protection */ + if (!stm_is_locked_sr(nor, 0, ofs, status_old)) + can_be_bottom = false; + + /* If anything above us is unlocked, we can't use 'top' protection */ + if (!stm_is_locked_sr(nor, ofs + len, mtd->size - (ofs + len), + status_old)) + can_be_top = false; + + if (!can_be_bottom && !can_be_top) + return -EINVAL; + + /* Prefer top, if both are valid */ + use_top = can_be_top; + + /* lock_len: length of region that should end up locked */ + if (use_top) + lock_len = mtd->size - ofs; + else + lock_len = ofs + len; + + /* + * Need smallest pow such that: + * + * 1 / (2^pow) <= (len / size) + * + * so (assuming power-of-2 size) we do: + * + * pow = ceil(log2(size / len)) = log2(size) - floor(log2(len)) + */ + pow = ilog2(mtd->size) - ilog2(lock_len); + val = mask - (pow << shift); + if (val & ~mask) + return -EINVAL; + /* Don't "lock" with no region! */ + if (!(val & mask)) + return -EINVAL; + + status_new = (status_old & ~mask & ~SR_TB) | val; + + /* Disallow further writes if WP pin is asserted */ + status_new |= SR_SRWD; + + if (!use_top) + status_new |= SR_TB; + + /* Don't bother if they're the same */ + if (status_new == status_old) + return 0; + + /* Only modify protection if it will not unlock other areas */ + if ((status_new & mask) < (status_old & mask)) + return -EINVAL; + + return write_sr_and_check(nor, status_new, mask); +} + +/* + * Unlock a region of the flash. See stm_lock() for more info + * + * Returns negative on errors, 0 on success. + */ +static int stm_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + struct mtd_info *mtd = &nor->mtd; + int status_old, status_new; + u8 mask = SR_BP2 | SR_BP1 | SR_BP0; + u8 shift = ffs(mask) - 1, pow, val; + loff_t lock_len; + bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB; + bool use_top; + + status_old = read_sr(nor); + if (status_old < 0) + return status_old; + + /* If nothing in our range is locked, we don't need to do anything */ + if (stm_is_unlocked_sr(nor, ofs, len, status_old)) + return 0; + + /* If anything below us is locked, we can't use 'top' protection */ + if (!stm_is_unlocked_sr(nor, 0, ofs, status_old)) + can_be_top = false; + + /* If anything above us is locked, we can't use 'bottom' protection */ + if (!stm_is_unlocked_sr(nor, ofs + len, mtd->size - (ofs + len), + status_old)) + can_be_bottom = false; + + if (!can_be_bottom && !can_be_top) + return -EINVAL; + + /* Prefer top, if both are valid */ + use_top = can_be_top; + + /* lock_len: length of region that should remain locked */ + if (use_top) + lock_len = mtd->size - (ofs + len); + else + lock_len = ofs; + + /* + * Need largest pow such that: + * + * 1 / (2^pow) >= (len / size) + * + * so (assuming power-of-2 size) we do: + * + * pow = floor(log2(size / len)) = log2(size) - ceil(log2(len)) + */ + pow = ilog2(mtd->size) - order_base_2(lock_len); + if (lock_len == 0) { + val = 0; /* fully unlocked */ + } else { + val = mask - (pow << shift); + /* Some power-of-two sizes are not supported */ + if (val & ~mask) + return -EINVAL; + } + + status_new = (status_old & ~mask & ~SR_TB) | val; + + /* Don't protect status register if we're fully unlocked */ + if (lock_len == 0) + status_new &= ~SR_SRWD; + + if (!use_top) + status_new |= SR_TB; + + /* Don't bother if they're the same */ + if (status_new == status_old) + return 0; + + /* Only modify protection if it will not lock other areas */ + if ((status_new & mask) > (status_old & mask)) + return -EINVAL; + + return write_sr_and_check(nor, status_new, mask); +} + +/* + * Check if a region of the flash is (completely) locked. See stm_lock() for + * more info. + * + * Returns 1 if entire region is locked, 0 if any portion is unlocked, and + * negative on errors. + */ +static int stm_is_locked(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + int status; + + status = read_sr(nor); + if (status < 0) + return status; + + return stm_is_locked_sr(nor, ofs, len, status); +} + +static const struct spi_nor_locking_ops stm_locking_ops = { + .lock = stm_lock, + .unlock = stm_unlock, + .is_locked = stm_is_locked, +}; + +static int spi_nor_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + struct spi_nor *nor = mtd_to_spi_nor(mtd); + int ret; + + ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_LOCK); + if (ret) + return ret; + + ret = nor->params.locking_ops->lock(nor, ofs, len); + + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_UNLOCK); + return ret; +} + +static int spi_nor_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + struct spi_nor *nor = mtd_to_spi_nor(mtd); + int ret; + + ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_UNLOCK); + if (ret) + return ret; + + ret = nor->params.locking_ops->unlock(nor, ofs, len); + + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_LOCK); + return ret; +} + +static int spi_nor_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + struct spi_nor *nor = mtd_to_spi_nor(mtd); + int ret; + + ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_UNLOCK); + if (ret) + return ret; + + ret = nor->params.locking_ops->is_locked(nor, ofs, len); + + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_LOCK); + return ret; +} + +/* + * Write status Register and configuration register with 2 bytes + * The first byte will be written to the status register, while the + * second byte will be written to the configuration register. + * Return negative if error occurred. + */ +static int write_sr_cr(struct spi_nor *nor, u8 *sr_cr) +{ + int ret; + + write_enable(nor); + + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(2, sr_cr, 1)); + + ret = spi_mem_exec_op(nor->spimem, &op); + } else { + ret = nor->write_reg(nor, SPINOR_OP_WRSR, sr_cr, 2); + } + + if (ret < 0) { + dev_err(nor->dev, + "error while writing configuration register\n"); + return -EINVAL; + } + + ret = spi_nor_wait_till_ready(nor); + if (ret) { + dev_err(nor->dev, + "timeout while writing configuration register\n"); + return ret; + } + + return 0; +} + +/** + * macronix_quad_enable() - set QE bit in Status Register. + * @nor: pointer to a 'struct spi_nor' + * + * Set the Quad Enable (QE) bit in the Status Register. + * + * bit 6 of the Status Register is the QE bit for Macronix like QSPI memories. + * + * Return: 0 on success, -errno otherwise. + */ +static int macronix_quad_enable(struct spi_nor *nor) +{ + int ret, val; + + val = read_sr(nor); + if (val < 0) + return val; + if (val & SR_QUAD_EN_MX) + return 0; + + write_enable(nor); + + write_sr(nor, val | SR_QUAD_EN_MX); + + ret = spi_nor_wait_till_ready(nor); + if (ret) + return ret; + + ret = read_sr(nor); + if (!(ret > 0 && (ret & SR_QUAD_EN_MX))) { + dev_err(nor->dev, "Macronix Quad bit not set\n"); + return -EINVAL; + } + + return 0; +} + +/** + * spansion_quad_enable() - set QE bit in Configuraiton Register. + * @nor: pointer to a 'struct spi_nor' + * + * Set the Quad Enable (QE) bit in the Configuration Register. + * This function is kept for legacy purpose because it has been used for a + * long time without anybody complaining but it should be considered as + * deprecated and maybe buggy. + * First, this function doesn't care about the previous values of the Status + * and Configuration Registers when it sets the QE bit (bit 1) in the + * Configuration Register: all other bits are cleared, which may have unwanted + * side effects like removing some block protections. + * Secondly, it uses the Read Configuration Register (35h) instruction though + * some very old and few memories don't support this instruction. If a pull-up + * resistor is present on the MISO/IO1 line, we might still be able to pass the + * "read back" test because the QSPI memory doesn't recognize the command, + * so leaves the MISO/IO1 line state unchanged, hence read_cr() returns 0xFF. + * + * bit 1 of the Configuration Register is the QE bit for Spansion like QSPI + * memories. + * + * Return: 0 on success, -errno otherwise. + */ +static int spansion_quad_enable(struct spi_nor *nor) +{ + u8 *sr_cr = nor->bouncebuf; + int ret; + + sr_cr[0] = 0; + sr_cr[1] = CR_QUAD_EN_SPAN; + ret = write_sr_cr(nor, sr_cr); + if (ret) + return ret; + + /* read back and check it */ + ret = read_cr(nor); + if (!(ret > 0 && (ret & CR_QUAD_EN_SPAN))) { + dev_err(nor->dev, "Spansion Quad bit not set\n"); + return -EINVAL; + } + + return 0; +} + +/** + * spansion_no_read_cr_quad_enable() - set QE bit in Configuration Register. + * @nor: pointer to a 'struct spi_nor' + * + * Set the Quad Enable (QE) bit in the Configuration Register. + * This function should be used with QSPI memories not supporting the Read + * Configuration Register (35h) instruction. + * + * bit 1 of the Configuration Register is the QE bit for Spansion like QSPI + * memories. + * + * Return: 0 on success, -errno otherwise. + */ +static int spansion_no_read_cr_quad_enable(struct spi_nor *nor) +{ + u8 *sr_cr = nor->bouncebuf; + int ret; + + /* Keep the current value of the Status Register. */ + ret = read_sr(nor); + if (ret < 0) { + dev_err(nor->dev, "error while reading status register\n"); + return -EINVAL; + } + sr_cr[0] = ret; + sr_cr[1] = CR_QUAD_EN_SPAN; + + return write_sr_cr(nor, sr_cr); +} + +/** + * spansion_read_cr_quad_enable() - set QE bit in Configuration Register. + * @nor: pointer to a 'struct spi_nor' + * + * Set the Quad Enable (QE) bit in the Configuration Register. + * This function should be used with QSPI memories supporting the Read + * Configuration Register (35h) instruction. + * + * bit 1 of the Configuration Register is the QE bit for Spansion like QSPI + * memories. + * + * Return: 0 on success, -errno otherwise. + */ +static int spansion_read_cr_quad_enable(struct spi_nor *nor) +{ + struct device *dev = nor->dev; + u8 *sr_cr = nor->bouncebuf; + int ret; + + /* Check current Quad Enable bit value. */ + ret = read_cr(nor); + if (ret < 0) { + dev_err(dev, "error while reading configuration register\n"); + return -EINVAL; + } + + if (ret & CR_QUAD_EN_SPAN) + return 0; + + sr_cr[1] = ret | CR_QUAD_EN_SPAN; + + /* Keep the current value of the Status Register. */ + ret = read_sr(nor); + if (ret < 0) { + dev_err(dev, "error while reading status register\n"); + return -EINVAL; + } + sr_cr[0] = ret; + + ret = write_sr_cr(nor, sr_cr); + if (ret) + return ret; + + /* Read back and check it. */ + ret = read_cr(nor); + if (!(ret > 0 && (ret & CR_QUAD_EN_SPAN))) { + dev_err(nor->dev, "Spansion Quad bit not set\n"); + return -EINVAL; + } + + return 0; +} + +static int spi_nor_write_sr2(struct spi_nor *nor, u8 *sr2) +{ + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR2, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(1, sr2, 1)); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->write_reg(nor, SPINOR_OP_WRSR2, sr2, 1); +} + +static int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2) +{ + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR2, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(1, sr2, 1)); + + return spi_mem_exec_op(nor->spimem, &op); + } + + return nor->read_reg(nor, SPINOR_OP_RDSR2, sr2, 1); +} + +/** + * sr2_bit7_quad_enable() - set QE bit in Status Register 2. + * @nor: pointer to a 'struct spi_nor' + * + * Set the Quad Enable (QE) bit in the Status Register 2. + * + * This is one of the procedures to set the QE bit described in the SFDP + * (JESD216 rev B) specification but no manufacturer using this procedure has + * been identified yet, hence the name of the function. + * + * Return: 0 on success, -errno otherwise. + */ +static int sr2_bit7_quad_enable(struct spi_nor *nor) +{ + u8 *sr2 = nor->bouncebuf; + int ret; + + /* Check current Quad Enable bit value. */ + ret = spi_nor_read_sr2(nor, sr2); + if (ret) + return ret; + if (*sr2 & SR2_QUAD_EN_BIT7) + return 0; + + /* Update the Quad Enable bit. */ + *sr2 |= SR2_QUAD_EN_BIT7; + + write_enable(nor); + + ret = spi_nor_write_sr2(nor, sr2); + if (ret < 0) { + dev_err(nor->dev, "error while writing status register 2\n"); + return -EINVAL; + } + + ret = spi_nor_wait_till_ready(nor); + if (ret < 0) { + dev_err(nor->dev, "timeout while writing status register 2\n"); + return ret; + } + + /* Read back and check it. */ + ret = spi_nor_read_sr2(nor, sr2); + if (!(ret > 0 && (*sr2 & SR2_QUAD_EN_BIT7))) { + dev_err(nor->dev, "SR2 Quad bit not set\n"); + return -EINVAL; + } + + return 0; +} + +/** + * spi_nor_clear_sr_bp() - clear the Status Register Block Protection bits. + * @nor: pointer to a 'struct spi_nor' + * + * Read-modify-write function that clears the Block Protection bits from the + * Status Register without affecting other bits. + * + * Return: 0 on success, -errno otherwise. + */ +static int spi_nor_clear_sr_bp(struct spi_nor *nor) +{ + int ret; + u8 mask = SR_BP2 | SR_BP1 | SR_BP0; + + ret = read_sr(nor); + if (ret < 0) { + dev_err(nor->dev, "error while reading status register\n"); + return ret; + } + + write_enable(nor); + + ret = write_sr(nor, ret & ~mask); + if (ret) { + dev_err(nor->dev, "write to status register failed\n"); + return ret; + } + + ret = spi_nor_wait_till_ready(nor); + if (ret) + dev_err(nor->dev, "timeout while writing status register\n"); + return ret; +} + +/** + * spi_nor_spansion_clear_sr_bp() - clear the Status Register Block Protection + * bits on spansion flashes. + * @nor: pointer to a 'struct spi_nor' + * + * Read-modify-write function that clears the Block Protection bits from the + * Status Register without affecting other bits. The function is tightly + * coupled with the spansion_quad_enable() function. Both assume that the Write + * Register with 16 bits, together with the Read Configuration Register (35h) + * instructions are supported. + * + * Return: 0 on success, -errno otherwise. + */ +static int spi_nor_spansion_clear_sr_bp(struct spi_nor *nor) +{ + int ret; + u8 mask = SR_BP2 | SR_BP1 | SR_BP0; + u8 *sr_cr = nor->bouncebuf; + + /* Check current Quad Enable bit value. */ + ret = read_cr(nor); + if (ret < 0) { + dev_err(nor->dev, + "error while reading configuration register\n"); + return ret; + } + + /* + * When the configuration register Quad Enable bit is one, only the + * Write Status (01h) command with two data bytes may be used. + */ + if (ret & CR_QUAD_EN_SPAN) { + sr_cr[1] = ret; + + ret = read_sr(nor); + if (ret < 0) { + dev_err(nor->dev, + "error while reading status register\n"); + return ret; + } + sr_cr[0] = ret & ~mask; + + ret = write_sr_cr(nor, sr_cr); + if (ret) + dev_err(nor->dev, "16-bit write register failed\n"); + return ret; + } + + /* + * If the Quad Enable bit is zero, use the Write Status (01h) command + * with one data byte. + */ + return spi_nor_clear_sr_bp(nor); +} + +/* Used when the "_ext_id" is two bytes at most */ +#define INFO(_jedec_id, _ext_id, _sector_size, _n_sectors, _flags) \ + .id = { \ + ((_jedec_id) >> 16) & 0xff, \ + ((_jedec_id) >> 8) & 0xff, \ + (_jedec_id) & 0xff, \ + ((_ext_id) >> 8) & 0xff, \ + (_ext_id) & 0xff, \ + }, \ + .id_len = (!(_jedec_id) ? 0 : (3 + ((_ext_id) ? 2 : 0))), \ + .sector_size = (_sector_size), \ + .n_sectors = (_n_sectors), \ + .page_size = 256, \ + .flags = (_flags), + +#define INFO6(_jedec_id, _ext_id, _sector_size, _n_sectors, _flags) \ + .id = { \ + ((_jedec_id) >> 16) & 0xff, \ + ((_jedec_id) >> 8) & 0xff, \ + (_jedec_id) & 0xff, \ + ((_ext_id) >> 16) & 0xff, \ + ((_ext_id) >> 8) & 0xff, \ + (_ext_id) & 0xff, \ + }, \ + .id_len = 6, \ + .sector_size = (_sector_size), \ + .n_sectors = (_n_sectors), \ + .page_size = 256, \ + .flags = (_flags), + +#define CAT25_INFO(_sector_size, _n_sectors, _page_size, _addr_width, _flags) \ + .sector_size = (_sector_size), \ + .n_sectors = (_n_sectors), \ + .page_size = (_page_size), \ + .addr_width = (_addr_width), \ + .flags = (_flags), + +#define S3AN_INFO(_jedec_id, _n_sectors, _page_size) \ + .id = { \ + ((_jedec_id) >> 16) & 0xff, \ + ((_jedec_id) >> 8) & 0xff, \ + (_jedec_id) & 0xff \ + }, \ + .id_len = 3, \ + .sector_size = (8*_page_size), \ + .n_sectors = (_n_sectors), \ + .page_size = _page_size, \ + .addr_width = 3, \ + .flags = SPI_NOR_NO_FR | SPI_S3AN, + +static int +is25lp256_post_bfpt_fixups(struct spi_nor *nor, + const struct sfdp_parameter_header *bfpt_header, + const struct sfdp_bfpt *bfpt, + struct spi_nor_flash_parameter *params) +{ + /* + * IS25LP256 supports 4B opcodes, but the BFPT advertises a + * BFPT_DWORD1_ADDRESS_BYTES_3_ONLY address width. + * Overwrite the address width advertised by the BFPT. + */ + if ((bfpt->dwords[BFPT_DWORD(1)] & BFPT_DWORD1_ADDRESS_BYTES_MASK) == + BFPT_DWORD1_ADDRESS_BYTES_3_ONLY) + nor->addr_width = 4; + + return 0; +} + +static struct spi_nor_fixups is25lp256_fixups = { + .post_bfpt = is25lp256_post_bfpt_fixups, +}; + +static int +mx25l25635_post_bfpt_fixups(struct spi_nor *nor, + const struct sfdp_parameter_header *bfpt_header, + const struct sfdp_bfpt *bfpt, + struct spi_nor_flash_parameter *params) +{ + /* + * MX25L25635F supports 4B opcodes but MX25L25635E does not. + * Unfortunately, Macronix has re-used the same JEDEC ID for both + * variants which prevents us from defining a new entry in the parts + * table. + * We need a way to differentiate MX25L25635E and MX25L25635F, and it + * seems that the F version advertises support for Fast Read 4-4-4 in + * its BFPT table. + */ + if (bfpt->dwords[BFPT_DWORD(5)] & BFPT_DWORD5_FAST_READ_4_4_4) + nor->flags |= SNOR_F_4B_OPCODES; + + return 0; +} + +static struct spi_nor_fixups mx25l25635_fixups = { + .post_bfpt = mx25l25635_post_bfpt_fixups, +}; + +static void gd25q256_default_init(struct spi_nor *nor) +{ + /* + * Some manufacturer like GigaDevice may use different + * bit to set QE on different memories, so the MFR can't + * indicate the quad_enable method for this case, we need + * to set it in the default_init fixup hook. + */ + nor->params.quad_enable = macronix_quad_enable; +} + +static struct spi_nor_fixups gd25q256_fixups = { + .default_init = gd25q256_default_init, +}; + +/* NOTE: double check command sets and memory organization when you add + * more nor chips. This current list focusses on newer chips, which + * have been converging on command sets which including JEDEC ID. + * + * All newly added entries should describe *hardware* and should use SECT_4K + * (or SECT_4K_PMC) if hardware supports erasing 4 KiB sectors. For usage + * scenarios excluding small sectors there is config option that can be + * disabled: CONFIG_MTD_SPI_NOR_USE_4K_SECTORS. + * For historical (and compatibility) reasons (before we got above config) some + * old entries may be missing 4K flag. + */ +static const struct flash_info spi_nor_ids[] = { + /* Atmel -- some are (confusingly) marketed as "DataFlash" */ + { "at25fs010", INFO(0x1f6601, 0, 32 * 1024, 4, SECT_4K) }, + { "at25fs040", INFO(0x1f6604, 0, 64 * 1024, 8, SECT_4K) }, + + { "at25df041a", INFO(0x1f4401, 0, 64 * 1024, 8, SECT_4K) }, + { "at25df321", INFO(0x1f4700, 0, 64 * 1024, 64, SECT_4K) }, + { "at25df321a", INFO(0x1f4701, 0, 64 * 1024, 64, SECT_4K) }, + { "at25df641", INFO(0x1f4800, 0, 64 * 1024, 128, SECT_4K) }, + + { "at26f004", INFO(0x1f0400, 0, 64 * 1024, 8, SECT_4K) }, + { "at26df081a", INFO(0x1f4501, 0, 64 * 1024, 16, SECT_4K) }, + { "at26df161a", INFO(0x1f4601, 0, 64 * 1024, 32, SECT_4K) }, + { "at26df321", INFO(0x1f4700, 0, 64 * 1024, 64, SECT_4K) }, + + { "at45db081d", INFO(0x1f2500, 0, 64 * 1024, 16, SECT_4K) }, + + /* EON -- en25xxx */ + { "en25f32", INFO(0x1c3116, 0, 64 * 1024, 64, SECT_4K) }, + { "en25p32", INFO(0x1c2016, 0, 64 * 1024, 64, 0) }, + { "en25q32b", INFO(0x1c3016, 0, 64 * 1024, 64, 0) }, + { "en25p64", INFO(0x1c2017, 0, 64 * 1024, 128, 0) }, + { "en25q64", INFO(0x1c3017, 0, 64 * 1024, 128, SECT_4K) }, + { "en25q80a", INFO(0x1c3014, 0, 64 * 1024, 16, + SECT_4K | SPI_NOR_DUAL_READ) }, + { "en25qh32", INFO(0x1c7016, 0, 64 * 1024, 64, 0) }, + { "en25qh64", INFO(0x1c7017, 0, 64 * 1024, 128, + SECT_4K | SPI_NOR_DUAL_READ) }, + { "en25qh128", INFO(0x1c7018, 0, 64 * 1024, 256, 0) }, + { "en25qh256", INFO(0x1c7019, 0, 64 * 1024, 512, 0) }, + { "en25s64", INFO(0x1c3817, 0, 64 * 1024, 128, SECT_4K) }, + + /* ESMT */ + { "f25l32pa", INFO(0x8c2016, 0, 64 * 1024, 64, SECT_4K | SPI_NOR_HAS_LOCK) }, + { "f25l32qa", INFO(0x8c4116, 0, 64 * 1024, 64, SECT_4K | SPI_NOR_HAS_LOCK) }, + { "f25l64qa", INFO(0x8c4117, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_HAS_LOCK) }, + + /* Everspin */ + { "mr25h128", CAT25_INFO( 16 * 1024, 1, 256, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) }, + { "mr25h256", CAT25_INFO( 32 * 1024, 1, 256, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) }, + { "mr25h10", CAT25_INFO(128 * 1024, 1, 256, 3, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) }, + { "mr25h40", CAT25_INFO(512 * 1024, 1, 256, 3, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) }, + + /* Fujitsu */ + { "mb85rs1mt", INFO(0x047f27, 0, 128 * 1024, 1, SPI_NOR_NO_ERASE) }, + + /* GigaDevice */ + { + "gd25q16", INFO(0xc84015, 0, 64 * 1024, 32, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) + }, + { + "gd25q32", INFO(0xc84016, 0, 64 * 1024, 64, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) + }, + { + "gd25lq32", INFO(0xc86016, 0, 64 * 1024, 64, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) + }, + { + "gd25q64", INFO(0xc84017, 0, 64 * 1024, 128, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) + }, + { + "gd25lq64c", INFO(0xc86017, 0, 64 * 1024, 128, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) + }, + { + "gd25q128", INFO(0xc84018, 0, 64 * 1024, 256, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) + }, + { + "gd25q256", INFO(0xc84019, 0, 64 * 1024, 512, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_4B_OPCODES | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) + .fixups = &gd25q256_fixups, + }, + + /* Intel/Numonyx -- xxxs33b */ + { "160s33b", INFO(0x898911, 0, 64 * 1024, 32, 0) }, + { "320s33b", INFO(0x898912, 0, 64 * 1024, 64, 0) }, + { "640s33b", INFO(0x898913, 0, 64 * 1024, 128, 0) }, + + /* ISSI */ + { "is25cd512", INFO(0x7f9d20, 0, 32 * 1024, 2, SECT_4K) }, + { "is25lq040b", INFO(0x9d4013, 0, 64 * 1024, 8, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "is25lp016d", INFO(0x9d6015, 0, 64 * 1024, 32, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "is25lp080d", INFO(0x9d6014, 0, 64 * 1024, 16, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "is25lp032", INFO(0x9d6016, 0, 64 * 1024, 64, + SECT_4K | SPI_NOR_DUAL_READ) }, + { "is25lp064", INFO(0x9d6017, 0, 64 * 1024, 128, + SECT_4K | SPI_NOR_DUAL_READ) }, + { "is25lp128", INFO(0x9d6018, 0, 64 * 1024, 256, + SECT_4K | SPI_NOR_DUAL_READ) }, + { "is25lp256", INFO(0x9d6019, 0, 64 * 1024, 512, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_4B_OPCODES) + .fixups = &is25lp256_fixups }, + { "is25wp032", INFO(0x9d7016, 0, 64 * 1024, 64, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "is25wp064", INFO(0x9d7017, 0, 64 * 1024, 128, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "is25wp128", INFO(0x9d7018, 0, 64 * 1024, 256, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + + /* Macronix */ + { "mx25l512e", INFO(0xc22010, 0, 64 * 1024, 1, SECT_4K) }, + { "mx25l2005a", INFO(0xc22012, 0, 64 * 1024, 4, SECT_4K) }, + { "mx25l4005a", INFO(0xc22013, 0, 64 * 1024, 8, SECT_4K) }, + { "mx25l8005", INFO(0xc22014, 0, 64 * 1024, 16, 0) }, + { "mx25l1606e", INFO(0xc22015, 0, 64 * 1024, 32, SECT_4K) }, + { "mx25l3205d", INFO(0xc22016, 0, 64 * 1024, 64, SECT_4K) }, + { "mx25l3255e", INFO(0xc29e16, 0, 64 * 1024, 64, SECT_4K) }, + { "mx25l6405d", INFO(0xc22017, 0, 64 * 1024, 128, SECT_4K) }, + { "mx25u2033e", INFO(0xc22532, 0, 64 * 1024, 4, SECT_4K) }, + { "mx25u3235f", INFO(0xc22536, 0, 64 * 1024, 64, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "mx25u4035", INFO(0xc22533, 0, 64 * 1024, 8, SECT_4K) }, + { "mx25u8035", INFO(0xc22534, 0, 64 * 1024, 16, SECT_4K) }, + { "mx25u6435f", INFO(0xc22537, 0, 64 * 1024, 128, SECT_4K) }, + { "mx25l12805d", INFO(0xc22018, 0, 64 * 1024, 256, 0) }, + { "mx25l12855e", INFO(0xc22618, 0, 64 * 1024, 256, 0) }, + { "mx25u12835f", INFO(0xc22538, 0, 64 * 1024, 256, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "mx25l25635e", INFO(0xc22019, 0, 64 * 1024, 512, + SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) + .fixups = &mx25l25635_fixups }, + { "mx25u25635f", INFO(0xc22539, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_4B_OPCODES) }, + { "mx25v8035f", INFO(0xc22314, 0, 64 * 1024, 16, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "mx25l25655e", INFO(0xc22619, 0, 64 * 1024, 512, 0) }, + { "mx66l51235f", INFO(0xc2201a, 0, 64 * 1024, 1024, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "mx66u51235f", INFO(0xc2253a, 0, 64 * 1024, 1024, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) }, + { "mx66l1g45g", INFO(0xc2201b, 0, 64 * 1024, 2048, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "mx66l1g55g", INFO(0xc2261b, 0, 64 * 1024, 2048, SPI_NOR_QUAD_READ) }, + + /* Micron <--> ST Micro */ + { "n25q016a", INFO(0x20bb15, 0, 64 * 1024, 32, SECT_4K | SPI_NOR_QUAD_READ) }, + { "n25q032", INFO(0x20ba16, 0, 64 * 1024, 64, SPI_NOR_QUAD_READ) }, + { "n25q032a", INFO(0x20bb16, 0, 64 * 1024, 64, SPI_NOR_QUAD_READ) }, + { "n25q064", INFO(0x20ba17, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_QUAD_READ) }, + { "n25q064a", INFO(0x20bb17, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_QUAD_READ) }, + { "n25q128a11", INFO(0x20bb18, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_QUAD_READ) }, + { "n25q128a13", INFO(0x20ba18, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_QUAD_READ) }, + { "n25q256a", INFO(0x20ba19, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "n25q256ax1", INFO(0x20bb19, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_QUAD_READ) }, + { "n25q512ax3", INFO(0x20ba20, 0, 64 * 1024, 1024, SECT_4K | USE_FSR | SPI_NOR_QUAD_READ) }, + { "n25q00", INFO(0x20ba21, 0, 64 * 1024, 2048, SECT_4K | USE_FSR | SPI_NOR_QUAD_READ | NO_CHIP_ERASE) }, + { "n25q00a", INFO(0x20bb21, 0, 64 * 1024, 2048, SECT_4K | USE_FSR | SPI_NOR_QUAD_READ | NO_CHIP_ERASE) }, + { "mt25ql02g", INFO(0x20ba22, 0, 64 * 1024, 4096, + SECT_4K | USE_FSR | SPI_NOR_QUAD_READ | + NO_CHIP_ERASE) }, + { "mt25qu512a (n25q512a)", INFO(0x20bb20, 0, 64 * 1024, 1024, + SECT_4K | USE_FSR | SPI_NOR_DUAL_READ | + SPI_NOR_QUAD_READ | + SPI_NOR_4B_OPCODES) }, + { "mt25qu02g", INFO(0x20bb22, 0, 64 * 1024, 4096, SECT_4K | USE_FSR | SPI_NOR_QUAD_READ | NO_CHIP_ERASE) }, + + /* Micron */ + { + "mt35xu512aba", INFO(0x2c5b1a, 0, 128 * 1024, 512, + SECT_4K | USE_FSR | SPI_NOR_OCTAL_READ | + SPI_NOR_4B_OPCODES) + }, + { "mt35xu02g", INFO(0x2c5b1c, 0, 128 * 1024, 2048, + SECT_4K | USE_FSR | SPI_NOR_OCTAL_READ | + SPI_NOR_4B_OPCODES) }, + + /* PMC */ + { "pm25lv512", INFO(0, 0, 32 * 1024, 2, SECT_4K_PMC) }, + { "pm25lv010", INFO(0, 0, 32 * 1024, 4, SECT_4K_PMC) }, + { "pm25lq032", INFO(0x7f9d46, 0, 64 * 1024, 64, SECT_4K) }, + + /* Spansion/Cypress -- single (large) sector size only, at least + * for the chips listed here (without boot sectors). + */ + { "s25sl032p", INFO(0x010215, 0x4d00, 64 * 1024, 64, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "s25sl064p", INFO(0x010216, 0x4d00, 64 * 1024, 128, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "s25fl128s0", INFO6(0x012018, 0x4d0080, 256 * 1024, 64, + SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) }, + { "s25fl128s1", INFO6(0x012018, 0x4d0180, 64 * 1024, 256, + SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) }, + { "s25fl256s0", INFO(0x010219, 0x4d00, 256 * 1024, 128, USE_CLSR) }, + { "s25fl256s1", INFO(0x010219, 0x4d01, 64 * 1024, 512, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) }, + { "s25fl512s", INFO6(0x010220, 0x4d0080, 256 * 1024, 256, + SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_HAS_LOCK | USE_CLSR) }, + { "s25fs512s", INFO6(0x010220, 0x4d0081, 256 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) }, + { "s70fl01gs", INFO(0x010221, 0x4d00, 256 * 1024, 256, 0) }, + { "s25sl12800", INFO(0x012018, 0x0300, 256 * 1024, 64, 0) }, + { "s25sl12801", INFO(0x012018, 0x0301, 64 * 1024, 256, 0) }, + { "s25fl129p0", INFO(0x012018, 0x4d00, 256 * 1024, 64, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) }, + { "s25fl129p1", INFO(0x012018, 0x4d01, 64 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) }, + { "s25sl004a", INFO(0x010212, 0, 64 * 1024, 8, 0) }, + { "s25sl008a", INFO(0x010213, 0, 64 * 1024, 16, 0) }, + { "s25sl016a", INFO(0x010214, 0, 64 * 1024, 32, 0) }, + { "s25sl032a", INFO(0x010215, 0, 64 * 1024, 64, 0) }, + { "s25sl064a", INFO(0x010216, 0, 64 * 1024, 128, 0) }, + { "s25fl004k", INFO(0xef4013, 0, 64 * 1024, 8, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "s25fl008k", INFO(0xef4014, 0, 64 * 1024, 16, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "s25fl016k", INFO(0xef4015, 0, 64 * 1024, 32, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "s25fl064k", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) }, + { "s25fl116k", INFO(0x014015, 0, 64 * 1024, 32, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "s25fl132k", INFO(0x014016, 0, 64 * 1024, 64, SECT_4K) }, + { "s25fl164k", INFO(0x014017, 0, 64 * 1024, 128, SECT_4K) }, + { "s25fl204k", INFO(0x014013, 0, 64 * 1024, 8, SECT_4K | SPI_NOR_DUAL_READ) }, + { "s25fl208k", INFO(0x014014, 0, 64 * 1024, 16, SECT_4K | SPI_NOR_DUAL_READ) }, + { "s25fl064l", INFO(0x016017, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) }, + { "s25fl128l", INFO(0x016018, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) }, + { "s25fl256l", INFO(0x016019, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) }, + + /* SST -- large erase sizes are "overlays", "sectors" are 4K */ + { "sst25vf040b", INFO(0xbf258d, 0, 64 * 1024, 8, SECT_4K | SST_WRITE) }, + { "sst25vf080b", INFO(0xbf258e, 0, 64 * 1024, 16, SECT_4K | SST_WRITE) }, + { "sst25vf016b", INFO(0xbf2541, 0, 64 * 1024, 32, SECT_4K | SST_WRITE) }, + { "sst25vf032b", INFO(0xbf254a, 0, 64 * 1024, 64, SECT_4K | SST_WRITE) }, + { "sst25vf064c", INFO(0xbf254b, 0, 64 * 1024, 128, SECT_4K) }, + { "sst25wf512", INFO(0xbf2501, 0, 64 * 1024, 1, SECT_4K | SST_WRITE) }, + { "sst25wf010", INFO(0xbf2502, 0, 64 * 1024, 2, SECT_4K | SST_WRITE) }, + { "sst25wf020", INFO(0xbf2503, 0, 64 * 1024, 4, SECT_4K | SST_WRITE) }, + { "sst25wf020a", INFO(0x621612, 0, 64 * 1024, 4, SECT_4K) }, + { "sst25wf040b", INFO(0x621613, 0, 64 * 1024, 8, SECT_4K) }, + { "sst25wf040", INFO(0xbf2504, 0, 64 * 1024, 8, SECT_4K | SST_WRITE) }, + { "sst25wf080", INFO(0xbf2505, 0, 64 * 1024, 16, SECT_4K | SST_WRITE) }, + { "sst26wf016b", INFO(0xbf2651, 0, 64 * 1024, 32, SECT_4K | + SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "sst26vf064b", INFO(0xbf2643, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + + /* ST Microelectronics -- newer production may have feature updates */ + { "m25p05", INFO(0x202010, 0, 32 * 1024, 2, 0) }, + { "m25p10", INFO(0x202011, 0, 32 * 1024, 4, 0) }, + { "m25p20", INFO(0x202012, 0, 64 * 1024, 4, 0) }, + { "m25p40", INFO(0x202013, 0, 64 * 1024, 8, 0) }, + { "m25p80", INFO(0x202014, 0, 64 * 1024, 16, 0) }, + { "m25p16", INFO(0x202015, 0, 64 * 1024, 32, 0) }, + { "m25p32", INFO(0x202016, 0, 64 * 1024, 64, 0) }, + { "m25p64", INFO(0x202017, 0, 64 * 1024, 128, 0) }, + { "m25p128", INFO(0x202018, 0, 256 * 1024, 64, 0) }, + + { "m25p05-nonjedec", INFO(0, 0, 32 * 1024, 2, 0) }, + { "m25p10-nonjedec", INFO(0, 0, 32 * 1024, 4, 0) }, + { "m25p20-nonjedec", INFO(0, 0, 64 * 1024, 4, 0) }, + { "m25p40-nonjedec", INFO(0, 0, 64 * 1024, 8, 0) }, + { "m25p80-nonjedec", INFO(0, 0, 64 * 1024, 16, 0) }, + { "m25p16-nonjedec", INFO(0, 0, 64 * 1024, 32, 0) }, + { "m25p32-nonjedec", INFO(0, 0, 64 * 1024, 64, 0) }, + { "m25p64-nonjedec", INFO(0, 0, 64 * 1024, 128, 0) }, + { "m25p128-nonjedec", INFO(0, 0, 256 * 1024, 64, 0) }, + + { "m45pe10", INFO(0x204011, 0, 64 * 1024, 2, 0) }, + { "m45pe80", INFO(0x204014, 0, 64 * 1024, 16, 0) }, + { "m45pe16", INFO(0x204015, 0, 64 * 1024, 32, 0) }, + + { "m25pe20", INFO(0x208012, 0, 64 * 1024, 4, 0) }, + { "m25pe80", INFO(0x208014, 0, 64 * 1024, 16, 0) }, + { "m25pe16", INFO(0x208015, 0, 64 * 1024, 32, SECT_4K) }, + + { "m25px16", INFO(0x207115, 0, 64 * 1024, 32, SECT_4K) }, + { "m25px32", INFO(0x207116, 0, 64 * 1024, 64, SECT_4K) }, + { "m25px32-s0", INFO(0x207316, 0, 64 * 1024, 64, SECT_4K) }, + { "m25px32-s1", INFO(0x206316, 0, 64 * 1024, 64, SECT_4K) }, + { "m25px64", INFO(0x207117, 0, 64 * 1024, 128, 0) }, + { "m25px80", INFO(0x207114, 0, 64 * 1024, 16, 0) }, + + /* Winbond -- w25x "blocks" are 64K, "sectors" are 4KiB */ + { "w25x05", INFO(0xef3010, 0, 64 * 1024, 1, SECT_4K) }, + { "w25x10", INFO(0xef3011, 0, 64 * 1024, 2, SECT_4K) }, + { "w25x20", INFO(0xef3012, 0, 64 * 1024, 4, SECT_4K) }, + { "w25x40", INFO(0xef3013, 0, 64 * 1024, 8, SECT_4K) }, + { "w25x80", INFO(0xef3014, 0, 64 * 1024, 16, SECT_4K) }, + { "w25x16", INFO(0xef3015, 0, 64 * 1024, 32, SECT_4K) }, + { + "w25q16dw", INFO(0xef6015, 0, 64 * 1024, 32, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) + }, + { "w25x32", INFO(0xef3016, 0, 64 * 1024, 64, SECT_4K) }, + { + "w25q16jv-im/jm", INFO(0xef7015, 0, 64 * 1024, 32, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) + }, + { "w25q20cl", INFO(0xef4012, 0, 64 * 1024, 4, SECT_4K) }, + { "w25q20bw", INFO(0xef5012, 0, 64 * 1024, 4, SECT_4K) }, + { "w25q20ew", INFO(0xef6012, 0, 64 * 1024, 4, SECT_4K) }, + { "w25q32", INFO(0xef4016, 0, 64 * 1024, 64, SECT_4K) }, + { + "w25q32dw", INFO(0xef6016, 0, 64 * 1024, 64, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) + }, + { + "w25q32jv", INFO(0xef7016, 0, 64 * 1024, 64, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) + }, + { "w25x64", INFO(0xef3017, 0, 64 * 1024, 128, SECT_4K) }, + { "w25q64", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) }, + { + "w25q64dw", INFO(0xef6017, 0, 64 * 1024, 128, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) + }, + { + "w25q128fw", INFO(0xef6018, 0, 64 * 1024, 256, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) + }, + { + "w25q128jv", INFO(0xef7018, 0, 64 * 1024, 256, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) + }, + { "w25q80", INFO(0xef5014, 0, 64 * 1024, 16, SECT_4K) }, + { "w25q80bl", INFO(0xef4014, 0, 64 * 1024, 16, SECT_4K) }, + { "w25q128", INFO(0xef4018, 0, 64 * 1024, 256, SECT_4K) }, + { "w25q256", INFO(0xef4019, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "w25q256jvm", INFO(0xef7019, 0, 64 * 1024, 512, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "w25q512jv", INFO(0xef4020, 0, 64 * 1024, 1024, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) }, + { "w25m512jv", INFO(0xef7119, 0, 64 * 1024, 1024, + SECT_4K | SPI_NOR_QUAD_READ | SPI_NOR_DUAL_READ) }, + + /* Catalyst / On Semiconductor -- non-JEDEC */ + { "cat25c11", CAT25_INFO( 16, 8, 16, 1, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) }, + { "cat25c03", CAT25_INFO( 32, 8, 16, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) }, + { "cat25c09", CAT25_INFO( 128, 8, 32, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) }, + { "cat25c17", CAT25_INFO( 256, 8, 32, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) }, + { "cat25128", CAT25_INFO(2048, 8, 64, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) }, + + /* Xilinx S3AN Internal Flash */ + { "3S50AN", S3AN_INFO(0x1f2200, 64, 264) }, + { "3S200AN", S3AN_INFO(0x1f2400, 256, 264) }, + { "3S400AN", S3AN_INFO(0x1f2400, 256, 264) }, + { "3S700AN", S3AN_INFO(0x1f2500, 512, 264) }, + { "3S1400AN", S3AN_INFO(0x1f2600, 512, 528) }, + + /* XMC (Wuhan Xinxin Semiconductor Manufacturing Corp.) */ + { "XM25QH64A", INFO(0x207017, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "XM25QH128A", INFO(0x207018, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { }, +}; + +static const struct flash_info *spi_nor_read_id(struct spi_nor *nor) +{ + int tmp; + u8 *id = nor->bouncebuf; + const struct flash_info *info; + + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDID, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(SPI_NOR_MAX_ID_LEN, id, 1)); + + tmp = spi_mem_exec_op(nor->spimem, &op); + } else { + tmp = nor->read_reg(nor, SPINOR_OP_RDID, id, + SPI_NOR_MAX_ID_LEN); + } + if (tmp < 0) { + dev_err(nor->dev, "error %d reading JEDEC ID\n", tmp); + return ERR_PTR(tmp); + } + + for (tmp = 0; tmp < ARRAY_SIZE(spi_nor_ids) - 1; tmp++) { + info = &spi_nor_ids[tmp]; + if (info->id_len) { + if (!memcmp(info->id, id, info->id_len)) + return &spi_nor_ids[tmp]; + } + } + dev_err(nor->dev, "unrecognized JEDEC id bytes: %*ph\n", + SPI_NOR_MAX_ID_LEN, id); + return ERR_PTR(-ENODEV); +} + +static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct spi_nor *nor = mtd_to_spi_nor(mtd); + int ret; + + dev_dbg(nor->dev, "from 0x%08x, len %zd\n", (u32)from, len); + + ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_READ); + if (ret) + return ret; + + while (len) { + loff_t addr = from; + + addr = spi_nor_convert_addr(nor, addr); + + ret = spi_nor_read_data(nor, addr, len, buf); + if (ret == 0) { + /* We shouldn't see 0-length reads */ + ret = -EIO; + goto read_err; + } + if (ret < 0) + goto read_err; + + WARN_ON(ret > len); + *retlen += ret; + buf += ret; + from += ret; + len -= ret; + } + ret = 0; + +read_err: + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_READ); + return ret; +} + +static int sst_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct spi_nor *nor = mtd_to_spi_nor(mtd); + size_t actual; + int ret; + + dev_dbg(nor->dev, "to 0x%08x, len %zd\n", (u32)to, len); + + ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_WRITE); + if (ret) + return ret; + + write_enable(nor); + + nor->sst_write_second = false; + + actual = to % 2; + /* Start write from odd address. */ + if (actual) { + nor->program_opcode = SPINOR_OP_BP; + + /* write one byte. */ + ret = spi_nor_write_data(nor, to, 1, buf); + if (ret < 0) + goto sst_write_err; + WARN(ret != 1, "While writing 1 byte written %i bytes\n", + (int)ret); + ret = spi_nor_wait_till_ready(nor); + if (ret) + goto sst_write_err; + } + to += actual; + + /* Write out most of the data here. */ + for (; actual < len - 1; actual += 2) { + nor->program_opcode = SPINOR_OP_AAI_WP; + + /* write two bytes. */ + ret = spi_nor_write_data(nor, to, 2, buf + actual); + if (ret < 0) + goto sst_write_err; + WARN(ret != 2, "While writing 2 bytes written %i bytes\n", + (int)ret); + ret = spi_nor_wait_till_ready(nor); + if (ret) + goto sst_write_err; + to += 2; + nor->sst_write_second = true; + } + nor->sst_write_second = false; + + write_disable(nor); + ret = spi_nor_wait_till_ready(nor); + if (ret) + goto sst_write_err; + + /* Write out trailing byte if it exists. */ + if (actual != len) { + write_enable(nor); + + nor->program_opcode = SPINOR_OP_BP; + ret = spi_nor_write_data(nor, to, 1, buf + actual); + if (ret < 0) + goto sst_write_err; + WARN(ret != 1, "While writing 1 byte written %i bytes\n", + (int)ret); + ret = spi_nor_wait_till_ready(nor); + if (ret) + goto sst_write_err; + write_disable(nor); + actual += 1; + } +sst_write_err: + *retlen += actual; + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_WRITE); + return ret; +} + +/* + * Write an address range to the nor chip. Data must be written in + * FLASH_PAGESIZE chunks. The address range may be any size provided + * it is within the physical boundaries. + */ +static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct spi_nor *nor = mtd_to_spi_nor(mtd); + size_t page_offset, page_remain, i; + ssize_t ret; + + dev_dbg(nor->dev, "to 0x%08x, len %zd\n", (u32)to, len); + + ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_WRITE); + if (ret) + return ret; + + for (i = 0; i < len; ) { + ssize_t written; + loff_t addr = to + i; + + /* + * If page_size is a power of two, the offset can be quickly + * calculated with an AND operation. On the other cases we + * need to do a modulus operation (more expensive). + * Power of two numbers have only one bit set and we can use + * the instruction hweight32 to detect if we need to do a + * modulus (do_div()) or not. + */ + if (hweight32(nor->page_size) == 1) { + page_offset = addr & (nor->page_size - 1); + } else { + uint64_t aux = addr; + + page_offset = do_div(aux, nor->page_size); + } + /* the size of data remaining on the first page */ + page_remain = min_t(size_t, + nor->page_size - page_offset, len - i); + + addr = spi_nor_convert_addr(nor, addr); + + write_enable(nor); + ret = spi_nor_write_data(nor, addr, page_remain, buf + i); + if (ret < 0) + goto write_err; + written = ret; + + ret = spi_nor_wait_till_ready(nor); + if (ret) + goto write_err; + *retlen += written; + i += written; + } + +write_err: + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_WRITE); + return ret; +} + +static int spi_nor_check(struct spi_nor *nor) +{ + if (!nor->dev || + (!nor->spimem && + (!nor->read || !nor->write || !nor->read_reg || + !nor->write_reg))) { + pr_err("spi-nor: please fill all the necessary fields!\n"); + return -EINVAL; + } + + return 0; +} + +static int s3an_nor_setup(struct spi_nor *nor, + const struct spi_nor_hwcaps *hwcaps) +{ + int ret; + + ret = spi_nor_xread_sr(nor, nor->bouncebuf); + if (ret < 0) { + dev_err(nor->dev, "error %d reading XRDSR\n", (int) ret); + return ret; + } + + nor->erase_opcode = SPINOR_OP_XSE; + nor->program_opcode = SPINOR_OP_XPP; + nor->read_opcode = SPINOR_OP_READ; + nor->flags |= SNOR_F_NO_OP_CHIP_ERASE; + + /* + * This flashes have a page size of 264 or 528 bytes (known as + * Default addressing mode). It can be changed to a more standard + * Power of two mode where the page size is 256/512. This comes + * with a price: there is 3% less of space, the data is corrupted + * and the page size cannot be changed back to default addressing + * mode. + * + * The current addressing mode can be read from the XRDSR register + * and should not be changed, because is a destructive operation. + */ + if (nor->bouncebuf[0] & XSR_PAGESIZE) { + /* Flash in Power of 2 mode */ + nor->page_size = (nor->page_size == 264) ? 256 : 512; + nor->mtd.writebufsize = nor->page_size; + nor->mtd.size = 8 * nor->page_size * nor->info->n_sectors; + nor->mtd.erasesize = 8 * nor->page_size; + } else { + /* Flash in Default addressing mode */ + nor->params.convert_addr = s3an_convert_addr; + nor->mtd.erasesize = nor->info->sector_size; + } + + return 0; +} + +static void +spi_nor_set_read_settings(struct spi_nor_read_command *read, + u8 num_mode_clocks, + u8 num_wait_states, + u8 opcode, + enum spi_nor_protocol proto) +{ + read->num_mode_clocks = num_mode_clocks; + read->num_wait_states = num_wait_states; + read->opcode = opcode; + read->proto = proto; +} + +static void +spi_nor_set_pp_settings(struct spi_nor_pp_command *pp, + u8 opcode, + enum spi_nor_protocol proto) +{ + pp->opcode = opcode; + pp->proto = proto; +} + +static int spi_nor_hwcaps2cmd(u32 hwcaps, const int table[][2], size_t size) +{ + size_t i; + + for (i = 0; i < size; i++) + if (table[i][0] == (int)hwcaps) + return table[i][1]; + + return -EINVAL; +} + +static int spi_nor_hwcaps_read2cmd(u32 hwcaps) +{ + static const int hwcaps_read2cmd[][2] = { + { SNOR_HWCAPS_READ, SNOR_CMD_READ }, + { SNOR_HWCAPS_READ_FAST, SNOR_CMD_READ_FAST }, + { SNOR_HWCAPS_READ_1_1_1_DTR, SNOR_CMD_READ_1_1_1_DTR }, + { SNOR_HWCAPS_READ_1_1_2, SNOR_CMD_READ_1_1_2 }, + { SNOR_HWCAPS_READ_1_2_2, SNOR_CMD_READ_1_2_2 }, + { SNOR_HWCAPS_READ_2_2_2, SNOR_CMD_READ_2_2_2 }, + { SNOR_HWCAPS_READ_1_2_2_DTR, SNOR_CMD_READ_1_2_2_DTR }, + { SNOR_HWCAPS_READ_1_1_4, SNOR_CMD_READ_1_1_4 }, + { SNOR_HWCAPS_READ_1_4_4, SNOR_CMD_READ_1_4_4 }, + { SNOR_HWCAPS_READ_4_4_4, SNOR_CMD_READ_4_4_4 }, + { SNOR_HWCAPS_READ_1_4_4_DTR, SNOR_CMD_READ_1_4_4_DTR }, + { SNOR_HWCAPS_READ_1_1_8, SNOR_CMD_READ_1_1_8 }, + { SNOR_HWCAPS_READ_1_8_8, SNOR_CMD_READ_1_8_8 }, + { SNOR_HWCAPS_READ_8_8_8, SNOR_CMD_READ_8_8_8 }, + { SNOR_HWCAPS_READ_1_8_8_DTR, SNOR_CMD_READ_1_8_8_DTR }, + }; + + return spi_nor_hwcaps2cmd(hwcaps, hwcaps_read2cmd, + ARRAY_SIZE(hwcaps_read2cmd)); +} + +static int spi_nor_hwcaps_pp2cmd(u32 hwcaps) +{ + static const int hwcaps_pp2cmd[][2] = { + { SNOR_HWCAPS_PP, SNOR_CMD_PP }, + { SNOR_HWCAPS_PP_1_1_4, SNOR_CMD_PP_1_1_4 }, + { SNOR_HWCAPS_PP_1_4_4, SNOR_CMD_PP_1_4_4 }, + { SNOR_HWCAPS_PP_4_4_4, SNOR_CMD_PP_4_4_4 }, + { SNOR_HWCAPS_PP_1_1_8, SNOR_CMD_PP_1_1_8 }, + { SNOR_HWCAPS_PP_1_8_8, SNOR_CMD_PP_1_8_8 }, + { SNOR_HWCAPS_PP_8_8_8, SNOR_CMD_PP_8_8_8 }, + }; + + return spi_nor_hwcaps2cmd(hwcaps, hwcaps_pp2cmd, + ARRAY_SIZE(hwcaps_pp2cmd)); +} + +/* + * Serial Flash Discoverable Parameters (SFDP) parsing. + */ + +/** + * spi_nor_read_raw() - raw read of serial flash memory. read_opcode, + * addr_width and read_dummy members of the struct spi_nor + * should be previously + * set. + * @nor: pointer to a 'struct spi_nor' + * @addr: offset in the serial flash memory + * @len: number of bytes to read + * @buf: buffer where the data is copied into (dma-safe memory) + * + * Return: 0 on success, -errno otherwise. + */ +static int spi_nor_read_raw(struct spi_nor *nor, u32 addr, size_t len, u8 *buf) +{ + int ret; + + while (len) { + ret = spi_nor_read_data(nor, addr, len, buf); + if (ret < 0) + return ret; + if (!ret || ret > len) + return -EIO; + + buf += ret; + addr += ret; + len -= ret; + } + return 0; +} + +/** + * spi_nor_read_sfdp() - read Serial Flash Discoverable Parameters. + * @nor: pointer to a 'struct spi_nor' + * @addr: offset in the SFDP area to start reading data from + * @len: number of bytes to read + * @buf: buffer where the SFDP data are copied into (dma-safe memory) + * + * Whatever the actual numbers of bytes for address and dummy cycles are + * for (Fast) Read commands, the Read SFDP (5Ah) instruction is always + * followed by a 3-byte address and 8 dummy clock cycles. + * + * Return: 0 on success, -errno otherwise. + */ +static int spi_nor_read_sfdp(struct spi_nor *nor, u32 addr, + size_t len, void *buf) +{ + u8 addr_width, read_opcode, read_dummy; + int ret; + + read_opcode = nor->read_opcode; + addr_width = nor->addr_width; + read_dummy = nor->read_dummy; + + nor->read_opcode = SPINOR_OP_RDSFDP; + nor->addr_width = 3; + nor->read_dummy = 8; + + ret = spi_nor_read_raw(nor, addr, len, buf); + + nor->read_opcode = read_opcode; + nor->addr_width = addr_width; + nor->read_dummy = read_dummy; + + return ret; +} + +/** + * spi_nor_spimem_check_op - check if the operation is supported + * by controller + *@nor: pointer to a 'struct spi_nor' + *@op: pointer to op template to be checked + * + * Returns 0 if operation is supported, -ENOTSUPP otherwise. + */ +static int spi_nor_spimem_check_op(struct spi_nor *nor, + struct spi_mem_op *op) +{ + /* + * First test with 4 address bytes. The opcode itself might + * be a 3B addressing opcode but we don't care, because + * SPI controller implementation should not check the opcode, + * but just the sequence. + */ + op->addr.nbytes = 4; + if (!spi_mem_supports_op(nor->spimem, op)) { + if (nor->mtd.size > SZ_16M) + return -ENOTSUPP; + + /* If flash size <= 16MB, 3 address bytes are sufficient */ + op->addr.nbytes = 3; + if (!spi_mem_supports_op(nor->spimem, op)) + return -ENOTSUPP; + } + + return 0; +} + +/** + * spi_nor_spimem_check_readop - check if the read op is supported + * by controller + *@nor: pointer to a 'struct spi_nor' + *@read: pointer to op template to be checked + * + * Returns 0 if operation is supported, -ENOTSUPP otherwise. + */ +static int spi_nor_spimem_check_readop(struct spi_nor *nor, + const struct spi_nor_read_command *read) +{ + struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(read->opcode, 1), + SPI_MEM_OP_ADDR(3, 0, 1), + SPI_MEM_OP_DUMMY(0, 1), + SPI_MEM_OP_DATA_IN(0, NULL, 1)); + + op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(read->proto); + op.addr.buswidth = spi_nor_get_protocol_addr_nbits(read->proto); + op.data.buswidth = spi_nor_get_protocol_data_nbits(read->proto); + op.dummy.buswidth = op.addr.buswidth; + op.dummy.nbytes = (read->num_mode_clocks + read->num_wait_states) * + op.dummy.buswidth / 8; + + return spi_nor_spimem_check_op(nor, &op); +} + +/** + * spi_nor_spimem_check_pp - check if the page program op is supported + * by controller + *@nor: pointer to a 'struct spi_nor' + *@pp: pointer to op template to be checked + * + * Returns 0 if operation is supported, -ENOTSUPP otherwise. + */ +static int spi_nor_spimem_check_pp(struct spi_nor *nor, + const struct spi_nor_pp_command *pp) +{ + struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(pp->opcode, 1), + SPI_MEM_OP_ADDR(3, 0, 1), + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(0, NULL, 1)); + + op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(pp->proto); + op.addr.buswidth = spi_nor_get_protocol_addr_nbits(pp->proto); + op.data.buswidth = spi_nor_get_protocol_data_nbits(pp->proto); + + return spi_nor_spimem_check_op(nor, &op); +} + +/** + * spi_nor_spimem_adjust_hwcaps - Find optimal Read/Write protocol + * based on SPI controller capabilities + * @nor: pointer to a 'struct spi_nor' + * @hwcaps: pointer to resulting capabilities after adjusting + * according to controller and flash's capability + */ +static void +spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, u32 *hwcaps) +{ + struct spi_nor_flash_parameter *params = &nor->params; + unsigned int cap; + + /* DTR modes are not supported yet, mask them all. */ + *hwcaps &= ~SNOR_HWCAPS_DTR; + + /* X-X-X modes are not supported yet, mask them all. */ + *hwcaps &= ~SNOR_HWCAPS_X_X_X; + + for (cap = 0; cap < sizeof(*hwcaps) * BITS_PER_BYTE; cap++) { + int rdidx, ppidx; + + if (!(*hwcaps & BIT(cap))) + continue; + + rdidx = spi_nor_hwcaps_read2cmd(BIT(cap)); + if (rdidx >= 0 && + spi_nor_spimem_check_readop(nor, ¶ms->reads[rdidx])) + *hwcaps &= ~BIT(cap); + + ppidx = spi_nor_hwcaps_pp2cmd(BIT(cap)); + if (ppidx < 0) + continue; + + if (spi_nor_spimem_check_pp(nor, + ¶ms->page_programs[ppidx])) + *hwcaps &= ~BIT(cap); + } +} + +/** + * spi_nor_read_sfdp_dma_unsafe() - read Serial Flash Discoverable Parameters. + * @nor: pointer to a 'struct spi_nor' + * @addr: offset in the SFDP area to start reading data from + * @len: number of bytes to read + * @buf: buffer where the SFDP data are copied into + * + * Wrap spi_nor_read_sfdp() using a kmalloc'ed bounce buffer as @buf is now not + * guaranteed to be dma-safe. + * + * Return: -ENOMEM if kmalloc() fails, the return code of spi_nor_read_sfdp() + * otherwise. + */ +static int spi_nor_read_sfdp_dma_unsafe(struct spi_nor *nor, u32 addr, + size_t len, void *buf) +{ + void *dma_safe_buf; + int ret; + + dma_safe_buf = kmalloc(len, GFP_KERNEL); + if (!dma_safe_buf) + return -ENOMEM; + + ret = spi_nor_read_sfdp(nor, addr, len, dma_safe_buf); + memcpy(buf, dma_safe_buf, len); + kfree(dma_safe_buf); + + return ret; +} + +/* Fast Read settings. */ + +static void +spi_nor_set_read_settings_from_bfpt(struct spi_nor_read_command *read, + u16 half, + enum spi_nor_protocol proto) +{ + read->num_mode_clocks = (half >> 5) & 0x07; + read->num_wait_states = (half >> 0) & 0x1f; + read->opcode = (half >> 8) & 0xff; + read->proto = proto; +} + +struct sfdp_bfpt_read { + /* The Fast Read x-y-z hardware capability in params->hwcaps.mask. */ + u32 hwcaps; + + /* + * The <supported_bit> bit in <supported_dword> BFPT DWORD tells us + * whether the Fast Read x-y-z command is supported. + */ + u32 supported_dword; + u32 supported_bit; + + /* + * The half-word at offset <setting_shift> in <setting_dword> BFPT DWORD + * encodes the op code, the number of mode clocks and the number of wait + * states to be used by Fast Read x-y-z command. + */ + u32 settings_dword; + u32 settings_shift; + + /* The SPI protocol for this Fast Read x-y-z command. */ + enum spi_nor_protocol proto; +}; + +static const struct sfdp_bfpt_read sfdp_bfpt_reads[] = { + /* Fast Read 1-1-2 */ + { + SNOR_HWCAPS_READ_1_1_2, + BFPT_DWORD(1), BIT(16), /* Supported bit */ + BFPT_DWORD(4), 0, /* Settings */ + SNOR_PROTO_1_1_2, + }, + + /* Fast Read 1-2-2 */ + { + SNOR_HWCAPS_READ_1_2_2, + BFPT_DWORD(1), BIT(20), /* Supported bit */ + BFPT_DWORD(4), 16, /* Settings */ + SNOR_PROTO_1_2_2, + }, + + /* Fast Read 2-2-2 */ + { + SNOR_HWCAPS_READ_2_2_2, + BFPT_DWORD(5), BIT(0), /* Supported bit */ + BFPT_DWORD(6), 16, /* Settings */ + SNOR_PROTO_2_2_2, + }, + + /* Fast Read 1-1-4 */ + { + SNOR_HWCAPS_READ_1_1_4, + BFPT_DWORD(1), BIT(22), /* Supported bit */ + BFPT_DWORD(3), 16, /* Settings */ + SNOR_PROTO_1_1_4, + }, + + /* Fast Read 1-4-4 */ + { + SNOR_HWCAPS_READ_1_4_4, + BFPT_DWORD(1), BIT(21), /* Supported bit */ + BFPT_DWORD(3), 0, /* Settings */ + SNOR_PROTO_1_4_4, + }, + + /* Fast Read 4-4-4 */ + { + SNOR_HWCAPS_READ_4_4_4, + BFPT_DWORD(5), BIT(4), /* Supported bit */ + BFPT_DWORD(7), 16, /* Settings */ + SNOR_PROTO_4_4_4, + }, +}; + +struct sfdp_bfpt_erase { + /* + * The half-word at offset <shift> in DWORD <dwoard> encodes the + * op code and erase sector size to be used by Sector Erase commands. + */ + u32 dword; + u32 shift; +}; + +static const struct sfdp_bfpt_erase sfdp_bfpt_erases[] = { + /* Erase Type 1 in DWORD8 bits[15:0] */ + {BFPT_DWORD(8), 0}, + + /* Erase Type 2 in DWORD8 bits[31:16] */ + {BFPT_DWORD(8), 16}, + + /* Erase Type 3 in DWORD9 bits[15:0] */ + {BFPT_DWORD(9), 0}, + + /* Erase Type 4 in DWORD9 bits[31:16] */ + {BFPT_DWORD(9), 16}, +}; + +/** + * spi_nor_set_erase_type() - set a SPI NOR erase type + * @erase: pointer to a structure that describes a SPI NOR erase type + * @size: the size of the sector/block erased by the erase type + * @opcode: the SPI command op code to erase the sector/block + */ +static void spi_nor_set_erase_type(struct spi_nor_erase_type *erase, + u32 size, u8 opcode) +{ + erase->size = size; + erase->opcode = opcode; + /* JEDEC JESD216B Standard imposes erase sizes to be power of 2. */ + erase->size_shift = ffs(erase->size) - 1; + erase->size_mask = (1 << erase->size_shift) - 1; +} + +/** + * spi_nor_set_erase_settings_from_bfpt() - set erase type settings from BFPT + * @erase: pointer to a structure that describes a SPI NOR erase type + * @size: the size of the sector/block erased by the erase type + * @opcode: the SPI command op code to erase the sector/block + * @i: erase type index as sorted in the Basic Flash Parameter Table + * + * The supported Erase Types will be sorted at init in ascending order, with + * the smallest Erase Type size being the first member in the erase_type array + * of the spi_nor_erase_map structure. Save the Erase Type index as sorted in + * the Basic Flash Parameter Table since it will be used later on to + * synchronize with the supported Erase Types defined in SFDP optional tables. + */ +static void +spi_nor_set_erase_settings_from_bfpt(struct spi_nor_erase_type *erase, + u32 size, u8 opcode, u8 i) +{ + erase->idx = i; + spi_nor_set_erase_type(erase, size, opcode); +} + +/** + * spi_nor_map_cmp_erase_type() - compare the map's erase types by size + * @l: member in the left half of the map's erase_type array + * @r: member in the right half of the map's erase_type array + * + * Comparison function used in the sort() call to sort in ascending order the + * map's erase types, the smallest erase type size being the first member in the + * sorted erase_type array. + * + * Return: the result of @l->size - @r->size + */ +static int spi_nor_map_cmp_erase_type(const void *l, const void *r) +{ + const struct spi_nor_erase_type *left = l, *right = r; + + return left->size - right->size; +} + +/** + * spi_nor_sort_erase_mask() - sort erase mask + * @map: the erase map of the SPI NOR + * @erase_mask: the erase type mask to be sorted + * + * Replicate the sort done for the map's erase types in BFPT: sort the erase + * mask in ascending order with the smallest erase type size starting from + * BIT(0) in the sorted erase mask. + * + * Return: sorted erase mask. + */ +static u8 spi_nor_sort_erase_mask(struct spi_nor_erase_map *map, u8 erase_mask) +{ + struct spi_nor_erase_type *erase_type = map->erase_type; + int i; + u8 sorted_erase_mask = 0; + + if (!erase_mask) + return 0; + + /* Replicate the sort done for the map's erase types. */ + for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) + if (erase_type[i].size && erase_mask & BIT(erase_type[i].idx)) + sorted_erase_mask |= BIT(i); + + return sorted_erase_mask; +} + +/** + * spi_nor_regions_sort_erase_types() - sort erase types in each region + * @map: the erase map of the SPI NOR + * + * Function assumes that the erase types defined in the erase map are already + * sorted in ascending order, with the smallest erase type size being the first + * member in the erase_type array. It replicates the sort done for the map's + * erase types. Each region's erase bitmask will indicate which erase types are + * supported from the sorted erase types defined in the erase map. + * Sort the all region's erase type at init in order to speed up the process of + * finding the best erase command at runtime. + */ +static void spi_nor_regions_sort_erase_types(struct spi_nor_erase_map *map) +{ + struct spi_nor_erase_region *region = map->regions; + u8 region_erase_mask, sorted_erase_mask; + + while (region) { + region_erase_mask = region->offset & SNOR_ERASE_TYPE_MASK; + + sorted_erase_mask = spi_nor_sort_erase_mask(map, + region_erase_mask); + + /* Overwrite erase mask. */ + region->offset = (region->offset & ~SNOR_ERASE_TYPE_MASK) | + sorted_erase_mask; + + region = spi_nor_region_next(region); + } +} + +/** + * spi_nor_init_uniform_erase_map() - Initialize uniform erase map + * @map: the erase map of the SPI NOR + * @erase_mask: bitmask encoding erase types that can erase the entire + * flash memory + * @flash_size: the spi nor flash memory size + */ +static void spi_nor_init_uniform_erase_map(struct spi_nor_erase_map *map, + u8 erase_mask, u64 flash_size) +{ + /* Offset 0 with erase_mask and SNOR_LAST_REGION bit set */ + map->uniform_region.offset = (erase_mask & SNOR_ERASE_TYPE_MASK) | + SNOR_LAST_REGION; + map->uniform_region.size = flash_size; + map->regions = &map->uniform_region; + map->uniform_erase_type = erase_mask; +} + +static int +spi_nor_post_bfpt_fixups(struct spi_nor *nor, + const struct sfdp_parameter_header *bfpt_header, + const struct sfdp_bfpt *bfpt, + struct spi_nor_flash_parameter *params) +{ + if (nor->info->fixups && nor->info->fixups->post_bfpt) + return nor->info->fixups->post_bfpt(nor, bfpt_header, bfpt, + params); + + return 0; +} + +/** + * spi_nor_parse_bfpt() - read and parse the Basic Flash Parameter Table. + * @nor: pointer to a 'struct spi_nor' + * @bfpt_header: pointer to the 'struct sfdp_parameter_header' describing + * the Basic Flash Parameter Table length and version + * @params: pointer to the 'struct spi_nor_flash_parameter' to be + * filled + * + * The Basic Flash Parameter Table is the main and only mandatory table as + * defined by the SFDP (JESD216) specification. + * It provides us with the total size (memory density) of the data array and + * the number of address bytes for Fast Read, Page Program and Sector Erase + * commands. + * For Fast READ commands, it also gives the number of mode clock cycles and + * wait states (regrouped in the number of dummy clock cycles) for each + * supported instruction op code. + * For Page Program, the page size is now available since JESD216 rev A, however + * the supported instruction op codes are still not provided. + * For Sector Erase commands, this table stores the supported instruction op + * codes and the associated sector sizes. + * Finally, the Quad Enable Requirements (QER) are also available since JESD216 + * rev A. The QER bits encode the manufacturer dependent procedure to be + * executed to set the Quad Enable (QE) bit in some internal register of the + * Quad SPI memory. Indeed the QE bit, when it exists, must be set before + * sending any Quad SPI command to the memory. Actually, setting the QE bit + * tells the memory to reassign its WP# and HOLD#/RESET# pins to functions IO2 + * and IO3 hence enabling 4 (Quad) I/O lines. + * + * Return: 0 on success, -errno otherwise. + */ +static int spi_nor_parse_bfpt(struct spi_nor *nor, + const struct sfdp_parameter_header *bfpt_header, + struct spi_nor_flash_parameter *params) +{ + struct spi_nor_erase_map *map = ¶ms->erase_map; + struct spi_nor_erase_type *erase_type = map->erase_type; + struct sfdp_bfpt bfpt; + size_t len; + int i, cmd, err; + u32 addr; + u16 half; + u8 erase_mask; + + /* JESD216 Basic Flash Parameter Table length is at least 9 DWORDs. */ + if (bfpt_header->length < BFPT_DWORD_MAX_JESD216) + return -EINVAL; + + /* Read the Basic Flash Parameter Table. */ + len = min_t(size_t, sizeof(bfpt), + bfpt_header->length * sizeof(u32)); + addr = SFDP_PARAM_HEADER_PTP(bfpt_header); + memset(&bfpt, 0, sizeof(bfpt)); + err = spi_nor_read_sfdp_dma_unsafe(nor, addr, len, &bfpt); + if (err < 0) + return err; + + /* Fix endianness of the BFPT DWORDs. */ + for (i = 0; i < BFPT_DWORD_MAX; i++) + bfpt.dwords[i] = le32_to_cpu(bfpt.dwords[i]); + + /* Number of address bytes. */ + switch (bfpt.dwords[BFPT_DWORD(1)] & BFPT_DWORD1_ADDRESS_BYTES_MASK) { + case BFPT_DWORD1_ADDRESS_BYTES_3_ONLY: + nor->addr_width = 3; + break; + + case BFPT_DWORD1_ADDRESS_BYTES_4_ONLY: + nor->addr_width = 4; + break; + + default: + break; + } + + /* Flash Memory Density (in bits). */ + params->size = bfpt.dwords[BFPT_DWORD(2)]; + if (params->size & BIT(31)) { + params->size &= ~BIT(31); + + /* + * Prevent overflows on params->size. Anyway, a NOR of 2^64 + * bits is unlikely to exist so this error probably means + * the BFPT we are reading is corrupted/wrong. + */ + if (params->size > 63) + return -EINVAL; + + params->size = 1ULL << params->size; + } else { + params->size++; + } + params->size >>= 3; /* Convert to bytes. */ + + /* Fast Read settings. */ + for (i = 0; i < ARRAY_SIZE(sfdp_bfpt_reads); i++) { + const struct sfdp_bfpt_read *rd = &sfdp_bfpt_reads[i]; + struct spi_nor_read_command *read; + + if (!(bfpt.dwords[rd->supported_dword] & rd->supported_bit)) { + params->hwcaps.mask &= ~rd->hwcaps; + continue; + } + + params->hwcaps.mask |= rd->hwcaps; + cmd = spi_nor_hwcaps_read2cmd(rd->hwcaps); + read = ¶ms->reads[cmd]; + half = bfpt.dwords[rd->settings_dword] >> rd->settings_shift; + spi_nor_set_read_settings_from_bfpt(read, half, rd->proto); + } + + /* + * Sector Erase settings. Reinitialize the uniform erase map using the + * Erase Types defined in the bfpt table. + */ + erase_mask = 0; + memset(¶ms->erase_map, 0, sizeof(params->erase_map)); + for (i = 0; i < ARRAY_SIZE(sfdp_bfpt_erases); i++) { + const struct sfdp_bfpt_erase *er = &sfdp_bfpt_erases[i]; + u32 erasesize; + u8 opcode; + + half = bfpt.dwords[er->dword] >> er->shift; + erasesize = half & 0xff; + + /* erasesize == 0 means this Erase Type is not supported. */ + if (!erasesize) + continue; + + erasesize = 1U << erasesize; + opcode = (half >> 8) & 0xff; + erase_mask |= BIT(i); + spi_nor_set_erase_settings_from_bfpt(&erase_type[i], erasesize, + opcode, i); + } + spi_nor_init_uniform_erase_map(map, erase_mask, params->size); + /* + * Sort all the map's Erase Types in ascending order with the smallest + * erase size being the first member in the erase_type array. + */ + sort(erase_type, SNOR_ERASE_TYPE_MAX, sizeof(erase_type[0]), + spi_nor_map_cmp_erase_type, NULL); + /* + * Sort the erase types in the uniform region in order to update the + * uniform_erase_type bitmask. The bitmask will be used later on when + * selecting the uniform erase. + */ + spi_nor_regions_sort_erase_types(map); + map->uniform_erase_type = map->uniform_region.offset & + SNOR_ERASE_TYPE_MASK; + + /* Stop here if not JESD216 rev A or later. */ + if (bfpt_header->length < BFPT_DWORD_MAX) + return spi_nor_post_bfpt_fixups(nor, bfpt_header, &bfpt, + params); + + /* Page size: this field specifies 'N' so the page size = 2^N bytes. */ + params->page_size = bfpt.dwords[BFPT_DWORD(11)]; + params->page_size &= BFPT_DWORD11_PAGE_SIZE_MASK; + params->page_size >>= BFPT_DWORD11_PAGE_SIZE_SHIFT; + params->page_size = 1U << params->page_size; + + /* Quad Enable Requirements. */ + switch (bfpt.dwords[BFPT_DWORD(15)] & BFPT_DWORD15_QER_MASK) { + case BFPT_DWORD15_QER_NONE: + params->quad_enable = NULL; + break; + + case BFPT_DWORD15_QER_SR2_BIT1_BUGGY: + case BFPT_DWORD15_QER_SR2_BIT1_NO_RD: + params->quad_enable = spansion_no_read_cr_quad_enable; + break; + + case BFPT_DWORD15_QER_SR1_BIT6: + params->quad_enable = macronix_quad_enable; + break; + + case BFPT_DWORD15_QER_SR2_BIT7: + params->quad_enable = sr2_bit7_quad_enable; + break; + + case BFPT_DWORD15_QER_SR2_BIT1: + params->quad_enable = spansion_read_cr_quad_enable; + break; + + default: + return -EINVAL; + } + + return spi_nor_post_bfpt_fixups(nor, bfpt_header, &bfpt, params); +} + +#define SMPT_CMD_ADDRESS_LEN_MASK GENMASK(23, 22) +#define SMPT_CMD_ADDRESS_LEN_0 (0x0UL << 22) +#define SMPT_CMD_ADDRESS_LEN_3 (0x1UL << 22) +#define SMPT_CMD_ADDRESS_LEN_4 (0x2UL << 22) +#define SMPT_CMD_ADDRESS_LEN_USE_CURRENT (0x3UL << 22) + +#define SMPT_CMD_READ_DUMMY_MASK GENMASK(19, 16) +#define SMPT_CMD_READ_DUMMY_SHIFT 16 +#define SMPT_CMD_READ_DUMMY(_cmd) \ + (((_cmd) & SMPT_CMD_READ_DUMMY_MASK) >> SMPT_CMD_READ_DUMMY_SHIFT) +#define SMPT_CMD_READ_DUMMY_IS_VARIABLE 0xfUL + +#define SMPT_CMD_READ_DATA_MASK GENMASK(31, 24) +#define SMPT_CMD_READ_DATA_SHIFT 24 +#define SMPT_CMD_READ_DATA(_cmd) \ + (((_cmd) & SMPT_CMD_READ_DATA_MASK) >> SMPT_CMD_READ_DATA_SHIFT) + +#define SMPT_CMD_OPCODE_MASK GENMASK(15, 8) +#define SMPT_CMD_OPCODE_SHIFT 8 +#define SMPT_CMD_OPCODE(_cmd) \ + (((_cmd) & SMPT_CMD_OPCODE_MASK) >> SMPT_CMD_OPCODE_SHIFT) + +#define SMPT_MAP_REGION_COUNT_MASK GENMASK(23, 16) +#define SMPT_MAP_REGION_COUNT_SHIFT 16 +#define SMPT_MAP_REGION_COUNT(_header) \ + ((((_header) & SMPT_MAP_REGION_COUNT_MASK) >> \ + SMPT_MAP_REGION_COUNT_SHIFT) + 1) + +#define SMPT_MAP_ID_MASK GENMASK(15, 8) +#define SMPT_MAP_ID_SHIFT 8 +#define SMPT_MAP_ID(_header) \ + (((_header) & SMPT_MAP_ID_MASK) >> SMPT_MAP_ID_SHIFT) + +#define SMPT_MAP_REGION_SIZE_MASK GENMASK(31, 8) +#define SMPT_MAP_REGION_SIZE_SHIFT 8 +#define SMPT_MAP_REGION_SIZE(_region) \ + (((((_region) & SMPT_MAP_REGION_SIZE_MASK) >> \ + SMPT_MAP_REGION_SIZE_SHIFT) + 1) * 256) + +#define SMPT_MAP_REGION_ERASE_TYPE_MASK GENMASK(3, 0) +#define SMPT_MAP_REGION_ERASE_TYPE(_region) \ + ((_region) & SMPT_MAP_REGION_ERASE_TYPE_MASK) + +#define SMPT_DESC_TYPE_MAP BIT(1) +#define SMPT_DESC_END BIT(0) + +/** + * spi_nor_smpt_addr_width() - return the address width used in the + * configuration detection command. + * @nor: pointer to a 'struct spi_nor' + * @settings: configuration detection command descriptor, dword1 + */ +static u8 spi_nor_smpt_addr_width(const struct spi_nor *nor, const u32 settings) +{ + switch (settings & SMPT_CMD_ADDRESS_LEN_MASK) { + case SMPT_CMD_ADDRESS_LEN_0: + return 0; + case SMPT_CMD_ADDRESS_LEN_3: + return 3; + case SMPT_CMD_ADDRESS_LEN_4: + return 4; + case SMPT_CMD_ADDRESS_LEN_USE_CURRENT: + /* fall through */ + default: + return nor->addr_width; + } +} + +/** + * spi_nor_smpt_read_dummy() - return the configuration detection command read + * latency, in clock cycles. + * @nor: pointer to a 'struct spi_nor' + * @settings: configuration detection command descriptor, dword1 + * + * Return: the number of dummy cycles for an SMPT read + */ +static u8 spi_nor_smpt_read_dummy(const struct spi_nor *nor, const u32 settings) +{ + u8 read_dummy = SMPT_CMD_READ_DUMMY(settings); + + if (read_dummy == SMPT_CMD_READ_DUMMY_IS_VARIABLE) + return nor->read_dummy; + return read_dummy; +} + +/** + * spi_nor_get_map_in_use() - get the configuration map in use + * @nor: pointer to a 'struct spi_nor' + * @smpt: pointer to the sector map parameter table + * @smpt_len: sector map parameter table length + * + * Return: pointer to the map in use, ERR_PTR(-errno) otherwise. + */ +static const u32 *spi_nor_get_map_in_use(struct spi_nor *nor, const u32 *smpt, + u8 smpt_len) +{ + const u32 *ret; + u8 *buf; + u32 addr; + int err; + u8 i; + u8 addr_width, read_opcode, read_dummy; + u8 read_data_mask, map_id; + + /* Use a kmalloc'ed bounce buffer to guarantee it is DMA-able. */ + buf = kmalloc(sizeof(*buf), GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + addr_width = nor->addr_width; + read_dummy = nor->read_dummy; + read_opcode = nor->read_opcode; + + map_id = 0; + /* Determine if there are any optional Detection Command Descriptors */ + for (i = 0; i < smpt_len; i += 2) { + if (smpt[i] & SMPT_DESC_TYPE_MAP) + break; + + read_data_mask = SMPT_CMD_READ_DATA(smpt[i]); + nor->addr_width = spi_nor_smpt_addr_width(nor, smpt[i]); + nor->read_dummy = spi_nor_smpt_read_dummy(nor, smpt[i]); + nor->read_opcode = SMPT_CMD_OPCODE(smpt[i]); + addr = smpt[i + 1]; + + err = spi_nor_read_raw(nor, addr, 1, buf); + if (err) { + ret = ERR_PTR(err); + goto out; + } + + /* + * Build an index value that is used to select the Sector Map + * Configuration that is currently in use. + */ + map_id = map_id << 1 | !!(*buf & read_data_mask); + } + + /* + * If command descriptors are provided, they always precede map + * descriptors in the table. There is no need to start the iteration + * over smpt array all over again. + * + * Find the matching configuration map. + */ + ret = ERR_PTR(-EINVAL); + while (i < smpt_len) { + if (SMPT_MAP_ID(smpt[i]) == map_id) { + ret = smpt + i; + break; + } + + /* + * If there are no more configuration map descriptors and no + * configuration ID matched the configuration identifier, the + * sector address map is unknown. + */ + if (smpt[i] & SMPT_DESC_END) + break; + + /* increment the table index to the next map */ + i += SMPT_MAP_REGION_COUNT(smpt[i]) + 1; + } + + /* fall through */ +out: + kfree(buf); + nor->addr_width = addr_width; + nor->read_dummy = read_dummy; + nor->read_opcode = read_opcode; + return ret; +} + +/** + * spi_nor_region_check_overlay() - set overlay bit when the region is overlaid + * @region: pointer to a structure that describes a SPI NOR erase region + * @erase: pointer to a structure that describes a SPI NOR erase type + * @erase_type: erase type bitmask + */ +static void +spi_nor_region_check_overlay(struct spi_nor_erase_region *region, + const struct spi_nor_erase_type *erase, + const u8 erase_type) +{ + int i; + + for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) { + if (!(erase_type & BIT(i))) + continue; + if (region->size & erase[i].size_mask) { + spi_nor_region_mark_overlay(region); + return; + } + } +} + +/** + * spi_nor_init_non_uniform_erase_map() - initialize the non-uniform erase map + * @nor: pointer to a 'struct spi_nor' + * @params: pointer to a duplicate 'struct spi_nor_flash_parameter' that is + * used for storing SFDP parsed data + * @smpt: pointer to the sector map parameter table + * + * Return: 0 on success, -errno otherwise. + */ +static int +spi_nor_init_non_uniform_erase_map(struct spi_nor *nor, + struct spi_nor_flash_parameter *params, + const u32 *smpt) +{ + struct spi_nor_erase_map *map = ¶ms->erase_map; + struct spi_nor_erase_type *erase = map->erase_type; + struct spi_nor_erase_region *region; + u64 offset; + u32 region_count; + int i, j; + u8 uniform_erase_type, save_uniform_erase_type; + u8 erase_type, regions_erase_type; + + region_count = SMPT_MAP_REGION_COUNT(*smpt); + /* + * The regions will be freed when the driver detaches from the + * device. + */ + region = devm_kcalloc(nor->dev, region_count, sizeof(*region), + GFP_KERNEL); + if (!region) + return -ENOMEM; + map->regions = region; + + uniform_erase_type = 0xff; + regions_erase_type = 0; + offset = 0; + /* Populate regions. */ + for (i = 0; i < region_count; i++) { + j = i + 1; /* index for the region dword */ + region[i].size = SMPT_MAP_REGION_SIZE(smpt[j]); + erase_type = SMPT_MAP_REGION_ERASE_TYPE(smpt[j]); + region[i].offset = offset | erase_type; + + spi_nor_region_check_overlay(®ion[i], erase, erase_type); + + /* + * Save the erase types that are supported in all regions and + * can erase the entire flash memory. + */ + uniform_erase_type &= erase_type; + + /* + * regions_erase_type mask will indicate all the erase types + * supported in this configuration map. + */ + regions_erase_type |= erase_type; + + offset = (region[i].offset & ~SNOR_ERASE_FLAGS_MASK) + + region[i].size; + } + + save_uniform_erase_type = map->uniform_erase_type; + map->uniform_erase_type = spi_nor_sort_erase_mask(map, + uniform_erase_type); + + if (!regions_erase_type) { + /* + * Roll back to the previous uniform_erase_type mask, SMPT is + * broken. + */ + map->uniform_erase_type = save_uniform_erase_type; + return -EINVAL; + } + + /* + * BFPT advertises all the erase types supported by all the possible + * map configurations. Mask out the erase types that are not supported + * by the current map configuration. + */ + for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) + if (!(regions_erase_type & BIT(erase[i].idx))) + spi_nor_set_erase_type(&erase[i], 0, 0xFF); + + spi_nor_region_mark_end(®ion[i - 1]); + + return 0; +} + +/** + * spi_nor_parse_smpt() - parse Sector Map Parameter Table + * @nor: pointer to a 'struct spi_nor' + * @smpt_header: sector map parameter table header + * @params: pointer to a duplicate 'struct spi_nor_flash_parameter' + * that is used for storing SFDP parsed data + * + * This table is optional, but when available, we parse it to identify the + * location and size of sectors within the main data array of the flash memory + * device and to identify which Erase Types are supported by each sector. + * + * Return: 0 on success, -errno otherwise. + */ +static int spi_nor_parse_smpt(struct spi_nor *nor, + const struct sfdp_parameter_header *smpt_header, + struct spi_nor_flash_parameter *params) +{ + const u32 *sector_map; + u32 *smpt; + size_t len; + u32 addr; + int i, ret; + + /* Read the Sector Map Parameter Table. */ + len = smpt_header->length * sizeof(*smpt); + smpt = kmalloc(len, GFP_KERNEL); + if (!smpt) + return -ENOMEM; + + addr = SFDP_PARAM_HEADER_PTP(smpt_header); + ret = spi_nor_read_sfdp(nor, addr, len, smpt); + if (ret) + goto out; + + /* Fix endianness of the SMPT DWORDs. */ + for (i = 0; i < smpt_header->length; i++) + smpt[i] = le32_to_cpu(smpt[i]); + + sector_map = spi_nor_get_map_in_use(nor, smpt, smpt_header->length); + if (IS_ERR(sector_map)) { + ret = PTR_ERR(sector_map); + goto out; + } + + ret = spi_nor_init_non_uniform_erase_map(nor, params, sector_map); + if (ret) + goto out; + + spi_nor_regions_sort_erase_types(¶ms->erase_map); + /* fall through */ +out: + kfree(smpt); + return ret; +} + +#define SFDP_4BAIT_DWORD_MAX 2 + +struct sfdp_4bait { + /* The hardware capability. */ + u32 hwcaps; + + /* + * The <supported_bit> bit in DWORD1 of the 4BAIT tells us whether + * the associated 4-byte address op code is supported. + */ + u32 supported_bit; +}; + +/** + * spi_nor_parse_4bait() - parse the 4-Byte Address Instruction Table + * @nor: pointer to a 'struct spi_nor'. + * @param_header: pointer to the 'struct sfdp_parameter_header' describing + * the 4-Byte Address Instruction Table length and version. + * @params: pointer to the 'struct spi_nor_flash_parameter' to be. + * + * Return: 0 on success, -errno otherwise. + */ +static int spi_nor_parse_4bait(struct spi_nor *nor, + const struct sfdp_parameter_header *param_header, + struct spi_nor_flash_parameter *params) +{ + static const struct sfdp_4bait reads[] = { + { SNOR_HWCAPS_READ, BIT(0) }, + { SNOR_HWCAPS_READ_FAST, BIT(1) }, + { SNOR_HWCAPS_READ_1_1_2, BIT(2) }, + { SNOR_HWCAPS_READ_1_2_2, BIT(3) }, + { SNOR_HWCAPS_READ_1_1_4, BIT(4) }, + { SNOR_HWCAPS_READ_1_4_4, BIT(5) }, + { SNOR_HWCAPS_READ_1_1_1_DTR, BIT(13) }, + { SNOR_HWCAPS_READ_1_2_2_DTR, BIT(14) }, + { SNOR_HWCAPS_READ_1_4_4_DTR, BIT(15) }, + }; + static const struct sfdp_4bait programs[] = { + { SNOR_HWCAPS_PP, BIT(6) }, + { SNOR_HWCAPS_PP_1_1_4, BIT(7) }, + { SNOR_HWCAPS_PP_1_4_4, BIT(8) }, + }; + static const struct sfdp_4bait erases[SNOR_ERASE_TYPE_MAX] = { + { 0u /* not used */, BIT(9) }, + { 0u /* not used */, BIT(10) }, + { 0u /* not used */, BIT(11) }, + { 0u /* not used */, BIT(12) }, + }; + struct spi_nor_pp_command *params_pp = params->page_programs; + struct spi_nor_erase_map *map = ¶ms->erase_map; + struct spi_nor_erase_type *erase_type = map->erase_type; + u32 *dwords; + size_t len; + u32 addr, discard_hwcaps, read_hwcaps, pp_hwcaps, erase_mask; + int i, ret; + + if (param_header->major != SFDP_JESD216_MAJOR || + param_header->length < SFDP_4BAIT_DWORD_MAX) + return -EINVAL; + + /* Read the 4-byte Address Instruction Table. */ + len = sizeof(*dwords) * SFDP_4BAIT_DWORD_MAX; + + /* Use a kmalloc'ed bounce buffer to guarantee it is DMA-able. */ + dwords = kmalloc(len, GFP_KERNEL); + if (!dwords) + return -ENOMEM; + + addr = SFDP_PARAM_HEADER_PTP(param_header); + ret = spi_nor_read_sfdp(nor, addr, len, dwords); + if (ret) + goto out; + + /* Fix endianness of the 4BAIT DWORDs. */ + for (i = 0; i < SFDP_4BAIT_DWORD_MAX; i++) + dwords[i] = le32_to_cpu(dwords[i]); + + /* + * Compute the subset of (Fast) Read commands for which the 4-byte + * version is supported. + */ + discard_hwcaps = 0; + read_hwcaps = 0; + for (i = 0; i < ARRAY_SIZE(reads); i++) { + const struct sfdp_4bait *read = &reads[i]; + + discard_hwcaps |= read->hwcaps; + if ((params->hwcaps.mask & read->hwcaps) && + (dwords[0] & read->supported_bit)) + read_hwcaps |= read->hwcaps; + } + + /* + * Compute the subset of Page Program commands for which the 4-byte + * version is supported. + */ + pp_hwcaps = 0; + for (i = 0; i < ARRAY_SIZE(programs); i++) { + const struct sfdp_4bait *program = &programs[i]; + + /* + * The 4 Byte Address Instruction (Optional) Table is the only + * SFDP table that indicates support for Page Program Commands. + * Bypass the params->hwcaps.mask and consider 4BAIT the biggest + * authority for specifying Page Program support. + */ + discard_hwcaps |= program->hwcaps; + if (dwords[0] & program->supported_bit) + pp_hwcaps |= program->hwcaps; + } + + /* + * Compute the subset of Sector Erase commands for which the 4-byte + * version is supported. + */ + erase_mask = 0; + for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) { + const struct sfdp_4bait *erase = &erases[i]; + + if (dwords[0] & erase->supported_bit) + erase_mask |= BIT(i); + } + + /* Replicate the sort done for the map's erase types in BFPT. */ + erase_mask = spi_nor_sort_erase_mask(map, erase_mask); + + /* + * We need at least one 4-byte op code per read, program and erase + * operation; the .read(), .write() and .erase() hooks share the + * nor->addr_width value. + */ + if (!read_hwcaps || !pp_hwcaps || !erase_mask) + goto out; + + /* + * Discard all operations from the 4-byte instruction set which are + * not supported by this memory. + */ + params->hwcaps.mask &= ~discard_hwcaps; + params->hwcaps.mask |= (read_hwcaps | pp_hwcaps); + + /* Use the 4-byte address instruction set. */ + for (i = 0; i < SNOR_CMD_READ_MAX; i++) { + struct spi_nor_read_command *read_cmd = ¶ms->reads[i]; + + read_cmd->opcode = spi_nor_convert_3to4_read(read_cmd->opcode); + } + + /* 4BAIT is the only SFDP table that indicates page program support. */ + if (pp_hwcaps & SNOR_HWCAPS_PP) + spi_nor_set_pp_settings(¶ms_pp[SNOR_CMD_PP], + SPINOR_OP_PP_4B, SNOR_PROTO_1_1_1); + if (pp_hwcaps & SNOR_HWCAPS_PP_1_1_4) + spi_nor_set_pp_settings(¶ms_pp[SNOR_CMD_PP_1_1_4], + SPINOR_OP_PP_1_1_4_4B, + SNOR_PROTO_1_1_4); + if (pp_hwcaps & SNOR_HWCAPS_PP_1_4_4) + spi_nor_set_pp_settings(¶ms_pp[SNOR_CMD_PP_1_4_4], + SPINOR_OP_PP_1_4_4_4B, + SNOR_PROTO_1_4_4); + + for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) { + if (erase_mask & BIT(i)) + erase_type[i].opcode = (dwords[1] >> + erase_type[i].idx * 8) & 0xFF; + else + spi_nor_set_erase_type(&erase_type[i], 0u, 0xFF); + } + + /* + * We set SNOR_F_HAS_4BAIT in order to skip spi_nor_set_4byte_opcodes() + * later because we already did the conversion to 4byte opcodes. Also, + * this latest function implements a legacy quirk for the erase size of + * Spansion memory. However this quirk is no longer needed with new + * SFDP compliant memories. + */ + nor->addr_width = 4; + nor->flags |= SNOR_F_4B_OPCODES | SNOR_F_HAS_4BAIT; + + /* fall through */ +out: + kfree(dwords); + return ret; +} + +/** + * spi_nor_parse_sfdp() - parse the Serial Flash Discoverable Parameters. + * @nor: pointer to a 'struct spi_nor' + * @params: pointer to the 'struct spi_nor_flash_parameter' to be + * filled + * + * The Serial Flash Discoverable Parameters are described by the JEDEC JESD216 + * specification. This is a standard which tends to supported by almost all + * (Q)SPI memory manufacturers. Those hard-coded tables allow us to learn at + * runtime the main parameters needed to perform basic SPI flash operations such + * as Fast Read, Page Program or Sector Erase commands. + * + * Return: 0 on success, -errno otherwise. + */ +static int spi_nor_parse_sfdp(struct spi_nor *nor, + struct spi_nor_flash_parameter *params) +{ + const struct sfdp_parameter_header *param_header, *bfpt_header; + struct sfdp_parameter_header *param_headers = NULL; + struct sfdp_header header; + struct device *dev = nor->dev; + size_t psize; + int i, err; + + /* Get the SFDP header. */ + err = spi_nor_read_sfdp_dma_unsafe(nor, 0, sizeof(header), &header); + if (err < 0) + return err; + + /* Check the SFDP header version. */ + if (le32_to_cpu(header.signature) != SFDP_SIGNATURE || + header.major != SFDP_JESD216_MAJOR) + return -EINVAL; + + /* + * Verify that the first and only mandatory parameter header is a + * Basic Flash Parameter Table header as specified in JESD216. + */ + bfpt_header = &header.bfpt_header; + if (SFDP_PARAM_HEADER_ID(bfpt_header) != SFDP_BFPT_ID || + bfpt_header->major != SFDP_JESD216_MAJOR) + return -EINVAL; + + /* + * Allocate memory then read all parameter headers with a single + * Read SFDP command. These parameter headers will actually be parsed + * twice: a first time to get the latest revision of the basic flash + * parameter table, then a second time to handle the supported optional + * tables. + * Hence we read the parameter headers once for all to reduce the + * processing time. Also we use kmalloc() instead of devm_kmalloc() + * because we don't need to keep these parameter headers: the allocated + * memory is always released with kfree() before exiting this function. + */ + if (header.nph) { + psize = header.nph * sizeof(*param_headers); + + param_headers = kmalloc(psize, GFP_KERNEL); + if (!param_headers) + return -ENOMEM; + + err = spi_nor_read_sfdp(nor, sizeof(header), + psize, param_headers); + if (err < 0) { + dev_err(dev, "failed to read SFDP parameter headers\n"); + goto exit; + } + } + + /* + * Check other parameter headers to get the latest revision of + * the basic flash parameter table. + */ + for (i = 0; i < header.nph; i++) { + param_header = ¶m_headers[i]; + + if (SFDP_PARAM_HEADER_ID(param_header) == SFDP_BFPT_ID && + param_header->major == SFDP_JESD216_MAJOR && + (param_header->minor > bfpt_header->minor || + (param_header->minor == bfpt_header->minor && + param_header->length > bfpt_header->length))) + bfpt_header = param_header; + } + + err = spi_nor_parse_bfpt(nor, bfpt_header, params); + if (err) + goto exit; + + /* Parse optional parameter tables. */ + for (i = 0; i < header.nph; i++) { + param_header = ¶m_headers[i]; + + switch (SFDP_PARAM_HEADER_ID(param_header)) { + case SFDP_SECTOR_MAP_ID: + err = spi_nor_parse_smpt(nor, param_header, params); + break; + + case SFDP_4BAIT_ID: + err = spi_nor_parse_4bait(nor, param_header, params); + break; + + default: + break; + } + + if (err) { + dev_warn(dev, "Failed to parse optional parameter table: %04x\n", + SFDP_PARAM_HEADER_ID(param_header)); + /* + * Let's not drop all information we extracted so far + * if optional table parsers fail. In case of failing, + * each optional parser is responsible to roll back to + * the previously known spi_nor data. + */ + err = 0; + } + } + +exit: + kfree(param_headers); + return err; +} + +static int spi_nor_select_read(struct spi_nor *nor, + u32 shared_hwcaps) +{ + int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_READ_MASK) - 1; + const struct spi_nor_read_command *read; + + if (best_match < 0) + return -EINVAL; + + cmd = spi_nor_hwcaps_read2cmd(BIT(best_match)); + if (cmd < 0) + return -EINVAL; + + read = &nor->params.reads[cmd]; + nor->read_opcode = read->opcode; + nor->read_proto = read->proto; + + /* + * In the spi-nor framework, we don't need to make the difference + * between mode clock cycles and wait state clock cycles. + * Indeed, the value of the mode clock cycles is used by a QSPI + * flash memory to know whether it should enter or leave its 0-4-4 + * (Continuous Read / XIP) mode. + * eXecution In Place is out of the scope of the mtd sub-system. + * Hence we choose to merge both mode and wait state clock cycles + * into the so called dummy clock cycles. + */ + nor->read_dummy = read->num_mode_clocks + read->num_wait_states; + return 0; +} + +static int spi_nor_select_pp(struct spi_nor *nor, + u32 shared_hwcaps) +{ + int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_PP_MASK) - 1; + const struct spi_nor_pp_command *pp; + + if (best_match < 0) + return -EINVAL; + + cmd = spi_nor_hwcaps_pp2cmd(BIT(best_match)); + if (cmd < 0) + return -EINVAL; + + pp = &nor->params.page_programs[cmd]; + nor->program_opcode = pp->opcode; + nor->write_proto = pp->proto; + return 0; +} + +/** + * spi_nor_select_uniform_erase() - select optimum uniform erase type + * @map: the erase map of the SPI NOR + * @wanted_size: the erase type size to search for. Contains the value of + * info->sector_size or of the "small sector" size in case + * CONFIG_MTD_SPI_NOR_USE_4K_SECTORS is defined. + * + * Once the optimum uniform sector erase command is found, disable all the + * other. + * + * Return: pointer to erase type on success, NULL otherwise. + */ +static const struct spi_nor_erase_type * +spi_nor_select_uniform_erase(struct spi_nor_erase_map *map, + const u32 wanted_size) +{ + const struct spi_nor_erase_type *tested_erase, *erase = NULL; + int i; + u8 uniform_erase_type = map->uniform_erase_type; + + for (i = SNOR_ERASE_TYPE_MAX - 1; i >= 0; i--) { + if (!(uniform_erase_type & BIT(i))) + continue; + + tested_erase = &map->erase_type[i]; + + /* + * If the current erase size is the one, stop here: + * we have found the right uniform Sector Erase command. + */ + if (tested_erase->size == wanted_size) { + erase = tested_erase; + break; + } + + /* + * Otherwise, the current erase size is still a valid canditate. + * Select the biggest valid candidate. + */ + if (!erase && tested_erase->size) + erase = tested_erase; + /* keep iterating to find the wanted_size */ + } + + if (!erase) + return NULL; + + /* Disable all other Sector Erase commands. */ + map->uniform_erase_type &= ~SNOR_ERASE_TYPE_MASK; + map->uniform_erase_type |= BIT(erase - map->erase_type); + return erase; +} + +static int spi_nor_select_erase(struct spi_nor *nor) +{ + struct spi_nor_erase_map *map = &nor->params.erase_map; + const struct spi_nor_erase_type *erase = NULL; + struct mtd_info *mtd = &nor->mtd; + u32 wanted_size = nor->info->sector_size; + int i; + + /* + * The previous implementation handling Sector Erase commands assumed + * that the SPI flash memory has an uniform layout then used only one + * of the supported erase sizes for all Sector Erase commands. + * So to be backward compatible, the new implementation also tries to + * manage the SPI flash memory as uniform with a single erase sector + * size, when possible. + */ +#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS + /* prefer "small sector" erase if possible */ + wanted_size = 4096u; +#endif + + if (spi_nor_has_uniform_erase(nor)) { + erase = spi_nor_select_uniform_erase(map, wanted_size); + if (!erase) + return -EINVAL; + nor->erase_opcode = erase->opcode; + mtd->erasesize = erase->size; + return 0; + } + + /* + * For non-uniform SPI flash memory, set mtd->erasesize to the + * maximum erase sector size. No need to set nor->erase_opcode. + */ + for (i = SNOR_ERASE_TYPE_MAX - 1; i >= 0; i--) { + if (map->erase_type[i].size) { + erase = &map->erase_type[i]; + break; + } + } + + if (!erase) + return -EINVAL; + + mtd->erasesize = erase->size; + return 0; +} + +static int spi_nor_default_setup(struct spi_nor *nor, + const struct spi_nor_hwcaps *hwcaps) +{ + struct spi_nor_flash_parameter *params = &nor->params; + u32 ignored_mask, shared_mask; + int err; + + /* + * Keep only the hardware capabilities supported by both the SPI + * controller and the SPI flash memory. + */ + shared_mask = hwcaps->mask & params->hwcaps.mask; + + if (nor->spimem) { + /* + * When called from spi_nor_probe(), all caps are set and we + * need to discard some of them based on what the SPI + * controller actually supports (using spi_mem_supports_op()). + */ + spi_nor_spimem_adjust_hwcaps(nor, &shared_mask); + } else { + /* + * SPI n-n-n protocols are not supported when the SPI + * controller directly implements the spi_nor interface. + * Yet another reason to switch to spi-mem. + */ + ignored_mask = SNOR_HWCAPS_X_X_X; + if (shared_mask & ignored_mask) { + dev_dbg(nor->dev, + "SPI n-n-n protocols are not supported.\n"); + shared_mask &= ~ignored_mask; + } + } + + /* Select the (Fast) Read command. */ + err = spi_nor_select_read(nor, shared_mask); + if (err) { + dev_err(nor->dev, + "can't select read settings supported by both the SPI controller and memory.\n"); + return err; + } + + /* Select the Page Program command. */ + err = spi_nor_select_pp(nor, shared_mask); + if (err) { + dev_err(nor->dev, + "can't select write settings supported by both the SPI controller and memory.\n"); + return err; + } + + /* Select the Sector Erase command. */ + err = spi_nor_select_erase(nor); + if (err) { + dev_err(nor->dev, + "can't select erase settings supported by both the SPI controller and memory.\n"); + return err; + } + + return 0; +} + +static int spi_nor_setup(struct spi_nor *nor, + const struct spi_nor_hwcaps *hwcaps) +{ + if (!nor->params.setup) + return 0; + + return nor->params.setup(nor, hwcaps); +} + +static void macronix_set_default_init(struct spi_nor *nor) +{ + nor->params.quad_enable = macronix_quad_enable; + nor->params.set_4byte = macronix_set_4byte; +} + +static void st_micron_set_default_init(struct spi_nor *nor) +{ + nor->flags |= SNOR_F_HAS_LOCK; + nor->params.quad_enable = NULL; + nor->params.set_4byte = st_micron_set_4byte; +} + +static void winbond_set_default_init(struct spi_nor *nor) +{ + nor->params.set_4byte = winbond_set_4byte; +} + +/** + * spi_nor_manufacturer_init_params() - Initialize the flash's parameters and + * settings based on MFR register and ->default_init() hook. + * @nor: pointer to a 'struct spi-nor'. + */ +static void spi_nor_manufacturer_init_params(struct spi_nor *nor) +{ + /* Init flash parameters based on MFR */ + switch (JEDEC_MFR(nor->info)) { + case SNOR_MFR_MACRONIX: + macronix_set_default_init(nor); + break; + + case SNOR_MFR_ST: + case SNOR_MFR_MICRON: + st_micron_set_default_init(nor); + break; + + case SNOR_MFR_WINBOND: + winbond_set_default_init(nor); + break; + + default: + break; + } + + if (nor->info->fixups && nor->info->fixups->default_init) + nor->info->fixups->default_init(nor); +} + +/** + * spi_nor_sfdp_init_params() - Initialize the flash's parameters and settings + * based on JESD216 SFDP standard. + * @nor: pointer to a 'struct spi-nor'. + * + * The method has a roll-back mechanism: in case the SFDP parsing fails, the + * legacy flash parameters and settings will be restored. + */ +static void spi_nor_sfdp_init_params(struct spi_nor *nor) +{ + struct spi_nor_flash_parameter sfdp_params; + + memcpy(&sfdp_params, &nor->params, sizeof(sfdp_params)); + + if (spi_nor_parse_sfdp(nor, &sfdp_params)) { + nor->addr_width = 0; + nor->flags &= ~SNOR_F_4B_OPCODES; + } else { + memcpy(&nor->params, &sfdp_params, sizeof(nor->params)); + } +} + +/** + * spi_nor_info_init_params() - Initialize the flash's parameters and settings + * based on nor->info data. + * @nor: pointer to a 'struct spi-nor'. + */ +static void spi_nor_info_init_params(struct spi_nor *nor) +{ + struct spi_nor_flash_parameter *params = &nor->params; + struct spi_nor_erase_map *map = ¶ms->erase_map; + const struct flash_info *info = nor->info; + struct device_node *np = spi_nor_get_flash_node(nor); + u8 i, erase_mask; + + /* Initialize legacy flash parameters and settings. */ + params->quad_enable = spansion_quad_enable; + params->set_4byte = spansion_set_4byte; + params->setup = spi_nor_default_setup; + + /* Set SPI NOR sizes. */ + params->size = (u64)info->sector_size * info->n_sectors; + params->page_size = info->page_size; + + if (!(info->flags & SPI_NOR_NO_FR)) { + /* Default to Fast Read for DT and non-DT platform devices. */ + params->hwcaps.mask |= SNOR_HWCAPS_READ_FAST; + + /* Mask out Fast Read if not requested at DT instantiation. */ + if (np && !of_property_read_bool(np, "m25p,fast-read")) + params->hwcaps.mask &= ~SNOR_HWCAPS_READ_FAST; + } + + /* (Fast) Read settings. */ + params->hwcaps.mask |= SNOR_HWCAPS_READ; + spi_nor_set_read_settings(¶ms->reads[SNOR_CMD_READ], + 0, 0, SPINOR_OP_READ, + SNOR_PROTO_1_1_1); + + if (params->hwcaps.mask & SNOR_HWCAPS_READ_FAST) + spi_nor_set_read_settings(¶ms->reads[SNOR_CMD_READ_FAST], + 0, 8, SPINOR_OP_READ_FAST, + SNOR_PROTO_1_1_1); + + if (info->flags & SPI_NOR_DUAL_READ) { + params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2; + spi_nor_set_read_settings(¶ms->reads[SNOR_CMD_READ_1_1_2], + 0, 8, SPINOR_OP_READ_1_1_2, + SNOR_PROTO_1_1_2); + } + + if (info->flags & SPI_NOR_QUAD_READ) { + params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4; + spi_nor_set_read_settings(¶ms->reads[SNOR_CMD_READ_1_1_4], + 0, 8, SPINOR_OP_READ_1_1_4, + SNOR_PROTO_1_1_4); + } + + if (info->flags & SPI_NOR_OCTAL_READ) { + params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_8; + spi_nor_set_read_settings(¶ms->reads[SNOR_CMD_READ_1_1_8], + 0, 8, SPINOR_OP_READ_1_1_8, + SNOR_PROTO_1_1_8); + } + + /* Page Program settings. */ + params->hwcaps.mask |= SNOR_HWCAPS_PP; + spi_nor_set_pp_settings(¶ms->page_programs[SNOR_CMD_PP], + SPINOR_OP_PP, SNOR_PROTO_1_1_1); + + /* + * Sector Erase settings. Sort Erase Types in ascending order, with the + * smallest erase size starting at BIT(0). + */ + erase_mask = 0; + i = 0; + if (info->flags & SECT_4K_PMC) { + erase_mask |= BIT(i); + spi_nor_set_erase_type(&map->erase_type[i], 4096u, + SPINOR_OP_BE_4K_PMC); + i++; + } else if (info->flags & SECT_4K) { + erase_mask |= BIT(i); + spi_nor_set_erase_type(&map->erase_type[i], 4096u, + SPINOR_OP_BE_4K); + i++; + } + erase_mask |= BIT(i); + spi_nor_set_erase_type(&map->erase_type[i], info->sector_size, + SPINOR_OP_SE); + spi_nor_init_uniform_erase_map(map, erase_mask, params->size); +} + +static void spansion_post_sfdp_fixups(struct spi_nor *nor) +{ + struct mtd_info *mtd = &nor->mtd; + + if (mtd->size <= SZ_16M) + return; + + nor->flags |= SNOR_F_4B_OPCODES; + /* No small sector erase for 4-byte command set */ + nor->erase_opcode = SPINOR_OP_SE; + nor->mtd.erasesize = nor->info->sector_size; +} + +static void s3an_post_sfdp_fixups(struct spi_nor *nor) +{ + nor->params.setup = s3an_nor_setup; +} + +/** + * spi_nor_post_sfdp_fixups() - Updates the flash's parameters and settings + * after SFDP has been parsed (is also called for SPI NORs that do not + * support RDSFDP). + * @nor: pointer to a 'struct spi_nor' + * + * Typically used to tweak various parameters that could not be extracted by + * other means (i.e. when information provided by the SFDP/flash_info tables + * are incomplete or wrong). + */ +static void spi_nor_post_sfdp_fixups(struct spi_nor *nor) +{ + switch (JEDEC_MFR(nor->info)) { + case SNOR_MFR_SPANSION: + spansion_post_sfdp_fixups(nor); + break; + + default: + break; + } + + if (nor->info->flags & SPI_S3AN) + s3an_post_sfdp_fixups(nor); + + if (nor->info->fixups && nor->info->fixups->post_sfdp) + nor->info->fixups->post_sfdp(nor); +} + +/** + * spi_nor_late_init_params() - Late initialization of default flash parameters. + * @nor: pointer to a 'struct spi_nor' + * + * Used to set default flash parameters and settings when the ->default_init() + * hook or the SFDP parser let voids. + */ +static void spi_nor_late_init_params(struct spi_nor *nor) +{ + /* + * NOR protection support. When locking_ops are not provided, we pick + * the default ones. + */ + if (nor->flags & SNOR_F_HAS_LOCK && !nor->params.locking_ops) + nor->params.locking_ops = &stm_locking_ops; +} + +/** + * spi_nor_init_params() - Initialize the flash's parameters and settings. + * @nor: pointer to a 'struct spi-nor'. + * + * The flash parameters and settings are initialized based on a sequence of + * calls that are ordered by priority: + * + * 1/ Default flash parameters initialization. The initializations are done + * based on nor->info data: + * spi_nor_info_init_params() + * + * which can be overwritten by: + * 2/ Manufacturer flash parameters initialization. The initializations are + * done based on MFR register, or when the decisions can not be done solely + * based on MFR, by using specific flash_info tweeks, ->default_init(): + * spi_nor_manufacturer_init_params() + * + * which can be overwritten by: + * 3/ SFDP flash parameters initialization. JESD216 SFDP is a standard and + * should be more accurate that the above. + * spi_nor_sfdp_init_params() + * + * Please note that there is a ->post_bfpt() fixup hook that can overwrite + * the flash parameters and settings immediately after parsing the Basic + * Flash Parameter Table. + * + * which can be overwritten by: + * 4/ Post SFDP flash parameters initialization. Used to tweak various + * parameters that could not be extracted by other means (i.e. when + * information provided by the SFDP/flash_info tables are incomplete or + * wrong). + * spi_nor_post_sfdp_fixups() + * + * 5/ Late default flash parameters initialization, used when the + * ->default_init() hook or the SFDP parser do not set specific params. + * spi_nor_late_init_params() + */ +static void spi_nor_init_params(struct spi_nor *nor) +{ + spi_nor_info_init_params(nor); + + spi_nor_manufacturer_init_params(nor); + + if ((nor->info->flags & (SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)) && + !(nor->info->flags & SPI_NOR_SKIP_SFDP)) + spi_nor_sfdp_init_params(nor); + + spi_nor_post_sfdp_fixups(nor); + + spi_nor_late_init_params(nor); +} + +/** + * spi_nor_quad_enable() - enable Quad I/O if needed. + * @nor: pointer to a 'struct spi_nor' + * + * Return: 0 on success, -errno otherwise. + */ +static int spi_nor_quad_enable(struct spi_nor *nor) +{ + if (!nor->params.quad_enable) + return 0; + + if (!(spi_nor_get_protocol_width(nor->read_proto) == 4 || + spi_nor_get_protocol_width(nor->write_proto) == 4)) + return 0; + + return nor->params.quad_enable(nor); +} + +static int spi_nor_init(struct spi_nor *nor) +{ + int err; + + if (nor->clear_sr_bp) { + if (nor->params.quad_enable == spansion_quad_enable) + nor->clear_sr_bp = spi_nor_spansion_clear_sr_bp; + + err = nor->clear_sr_bp(nor); + if (err) { + dev_err(nor->dev, + "fail to clear block protection bits\n"); + return err; + } + } + + err = spi_nor_quad_enable(nor); + if (err) { + dev_err(nor->dev, "quad mode not supported\n"); + return err; + } + + if (nor->addr_width == 4 && !(nor->flags & SNOR_F_4B_OPCODES)) { + /* + * If the RESET# pin isn't hooked up properly, or the system + * otherwise doesn't perform a reset command in the boot + * sequence, it's impossible to 100% protect against unexpected + * reboots (e.g., crashes). Warn the user (or hopefully, system + * designer) that this is bad. + */ + WARN_ONCE(nor->flags & SNOR_F_BROKEN_RESET, + "enabling reset hack; may not recover from unexpected reboots\n"); + nor->params.set_4byte(nor, true); + } + + return 0; +} + +/* mtd resume handler */ +static void spi_nor_resume(struct mtd_info *mtd) +{ + struct spi_nor *nor = mtd_to_spi_nor(mtd); + struct device *dev = nor->dev; + int ret; + + /* re-initialize the nor chip */ + ret = spi_nor_init(nor); + if (ret) + dev_err(dev, "resume() failed\n"); +} + +void spi_nor_restore(struct spi_nor *nor) +{ + /* restore the addressing mode */ + if (nor->addr_width == 4 && !(nor->flags & SNOR_F_4B_OPCODES) && + nor->flags & SNOR_F_BROKEN_RESET) + nor->params.set_4byte(nor, false); +} +EXPORT_SYMBOL_GPL(spi_nor_restore); + +static const struct flash_info *spi_nor_match_id(const char *name) +{ + const struct flash_info *id = spi_nor_ids; + + while (id->name) { + if (!strcmp(name, id->name)) + return id; + id++; + } + return NULL; +} + +static int spi_nor_set_addr_width(struct spi_nor *nor) +{ + if (nor->addr_width) { + /* already configured from SFDP */ + } else if (nor->info->addr_width) { + nor->addr_width = nor->info->addr_width; + } else if (nor->mtd.size > 0x1000000) { + /* enable 4-byte addressing if the device exceeds 16MiB */ + nor->addr_width = 4; + } else { + nor->addr_width = 3; + } + + if (nor->addr_width > SPI_NOR_MAX_ADDR_WIDTH) { + dev_err(nor->dev, "address width is too large: %u\n", + nor->addr_width); + return -EINVAL; + } + + /* Set 4byte opcodes when possible. */ + if (nor->addr_width == 4 && nor->flags & SNOR_F_4B_OPCODES && + !(nor->flags & SNOR_F_HAS_4BAIT)) + spi_nor_set_4byte_opcodes(nor); + + return 0; +} + +static void spi_nor_debugfs_init(struct spi_nor *nor, + const struct flash_info *info) +{ + struct mtd_info *mtd = &nor->mtd; + + mtd->dbg.partname = info->name; + mtd->dbg.partid = devm_kasprintf(nor->dev, GFP_KERNEL, "spi-nor:%*phN", + info->id_len, info->id); +} + +static const struct flash_info *spi_nor_get_flash_info(struct spi_nor *nor, + const char *name) +{ + const struct flash_info *info = NULL; + + if (name) + info = spi_nor_match_id(name); + /* Try to auto-detect if chip name wasn't specified or not found */ + if (!info) + info = spi_nor_read_id(nor); + if (IS_ERR_OR_NULL(info)) + return ERR_PTR(-ENOENT); + + /* + * If caller has specified name of flash model that can normally be + * detected using JEDEC, let's verify it. + */ + if (name && info->id_len) { + const struct flash_info *jinfo; + + jinfo = spi_nor_read_id(nor); + if (IS_ERR(jinfo)) { + return jinfo; + } else if (jinfo != info) { + /* + * JEDEC knows better, so overwrite platform ID. We + * can't trust partitions any longer, but we'll let + * mtd apply them anyway, since some partitions may be + * marked read-only, and we don't want to lose that + * information, even if it's not 100% accurate. + */ + dev_warn(nor->dev, "found %s, expected %s\n", + jinfo->name, info->name); + info = jinfo; + } + } + + return info; +} + +int spi_nor_scan(struct spi_nor *nor, const char *name, + const struct spi_nor_hwcaps *hwcaps) +{ + const struct flash_info *info; + struct device *dev = nor->dev; + struct mtd_info *mtd = &nor->mtd; + struct device_node *np = spi_nor_get_flash_node(nor); + struct spi_nor_flash_parameter *params = &nor->params; + int ret; + int i; + + ret = spi_nor_check(nor); + if (ret) + return ret; + + /* Reset SPI protocol for all commands. */ + nor->reg_proto = SNOR_PROTO_1_1_1; + nor->read_proto = SNOR_PROTO_1_1_1; + nor->write_proto = SNOR_PROTO_1_1_1; + + /* + * We need the bounce buffer early to read/write registers when going + * through the spi-mem layer (buffers have to be DMA-able). + * For spi-mem drivers, we'll reallocate a new buffer if + * nor->page_size turns out to be greater than PAGE_SIZE (which + * shouldn't happen before long since NOR pages are usually less + * than 1KB) after spi_nor_scan() returns. + */ + nor->bouncebuf_size = PAGE_SIZE; + nor->bouncebuf = devm_kmalloc(dev, nor->bouncebuf_size, + GFP_KERNEL); + if (!nor->bouncebuf) + return -ENOMEM; + + info = spi_nor_get_flash_info(nor, name); + if (IS_ERR(info)) + return PTR_ERR(info); + + nor->info = info; + + spi_nor_debugfs_init(nor, info); + + mutex_init(&nor->lock); + + /* + * Make sure the XSR_RDY flag is set before calling + * spi_nor_wait_till_ready(). Xilinx S3AN share MFR + * with Atmel spi-nor + */ + if (info->flags & SPI_NOR_XSR_RDY) + nor->flags |= SNOR_F_READY_XSR_RDY; + + if (info->flags & SPI_NOR_HAS_LOCK) + nor->flags |= SNOR_F_HAS_LOCK; + + /* + * Atmel, SST, Intel/Numonyx, and others serial NOR tend to power up + * with the software protection bits set. + */ + if (JEDEC_MFR(nor->info) == SNOR_MFR_ATMEL || + JEDEC_MFR(nor->info) == SNOR_MFR_INTEL || + JEDEC_MFR(nor->info) == SNOR_MFR_SST || + nor->info->flags & SPI_NOR_HAS_LOCK) + nor->clear_sr_bp = spi_nor_clear_sr_bp; + + /* Init flash parameters based on flash_info struct and SFDP */ + spi_nor_init_params(nor); + + if (!mtd->name) + mtd->name = dev_name(dev); + mtd->priv = nor; + mtd->type = MTD_NORFLASH; + mtd->writesize = 1; + mtd->flags = MTD_CAP_NORFLASH; + mtd->size = params->size; + mtd->_erase = spi_nor_erase; + mtd->_read = spi_nor_read; + mtd->_resume = spi_nor_resume; + + if (nor->params.locking_ops) { + mtd->_lock = spi_nor_lock; + mtd->_unlock = spi_nor_unlock; + mtd->_is_locked = spi_nor_is_locked; + } + + /* sst nor chips use AAI word program */ + if (info->flags & SST_WRITE) + mtd->_write = sst_write; + else + mtd->_write = spi_nor_write; + + if (info->flags & USE_FSR) + nor->flags |= SNOR_F_USE_FSR; + if (info->flags & SPI_NOR_HAS_TB) + nor->flags |= SNOR_F_HAS_SR_TB; + if (info->flags & NO_CHIP_ERASE) + nor->flags |= SNOR_F_NO_OP_CHIP_ERASE; + if (info->flags & USE_CLSR) + nor->flags |= SNOR_F_USE_CLSR; + + if (info->flags & SPI_NOR_NO_ERASE) + mtd->flags |= MTD_NO_ERASE; + + mtd->dev.parent = dev; + nor->page_size = params->page_size; + mtd->writebufsize = nor->page_size; + + if (of_property_read_bool(np, "broken-flash-reset")) + nor->flags |= SNOR_F_BROKEN_RESET; + + /* + * Configure the SPI memory: + * - select op codes for (Fast) Read, Page Program and Sector Erase. + * - set the number of dummy cycles (mode cycles + wait states). + * - set the SPI protocols for register and memory accesses. + */ + ret = spi_nor_setup(nor, hwcaps); + if (ret) + return ret; + + if (info->flags & SPI_NOR_4B_OPCODES) + nor->flags |= SNOR_F_4B_OPCODES; + + ret = spi_nor_set_addr_width(nor); + if (ret) + return ret; + + /* Send all the required SPI flash commands to initialize device */ + ret = spi_nor_init(nor); + if (ret) + return ret; + + dev_info(dev, "%s (%lld Kbytes)\n", info->name, + (long long)mtd->size >> 10); + + dev_dbg(dev, + "mtd .name = %s, .size = 0x%llx (%lldMiB), " + ".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n", + mtd->name, (long long)mtd->size, (long long)(mtd->size >> 20), + mtd->erasesize, mtd->erasesize / 1024, mtd->numeraseregions); + + if (mtd->numeraseregions) + for (i = 0; i < mtd->numeraseregions; i++) + dev_dbg(dev, + "mtd.eraseregions[%d] = { .offset = 0x%llx, " + ".erasesize = 0x%.8x (%uKiB), " + ".numblocks = %d }\n", + i, (long long)mtd->eraseregions[i].offset, + mtd->eraseregions[i].erasesize, + mtd->eraseregions[i].erasesize / 1024, + mtd->eraseregions[i].numblocks); + return 0; +} +EXPORT_SYMBOL_GPL(spi_nor_scan); + +static int spi_nor_probe(struct spi_mem *spimem) +{ + struct spi_device *spi = spimem->spi; + struct flash_platform_data *data = dev_get_platdata(&spi->dev); + struct spi_nor *nor; + /* + * Enable all caps by default. The core will mask them after + * checking what's really supported using spi_mem_supports_op(). + */ + const struct spi_nor_hwcaps hwcaps = { .mask = SNOR_HWCAPS_ALL }; + char *flash_name; + int ret; + + nor = devm_kzalloc(&spi->dev, sizeof(*nor), GFP_KERNEL); + if (!nor) + return -ENOMEM; + + nor->spimem = spimem; + nor->dev = &spi->dev; + spi_nor_set_flash_node(nor, spi->dev.of_node); + + spi_mem_set_drvdata(spimem, nor); + + if (data && data->name) + nor->mtd.name = data->name; + + if (!nor->mtd.name) + nor->mtd.name = spi_mem_get_name(spimem); + + /* + * For some (historical?) reason many platforms provide two different + * names in flash_platform_data: "name" and "type". Quite often name is + * set to "m25p80" and then "type" provides a real chip name. + * If that's the case, respect "type" and ignore a "name". + */ + if (data && data->type) + flash_name = data->type; + else if (!strcmp(spi->modalias, "spi-nor")) + flash_name = NULL; /* auto-detect */ + else + flash_name = spi->modalias; + + ret = spi_nor_scan(nor, flash_name, &hwcaps); + if (ret) + return ret; + + /* + * None of the existing parts have > 512B pages, but let's play safe + * and add this logic so that if anyone ever adds support for such + * a NOR we don't end up with buffer overflows. + */ + if (nor->page_size > PAGE_SIZE) { + nor->bouncebuf_size = nor->page_size; + devm_kfree(nor->dev, nor->bouncebuf); + nor->bouncebuf = devm_kmalloc(nor->dev, + nor->bouncebuf_size, + GFP_KERNEL); + if (!nor->bouncebuf) + return -ENOMEM; + } + + return mtd_device_register(&nor->mtd, data ? data->parts : NULL, + data ? data->nr_parts : 0); +} + +static int spi_nor_remove(struct spi_mem *spimem) +{ + struct spi_nor *nor = spi_mem_get_drvdata(spimem); + + spi_nor_restore(nor); + + /* Clean up MTD stuff. */ + return mtd_device_unregister(&nor->mtd); +} + +static void spi_nor_shutdown(struct spi_mem *spimem) +{ + struct spi_nor *nor = spi_mem_get_drvdata(spimem); + + spi_nor_restore(nor); +} + +/* + * Do NOT add to this array without reading the following: + * + * Historically, many flash devices are bound to this driver by their name. But + * since most of these flash are compatible to some extent, and their + * differences can often be differentiated by the JEDEC read-ID command, we + * encourage new users to add support to the spi-nor library, and simply bind + * against a generic string here (e.g., "jedec,spi-nor"). + * + * Many flash names are kept here in this list (as well as in spi-nor.c) to + * keep them available as module aliases for existing platforms. + */ +static const struct spi_device_id spi_nor_dev_ids[] = { + /* + * Allow non-DT platform devices to bind to the "spi-nor" modalias, and + * hack around the fact that the SPI core does not provide uevent + * matching for .of_match_table + */ + {"spi-nor"}, + + /* + * Entries not used in DTs that should be safe to drop after replacing + * them with "spi-nor" in platform data. + */ + {"s25sl064a"}, {"w25x16"}, {"m25p10"}, {"m25px64"}, + + /* + * Entries that were used in DTs without "jedec,spi-nor" fallback and + * should be kept for backward compatibility. + */ + {"at25df321a"}, {"at25df641"}, {"at26df081a"}, + {"mx25l4005a"}, {"mx25l1606e"}, {"mx25l6405d"}, {"mx25l12805d"}, + {"mx25l25635e"},{"mx66l51235l"}, + {"n25q064"}, {"n25q128a11"}, {"n25q128a13"}, {"n25q512a"}, + {"s25fl256s1"}, {"s25fl512s"}, {"s25sl12801"}, {"s25fl008k"}, + {"s25fl064k"}, + {"sst25vf040b"},{"sst25vf016b"},{"sst25vf032b"},{"sst25wf040"}, + {"m25p40"}, {"m25p80"}, {"m25p16"}, {"m25p32"}, + {"m25p64"}, {"m25p128"}, + {"w25x80"}, {"w25x32"}, {"w25q32"}, {"w25q32dw"}, + {"w25q80bl"}, {"w25q128"}, {"w25q256"}, + + /* Flashes that can't be detected using JEDEC */ + {"m25p05-nonjedec"}, {"m25p10-nonjedec"}, {"m25p20-nonjedec"}, + {"m25p40-nonjedec"}, {"m25p80-nonjedec"}, {"m25p16-nonjedec"}, + {"m25p32-nonjedec"}, {"m25p64-nonjedec"}, {"m25p128-nonjedec"}, + + /* Everspin MRAMs (non-JEDEC) */ + { "mr25h128" }, /* 128 Kib, 40 MHz */ + { "mr25h256" }, /* 256 Kib, 40 MHz */ + { "mr25h10" }, /* 1 Mib, 40 MHz */ + { "mr25h40" }, /* 4 Mib, 40 MHz */ + + { }, +}; +MODULE_DEVICE_TABLE(spi, spi_nor_dev_ids); + +static const struct of_device_id spi_nor_of_table[] = { + /* + * Generic compatibility for SPI NOR that can be identified by the + * JEDEC READ ID opcode (0x9F). Use this, if possible. + */ + { .compatible = "jedec,spi-nor" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, spi_nor_of_table); + +/* + * REVISIT: many of these chips have deep power-down modes, which + * should clearly be entered on suspend() to minimize power use. + * And also when they're otherwise idle... + */ +static struct spi_mem_driver spi_nor_driver = { + .spidrv = { + .driver = { + .name = "spi-nor", + .of_match_table = spi_nor_of_table, + }, + .id_table = spi_nor_dev_ids, + }, + .probe = spi_nor_probe, + .remove = spi_nor_remove, + .shutdown = spi_nor_shutdown, +}; +module_spi_mem_driver(spi_nor_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Huang Shijie <shijie8@gmail.com>"); +MODULE_AUTHOR("Mike Lavender"); +MODULE_DESCRIPTION("framework for SPI NOR"); diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 5062af10f138..425a478576cc 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -84,6 +84,8 @@ static const struct flash_info winbond_parts[] = { SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { "w25q256jw", INFO(0xef6019, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "w25q512jv", INFO(0xef4020, 0, 64 * 1024, 1024, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) }, { "w25m512jv", INFO(0xef7119, 0, 64 * 1024, 1024, SECT_4K | SPI_NOR_QUAD_READ | SPI_NOR_DUAL_READ) }, }; diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig index de50e8b9e656..4624eb34c193 100644 --- a/drivers/net/ethernet/Kconfig +++ b/drivers/net/ethernet/Kconfig @@ -139,6 +139,7 @@ source "drivers/net/ethernet/neterion/Kconfig" source "drivers/net/ethernet/netronome/Kconfig" source "drivers/net/ethernet/ni/Kconfig" source "drivers/net/ethernet/8390/Kconfig" +source "drivers/net/ethernet/nuvoton/Kconfig" source "drivers/net/ethernet/nvidia/Kconfig" source "drivers/net/ethernet/nxp/Kconfig" source "drivers/net/ethernet/oki-semi/Kconfig" diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile index f8f38dcb5f8a..c4c3b715c101 100644 --- a/drivers/net/ethernet/Makefile +++ b/drivers/net/ethernet/Makefile @@ -63,6 +63,7 @@ obj-$(CONFIG_NET_VENDOR_NATSEMI) += natsemi/ obj-$(CONFIG_NET_VENDOR_NETERION) += neterion/ obj-$(CONFIG_NET_VENDOR_NETRONOME) += netronome/ obj-$(CONFIG_NET_VENDOR_NI) += ni/ +obj-$(CONFIG_NET_VENDOR_NUVOTON) += nuvoton/ obj-$(CONFIG_NET_VENDOR_NVIDIA) += nvidia/ obj-$(CONFIG_LPC_ENET) += nxp/ obj-$(CONFIG_NET_VENDOR_OKI) += oki-semi/ diff --git a/drivers/net/ethernet/nuvoton/Kconfig b/drivers/net/ethernet/nuvoton/Kconfig new file mode 100644 index 000000000000..e79af5f0ba3c --- /dev/null +++ b/drivers/net/ethernet/nuvoton/Kconfig @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Nuvoton network device configuration +# + +config NET_VENDOR_NUVOTON + bool "Nuvoton devices" + default y + depends on ARM && (ARCH_W90X900 || ARCH_NPCM7XX) + help + If you have a network (Ethernet) card belonging to this class, say Y. + +if NET_VENDOR_NUVOTON + +config NPCM7XX_EMC_ETH + bool "Nuvoton NPCM7XX Ethernet EMC" + depends on ARM && ARCH_NPCM7XX + select PHYLIB + select MII + help + Say Y here if you want to use built-in Ethernet MAC + on NPCM750 MCU. + +config NPCM7XX_EMC_ETH_DEBUG + bool "Nuvoton NPCM7XX Ethernet EMC debug" + depends on NPCM7XX_EMC_ETH + help + Say Y here if you want debug info via /proc/driver/npcm7xx_emc.x + +endif # NET_VENDOR_NUVOTON diff --git a/drivers/net/ethernet/nuvoton/Makefile b/drivers/net/ethernet/nuvoton/Makefile new file mode 100644 index 000000000000..3811daa84be8 --- /dev/null +++ b/drivers/net/ethernet/nuvoton/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the Nuvoton network device drivers. +# + +obj-$(CONFIG_NPCM7XX_EMC_ETH) += npcm7xx_emc.o diff --git a/drivers/net/ethernet/nuvoton/npcm7xx_emc.c b/drivers/net/ethernet/nuvoton/npcm7xx_emc.c new file mode 100644 index 000000000000..9872da33fa5d --- /dev/null +++ b/drivers/net/ethernet/nuvoton/npcm7xx_emc.c @@ -0,0 +1,2090 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2014-2019 Nuvoton Technology corporation. + +#ifdef CONFIG_NPCM7XX_EMC_ETH_DEBUG +#define DEBUG +#endif + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/mii.h> +#include <linux/phy.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/ethtool.h> +#include <linux/platform_device.h> +#include <linux/gfp.h> +#include <linux/kthread.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/ctype.h> +#include <linux/debugfs.h> + +#include <linux/clk.h> + +#include <linux/of.h> +#include <linux/of_net.h> +#include <linux/of_device.h> +#include <linux/dma-mapping.h> + +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> + +#include <linux/if_ether.h> + +#include <net/ip.h> +#include <net/ncsi.h> + +#ifdef CONFIG_DEBUG_FS +static struct dentry *npcm7xx_fs_dir; +#endif + +#define MFSEL1_OFFSET 0x00C +#define MFSEL3_OFFSET 0x064 +#define INTCR_OFFSET 0x03C + +#define IPSRST1_OFFSET 0x020 + +#define DRV_MODULE_NAME "npcm7xx-emc" +#define DRV_MODULE_VERSION "3.90" + +/* Ethernet MAC Registers */ +#define REG_CAMCMR 0x00 +#define REG_CAMEN 0x04 +#define REG_CAMM_BASE 0x08 +#define REG_CAML_BASE 0x0c +#define REG_TXDLSA 0x88 +#define REG_RXDLSA 0x8C +#define REG_MCMDR 0x90 +#define REG_MIID 0x94 +#define REG_MIIDA 0x98 +#define REG_FFTCR 0x9C +#define REG_TSDR 0xa0 +#define REG_RSDR 0xa4 +#define REG_DMARFC 0xa8 +#define REG_MIEN 0xac +#define REG_MISTA 0xb0 +#define REG_MGSTA 0xb4 +#define REG_MPCNT 0xb8 +#define REG_MRPC 0xbc +#define REG_MRPCC 0xc0 +#define REG_MREPC 0xc4 +#define REG_DMARFS 0xc8 +#define REG_CTXDSA 0xcc +#define REG_CTXBSA 0xd0 +#define REG_CRXDSA 0xd4 +#define REG_CRXBSA 0xd8 + +/* EMC Diagnostic Registers */ +#define REG_RXFSM 0x200 +#define REG_TXFSM 0x204 +#define REG_FSM0 0x208 +#define REG_FSM1 0x20c +#define REG_DCR 0x210 +#define REG_DMMIR 0x214 +#define REG_BISTR 0x300 + +/* mac controller bit */ +#define MCMDR_RXON BIT(0) +#define MCMDR_ALP BIT(1) +#define MCMDR_ACP BIT(3) +#define MCMDR_SPCRC BIT(5) +#define MCMDR_TXON BIT(8) +#define MCMDR_NDEF BIT(9) +#define MCMDR_FDUP BIT(18) +#define MCMDR_ENMDC BIT(19) +#define MCMDR_OPMOD BIT(20) +#define SWR BIT(24) + +/* cam command regiser */ +#define CAMCMR_AUP BIT(0) +#define CAMCMR_AMP BIT(1) +#define CAMCMR_ABP BIT(2) +#define CAMCMR_CCAM BIT(3) +#define CAMCMR_ECMP BIT(4) + +/* cam enable regiser */ +#define CAM0EN BIT(0) + +/* mac mii controller bit */ +#define PHYAD BIT(8) +#define PHYWR BIT(16) +#define PHYBUSY BIT(17) +#define PHYPRESP BIT(18) +#define MDCON BIT(19) +#define CAM_ENTRY_SIZE 0x08 + +/* rx and tx status */ +#define TXDS_TXCP BIT(19) +#define RXDS_CRCE BIT(17) +#define RXDS_PTLE BIT(19) +#define RXDS_RXGD BIT(20) +#define RXDS_ALIE BIT(21) +#define RXDS_RP BIT(22) + +/* mac interrupt status*/ +#define MISTA_RXINTR BIT(0) +#define MISTA_CRCE BIT(1) +#define MISTA_RXOV BIT(2) +#define MISTA_PTLE BIT(3) +#define MISTA_RXGD BIT(4) +#define MISTA_ALIE BIT(5) +#define MISTA_RP BIT(6) +#define MISTA_MMP BIT(7) +#define MISTA_DFOI BIT(8) +#define MISTA_DENI BIT(9) +#define MISTA_RDU BIT(10) +#define MISTA_RXBERR BIT(11) +#define MISTA_CFR BIT(14) +#define MISTA_TXINTR BIT(16) +#define MISTA_TXEMP BIT(17) +#define MISTA_TXCP BIT(18) +#define MISTA_EXDEF BIT(19) +#define MISTA_NCS BIT(20) +#define MISTA_TXABT BIT(21) +#define MISTA_LC BIT(22) +#define MISTA_TDU BIT(23) +#define MISTA_TXBERR BIT(24) + +/* Transmit/Receive Start Demand Register */ +#define ENSTART BIT(0) + +#define ENRXINTR BIT(0) +#define ENCRCE BIT(1) +#define EMRXOV BIT(2) +#define ENPTLE BIT(3) +#define ENRXGD BIT(4) +#define ENALIE BIT(5) +#define ENRP BIT(6) +#define ENMMP BIT(7) +#define ENDFO BIT(8) +#define ENDENI BIT(9) +#define ENRDU BIT(10) +#define ENRXBERR BIT(11) +#define ENCFR BIT(14) +#define ENTXINTR BIT(16) +#define ENTXEMP BIT(17) +#define ENTXCP BIT(18) +#define ENTXDEF BIT(19) +#define ENNCS BIT(20) +#define ENTXABT BIT(21) +#define ENLC BIT(22) +#define ENTDU BIT(23) +#define ENTXBERR BIT(24) + +/* rx and tx owner bit */ +#define RX_OWN_DMA BIT(31) +#define TX_OWN_DMA BIT(31) + +/* tx frame desc controller bit */ +#define MACTXINTEN BIT(2) +#define CRCMODE BIT(1) +#define PADDINGMODE BIT(0) + +/* fftcr controller bit */ +#define RXTHD (0x03 << 0) +#define TXTHD (0x02 << 8) +#define BLENGTH (0x02 << 20) + +/* global setting for driver */ +#define RX_QUEUE_LEN 128 +#define TX_QUEUE_LEN 64 +#define MAX_RBUFF_SZ 0x600 +#define MAX_TBUFF_SZ 0x600 +#define TX_TIMEOUT 50 +#define DELAY 1000 +#define CAM0 0x0 +#define RX_POLL_SIZE 16 + +#ifdef CONFIG_VLAN_8021Q +#define IS_VLAN 1 +#else +#define IS_VLAN 0 +#endif + +#define MAX_PACKET_SIZE (1514 + (IS_VLAN * 4)) +#define MAX_PACKET_SIZE_W_CRC (MAX_PACKET_SIZE + 4) /* 1518 */ + +#define MHZ (1000 * 1000) +#define MII_TIMEOUT 100 + +struct plat_npcm7xx_emc_data { + char *phy_bus_name; + int phy_addr; + unsigned char mac_addr[ETH_ALEN]; +}; + +struct npcm7xx_rxbd { + __le32 sl; + __le32 buffer; + __le32 reserved; + __le32 next; +}; + +struct npcm7xx_txbd { + __le32 mode; /* Ownership bit and some other bits */ + __le32 buffer; /* Transmit Buffer Starting Address */ + __le32 sl; /* Transmit Byte Count and status bits */ + __le32 next; /* Next Tx Descriptor Starting Address */ +}; + +struct npcm7xx_ether { + struct sk_buff *rx_skb[RX_QUEUE_LEN]; + struct sk_buff *tx_skb[TX_QUEUE_LEN]; + spinlock_t lock; /* lock sk */ + struct npcm7xx_rxbd *rdesc; + struct npcm7xx_txbd *tdesc; + dma_addr_t rdesc_phys; + dma_addr_t tdesc_phys; + struct net_device_stats stats; + struct platform_device *pdev; + struct net_device *ndev; + struct resource *res; + unsigned int msg_enable; + struct mii_bus *mii_bus; + struct phy_device *phy_dev; + struct napi_struct napi; + struct ncsi_dev *ncsidev; + bool use_ncsi; + void __iomem *reg; + int rxirq; + int txirq; + unsigned int cur_tx; + unsigned int cur_rx; + unsigned int finish_tx; + unsigned int pending_tx; + __le32 start_tx_ptr; + __le32 start_rx_ptr; + unsigned int rx_berr; + unsigned int rx_err; + unsigned int rdu; + unsigned int rxov; + __le32 camcmr; + unsigned int rx_stuck; + int link; + int speed; + int duplex; + int need_reset; + char *dump_buf; + struct regmap *rst_regmap; + + /* debug counters */ + unsigned int max_waiting_rx; + unsigned int rx_count_pool; + unsigned int count_xmit; + unsigned int rx_int_count; + unsigned int rx_err_count; + unsigned int tx_int_count; + unsigned int tx_tdu; + unsigned int tx_tdu_i; + unsigned int tx_cp_i; + unsigned int count_finish; + +#ifdef CONFIG_DEBUG_FS + struct dentry *dbgfs_dir; + struct dentry *dbgfs_status; + struct dentry *dbgfs_dma_cap; +#endif +}; + +#if defined CONFIG_NPCM7XX_EMC_ETH_DEBUG || defined CONFIG_DEBUG_FS +#define REG_PRINT(reg_name) {t = scnprintf(next, size, "%-10s = %08X\n", \ + #reg_name, readl(ether->reg + reg_name)); size -= t; next += t; } +#define DUMP_PRINT(f, x...) {t = scnprintf(next, size, f, ## x); size -= t; \ + next += t; } + +static int npcm7xx_info_dump(char *buf, int count, struct net_device *dev) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + struct npcm7xx_txbd *txbd; + struct npcm7xx_rxbd *rxbd; + unsigned long flags; + unsigned int i, cur, txd_offset, rxd_offset; + char *next = buf; + unsigned int size = count; + int t; + int is_locked = spin_is_locked(ðer->lock); + + if (!is_locked) + spin_lock_irqsave(ðer->lock, flags); + + /* ------basic driver information ---- */ + DUMP_PRINT("NPCM7XX EMC %s driver version: %s\n", dev->name, + DRV_MODULE_VERSION); + + REG_PRINT(REG_CAMCMR); + REG_PRINT(REG_CAMEN); + REG_PRINT(REG_CAMM_BASE); + REG_PRINT(REG_CAML_BASE); + REG_PRINT(REG_TXDLSA); + REG_PRINT(REG_RXDLSA); + REG_PRINT(REG_MCMDR); + REG_PRINT(REG_MIID); + REG_PRINT(REG_MIIDA); + REG_PRINT(REG_FFTCR); + REG_PRINT(REG_TSDR); + REG_PRINT(REG_RSDR); + REG_PRINT(REG_DMARFC); + REG_PRINT(REG_MIEN); + REG_PRINT(REG_MISTA); + REG_PRINT(REG_MGSTA); + REG_PRINT(REG_MPCNT); + writel(0x7FFF, (ether->reg + REG_MPCNT)); + REG_PRINT(REG_MRPC); + REG_PRINT(REG_MRPCC); + REG_PRINT(REG_MREPC); + REG_PRINT(REG_DMARFS); + REG_PRINT(REG_CTXDSA); + REG_PRINT(REG_CTXBSA); + REG_PRINT(REG_CRXDSA); + REG_PRINT(REG_CRXBSA); + REG_PRINT(REG_RXFSM); + REG_PRINT(REG_TXFSM); + REG_PRINT(REG_FSM0); + REG_PRINT(REG_FSM1); + REG_PRINT(REG_DCR); + REG_PRINT(REG_DMMIR); + REG_PRINT(REG_BISTR); + DUMP_PRINT("\n"); + + DUMP_PRINT("netif_queue %s\n\n", netif_queue_stopped(dev) ? + "Stopped" : "Running"); + if (ether->rdesc) + DUMP_PRINT("napi is %s\n\n", test_bit(NAPI_STATE_SCHED, + ðer->napi.state) ? + "scheduled" : + "not scheduled"); + + txd_offset = (readl((ether->reg + REG_CTXDSA)) - + readl((ether->reg + REG_TXDLSA))) / + sizeof(struct npcm7xx_txbd); + DUMP_PRINT("TXD offset %6d\n", txd_offset); + DUMP_PRINT("cur_tx %6d\n", ether->cur_tx); + DUMP_PRINT("finish_tx %6d\n", ether->finish_tx); + DUMP_PRINT("pending_tx %6d\n", ether->pending_tx); + /* debug counters */ + DUMP_PRINT("tx_tdu %6d\n", ether->tx_tdu); + ether->tx_tdu = 0; + DUMP_PRINT("tx_tdu_i %6d\n", ether->tx_tdu_i); + ether->tx_tdu_i = 0; + DUMP_PRINT("tx_cp_i %6d\n", ether->tx_cp_i); + ether->tx_cp_i = 0; + DUMP_PRINT("tx_int_count %6d\n", ether->tx_int_count); + ether->tx_int_count = 0; + DUMP_PRINT("count_xmit tx %6d\n", ether->count_xmit); + ether->count_xmit = 0; + DUMP_PRINT("count_finish %6d\n", ether->count_finish); + ether->count_finish = 0; + DUMP_PRINT("\n"); + + rxd_offset = (readl((ether->reg + REG_CRXDSA)) - + readl((ether->reg + REG_RXDLSA))) + / sizeof(struct npcm7xx_txbd); + DUMP_PRINT("RXD offset %6d\n", rxd_offset); + DUMP_PRINT("cur_rx %6d\n", ether->cur_rx); + DUMP_PRINT("rx_err %6d\n", ether->rx_err); + ether->rx_err = 0; + DUMP_PRINT("rx_berr %6d\n", ether->rx_berr); + ether->rx_berr = 0; + DUMP_PRINT("rx_stuck %6d\n", ether->rx_stuck); + ether->rx_stuck = 0; + DUMP_PRINT("rdu %6d\n", ether->rdu); + ether->rdu = 0; + DUMP_PRINT("rxov rx %6d\n", ether->rxov); + ether->rxov = 0; + /* debug counters */ + DUMP_PRINT("rx_int_count %6d\n", ether->rx_int_count); + ether->rx_int_count = 0; + DUMP_PRINT("rx_err_count %6d\n", ether->rx_err_count); + ether->rx_err_count = 0; + DUMP_PRINT("rx_count_pool %6d\n", ether->rx_count_pool); + ether->rx_count_pool = 0; + DUMP_PRINT("max_waiting_rx %5d\n", ether->max_waiting_rx); + ether->max_waiting_rx = 0; + DUMP_PRINT("\n"); + DUMP_PRINT("need_reset %5d\n", ether->need_reset); + + if (ether->tdesc && ether->rdesc) { + cur = ether->finish_tx - 2; + for (i = 0; i < 3; i++) { + cur = (cur + 1) % TX_QUEUE_LEN; + txbd = (ether->tdesc + cur); + DUMP_PRINT("finish %3d txbd mode %08X buffer %08X sl %08X next %08X tx_skb %p\n", + cur, txbd->mode, txbd->buffer, + txbd->sl, txbd->next, ether->tx_skb[cur]); + } + DUMP_PRINT("\n"); + + cur = txd_offset - 2; + for (i = 0; i < 3; i++) { + cur = (cur + 1) % TX_QUEUE_LEN; + txbd = (ether->tdesc + cur); + DUMP_PRINT("txd_of %3d txbd mode %08X buffer %08X sl %08X next %08X\n", + cur, txbd->mode, txbd->buffer, + txbd->sl, txbd->next); + } + DUMP_PRINT("\n"); + + cur = ether->cur_tx - 63; + for (i = 0; i < 64; i++) { + cur = (cur + 1) % TX_QUEUE_LEN; + txbd = (ether->tdesc + cur); + DUMP_PRINT("cur_tx %3d txbd mode %08X buffer %08X sl %08X next %08X\n", + cur, txbd->mode, txbd->buffer, + txbd->sl, txbd->next); + } + DUMP_PRINT("\n"); + + cur = ether->cur_rx - 63; + for (i = 0; i < 64; i++) { + cur = (cur + 1) % RX_QUEUE_LEN; + rxbd = (ether->rdesc + cur); + DUMP_PRINT("cur_rx %3d rxbd sl %08X buffer %08X sl %08X next %08X\n", + cur, rxbd->sl, rxbd->buffer, + rxbd->reserved, rxbd->next); + } + DUMP_PRINT("\n"); + + cur = rxd_offset - 2; + for (i = 0; i < 3; i++) { + cur = (cur + 1) % RX_QUEUE_LEN; + rxbd = (ether->rdesc + cur); + DUMP_PRINT("rxd_of %3d rxbd sl %08X buffer %08X sl %08X next %08X\n", + cur, rxbd->sl, rxbd->buffer, + rxbd->reserved, rxbd->next); + } + DUMP_PRINT("\n"); + } + + if (!is_locked) + spin_unlock_irqrestore(ðer->lock, flags); + + return count - size; +} +#endif + +#ifdef CONFIG_NPCM7XX_EMC_ETH_DEBUG +static void npcm7xx_info_print(struct net_device *dev) +{ + char *emc_dump_buf; + int count; + struct npcm7xx_ether *ether; + struct platform_device *pdev; + const size_t print_size = 5 * PAGE_SIZE; + + ether = netdev_priv(dev); + pdev = ether->pdev; + + emc_dump_buf = kmalloc(print_size, GFP_KERNEL); + if (!emc_dump_buf) { + dev_err(&pdev->dev, "kmalloc failed\n"); + } else { + char c; + char *tmp_buf = emc_dump_buf; + + count = npcm7xx_info_dump(emc_dump_buf, print_size, dev); + while (count > 512) { + c = tmp_buf[512]; + tmp_buf[512] = 0; + dev_info(&pdev->dev, "%s", tmp_buf); + tmp_buf += 512; + tmp_buf[0] = c; + count -= 512; + } + dev_info(&pdev->dev, "%s", tmp_buf); + kfree(emc_dump_buf); + } +} +#endif + +#ifdef CONFIG_DEBUG_FS +#include <linux/seq_file.h> + +static int npcm7xx_debug_show(struct seq_file *sf, void *v) +{ + struct net_device *dev = (struct net_device *)sf->private; + struct npcm7xx_ether *ether = netdev_priv(dev); + const size_t print_size = 5 * PAGE_SIZE; + + if (!ether->dump_buf) { + ether->dump_buf = kmalloc(print_size, GFP_KERNEL); + if (!ether->dump_buf) + return -1; + npcm7xx_info_dump(ether->dump_buf, print_size, dev); + } + + seq_printf(sf, "%s", ether->dump_buf); + if (sf->count < sf->size) { + kfree(ether->dump_buf); + ether->dump_buf = NULL; + } + + return 0; +} + +static int npcm7xx_debug_show_open(struct inode *inode, struct file *file) +{ + return single_open(file, npcm7xx_debug_show, inode->i_private); +} + +static const struct file_operations npcm7xx_debug_show_fops = { + .open = npcm7xx_debug_show_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int npcm7xx_debug_reset(struct seq_file *sf, void *v) +{ + struct net_device *dev = (struct net_device *)sf->private; + struct npcm7xx_ether *ether = netdev_priv(dev); + unsigned long flags; + + seq_puts(sf, "Ask to reset the module\n"); + spin_lock_irqsave(ðer->lock, flags); + writel(0, (ether->reg + REG_MIEN)); + spin_unlock_irqrestore(ðer->lock, flags); + ether->need_reset = 1; + napi_schedule(ðer->napi); + + return 0; +} + +static int npcm7xx_debug_reset_open(struct inode *inode, struct file *file) +{ + return single_open(file, npcm7xx_debug_reset, inode->i_private); +} + +static const struct file_operations npcm7xx_debug_reset_fops = { + .owner = THIS_MODULE, + .open = npcm7xx_debug_reset_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int npcm7xx_debug_fs(struct npcm7xx_ether *ether) +{ + /* Create debugfs main directory if it doesn't exist yet */ + if (!npcm7xx_fs_dir) { + npcm7xx_fs_dir = debugfs_create_dir(DRV_MODULE_NAME, NULL); + + if (!npcm7xx_fs_dir || IS_ERR(npcm7xx_fs_dir)) { + dev_err(ðer->pdev->dev, "ERROR %s, debugfs create directory failed\n", + DRV_MODULE_NAME); + return -ENOMEM; + } + } + + /* Create per netdev entries */ + ether->dbgfs_dir = debugfs_create_dir(ether->ndev->name, + npcm7xx_fs_dir); + if (!ether->dbgfs_dir || IS_ERR(ether->dbgfs_dir)) { + dev_err(ðer->pdev->dev, "ERROR failed to create %s directory\n", ether->ndev->name); + return -ENOMEM; + } + + /* Entry to report DMA RX/TX rings */ + ether->dbgfs_status = + debugfs_create_file("status", 0444, + ether->dbgfs_dir, ether->ndev, + &npcm7xx_debug_show_fops); + + if (!ether->dbgfs_status || IS_ERR(ether->dbgfs_status)) { + dev_err(ðer->pdev->dev, "ERROR creating \'status\' debugfs file\n"); + debugfs_remove_recursive(ether->dbgfs_dir); + + return -ENOMEM; + } + + /* Entry to report the DMA HW features */ + ether->dbgfs_dma_cap = debugfs_create_file("do_reset", 0444, + ether->dbgfs_dir, + ether->ndev, + &npcm7xx_debug_reset_fops); + + if (!ether->dbgfs_dma_cap || IS_ERR(ether->dbgfs_dma_cap)) { + dev_err(ðer->pdev->dev, "ERROR creating stmmac \'do_reset\' debugfs file\n"); + debugfs_remove_recursive(ether->dbgfs_dir); + + return -ENOMEM; + } + + return 0; +} +#endif + +static void npcm7xx_opmode(struct net_device *dev, int speed, int duplex) +{ + __le32 val; + struct npcm7xx_ether *ether = netdev_priv(dev); + + val = readl((ether->reg + REG_MCMDR)); + if (speed == 100) + val |= MCMDR_OPMOD; + else + val &= ~MCMDR_OPMOD; + + if (duplex == DUPLEX_FULL) + val |= MCMDR_FDUP; + else + val &= ~MCMDR_FDUP; + + writel(val, (ether->reg + REG_MCMDR)); +} + +static void adjust_link(struct net_device *dev) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + struct phy_device *phydev = ether->phy_dev; + bool status_change = false; + unsigned long flags; + + /* clear GPIO interrupt status whihc indicates PHY statu change? */ + spin_lock_irqsave(ðer->lock, flags); + + if (phydev->link) { + if (ether->speed != phydev->speed || + ether->duplex != phydev->duplex) { + ether->speed = phydev->speed; + ether->duplex = phydev->duplex; + status_change = true; + } + } else { + ether->speed = 0; + ether->duplex = -1; + } + + if (phydev->link != ether->link) { + ether->link = phydev->link; + status_change = true; + } + + spin_unlock_irqrestore(ðer->lock, flags); + + if (status_change) + npcm7xx_opmode(dev, ether->speed, ether->duplex); +} + +static void npcm7xx_write_cam(struct net_device *dev, + unsigned int x, unsigned char *pval) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + __le32 msw, lsw; + + msw = (pval[0] << 24) | (pval[1] << 16) | (pval[2] << 8) | pval[3]; + + lsw = (pval[4] << 24) | (pval[5] << 16); + + writel(lsw, (ether->reg + REG_CAML_BASE) + x * CAM_ENTRY_SIZE); + writel(msw, (ether->reg + REG_CAMM_BASE) + x * CAM_ENTRY_SIZE); + dev_dbg(ðer->pdev->dev, "REG_CAML_BASE = 0x%08X REG_CAMM_BASE = 0x%08X", lsw, msw); +} + +static struct sk_buff *get_new_skb(struct net_device *dev, u32 i) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + struct sk_buff *skb = dev_alloc_skb(roundup(MAX_PACKET_SIZE_W_CRC, 4)); + + if (!skb) + return NULL; + + /* Do not unmark the following skb_reserve() Receive Buffer Starting + * Address must be aligned to 4 bytes and the following line + * if unmarked will make it align to 2 and this likely will + * hult the RX and crash the linux skb_reserve(skb, NET_IP_ALIGN); + */ + skb->dev = dev; + (ether->rdesc + i)->buffer = + dma_map_single(&dev->dev, skb->data, + roundup(MAX_PACKET_SIZE_W_CRC, 4), + DMA_FROM_DEVICE); + ether->rx_skb[i] = skb; + + return skb; +} + +static int npcm7xx_init_desc(struct net_device *dev) +{ + struct npcm7xx_ether *ether; + struct npcm7xx_txbd *tdesc; + struct npcm7xx_rxbd *rdesc; + struct platform_device *pdev; + unsigned int i; + + ether = netdev_priv(dev); + pdev = ether->pdev; + + if (!ether->tdesc) { + ether->tdesc = (struct npcm7xx_txbd *) + dma_alloc_coherent(&pdev->dev, + sizeof(struct npcm7xx_txbd) * + TX_QUEUE_LEN, + ðer->tdesc_phys, + GFP_KERNEL); + + if (!ether->tdesc) { + dev_err(&pdev->dev, "Failed to allocate memory for tx desc\n"); + return -ENOMEM; + } + } + + if (!ether->rdesc) { + ether->rdesc = (struct npcm7xx_rxbd *) + dma_alloc_coherent(&pdev->dev, + sizeof(struct npcm7xx_rxbd) * + RX_QUEUE_LEN, + ðer->rdesc_phys, + GFP_KERNEL); + + if (!ether->rdesc) { + dev_err(&pdev->dev, "Failed to allocate memory for rx desc\n"); + dma_free_coherent(&pdev->dev, + sizeof(struct npcm7xx_txbd) * + TX_QUEUE_LEN, ether->tdesc, + ether->tdesc_phys); + ether->tdesc = NULL; + return -ENOMEM; + } + } + + for (i = 0; i < TX_QUEUE_LEN; i++) { + unsigned int offset; + + tdesc = (ether->tdesc + i); + + if (i == TX_QUEUE_LEN - 1) + offset = 0; + else + offset = sizeof(struct npcm7xx_txbd) * (i + 1); + + tdesc->next = ether->tdesc_phys + offset; + tdesc->buffer = (__le32)NULL; + tdesc->sl = 0; + tdesc->mode = 0; + } + + ether->start_tx_ptr = ether->tdesc_phys; + + for (i = 0; i < RX_QUEUE_LEN; i++) { + unsigned int offset; + + rdesc = (ether->rdesc + i); + + if (i == RX_QUEUE_LEN - 1) + offset = 0; + else + offset = sizeof(struct npcm7xx_rxbd) * (i + 1); + + rdesc->next = ether->rdesc_phys + offset; + rdesc->sl = RX_OWN_DMA; + + if (!get_new_skb(dev, i)) { + dev_err(&pdev->dev, "get_new_skb() failed\n"); + + for (; i != 0; i--) { + dma_unmap_single(&dev->dev, (dma_addr_t) + ((ether->rdesc + i)->buffer), + roundup(MAX_PACKET_SIZE_W_CRC, + 4), DMA_FROM_DEVICE); + dev_kfree_skb_any(ether->rx_skb[i]); + ether->rx_skb[i] = NULL; + } + + dma_free_coherent(&pdev->dev, + sizeof(struct npcm7xx_txbd) * + TX_QUEUE_LEN, + ether->tdesc, ether->tdesc_phys); + dma_free_coherent(&pdev->dev, + sizeof(struct npcm7xx_rxbd) * + RX_QUEUE_LEN, + ether->rdesc, ether->rdesc_phys); + + return -ENOMEM; + } + } + + ether->start_rx_ptr = ether->rdesc_phys; + wmb(); + for (i = 0; i < TX_QUEUE_LEN; i++) + ether->tx_skb[i] = NULL; + + return 0; +} + +/* This API must call with Tx/Rx stopped */ +static void npcm7xx_free_desc(struct net_device *dev, + bool free_also_descriptors) +{ + struct sk_buff *skb; + u32 i; + struct npcm7xx_ether *ether = netdev_priv(dev); + struct platform_device *pdev = ether->pdev; + + for (i = 0; i < TX_QUEUE_LEN; i++) { + skb = ether->tx_skb[i]; + if (skb) { + dma_unmap_single(&dev->dev, (dma_addr_t)((ether->tdesc + + i)->buffer), + skb->len, DMA_TO_DEVICE); + dev_kfree_skb_any(skb); + ether->tx_skb[i] = NULL; + } + } + + for (i = 0; i < RX_QUEUE_LEN; i++) { + skb = ether->rx_skb[i]; + if (skb) { + dma_unmap_single(&dev->dev, (dma_addr_t)((ether->rdesc + + i)->buffer), + roundup(MAX_PACKET_SIZE_W_CRC, 4), + DMA_FROM_DEVICE); + dev_kfree_skb_any(skb); + ether->rx_skb[i] = NULL; + } + } + + if (free_also_descriptors) { + if (ether->tdesc) + dma_free_coherent(&pdev->dev, + sizeof(struct npcm7xx_txbd) * + TX_QUEUE_LEN, + ether->tdesc, ether->tdesc_phys); + ether->tdesc = NULL; + + if (ether->rdesc) + dma_free_coherent(&pdev->dev, + sizeof(struct npcm7xx_rxbd) * + RX_QUEUE_LEN, + ether->rdesc, ether->rdesc_phys); + ether->rdesc = NULL; + } +} + +static void npcm7xx_set_fifo_threshold(struct net_device *dev) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + __le32 val; + + val = RXTHD | TXTHD | BLENGTH; + writel(val, (ether->reg + REG_FFTCR)); +} + +static void npcm7xx_return_default_idle(struct net_device *dev) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + __le32 val; + __le32 saved_bits; + + val = readl((ether->reg + REG_MCMDR)); + saved_bits = val & (MCMDR_FDUP | MCMDR_OPMOD); + val |= SWR; + writel(val, (ether->reg + REG_MCMDR)); + + /* During the EMC reset the AHB will read 0 from all registers, + * so in order to see if the reset finished we can't count on + * (ether->reg + REG_MCMDR).SWR to become 0, instead we read another + * register that its reset value is not 0, + * we choose (ether->reg + REG_FFTCR). + */ + do { + val = readl((ether->reg + REG_FFTCR)); + } while (val == 0); + + /* + * Now we can verify if (ether->reg + REG_MCMDR).SWR became + * 0 (probably it will be 0 on the first read). + */ + do { + val = readl((ether->reg + REG_MCMDR)); + } while (val & SWR); + + /* restore values */ + writel(saved_bits, (ether->reg + REG_MCMDR)); +} + +static void npcm7xx_enable_mac_interrupt(struct net_device *dev) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + __le32 val; + + val = ENRXINTR | /* Start of RX interrupts */ + ENCRCE | + EMRXOV | + (ENPTLE * (!IS_VLAN)) | /* If we don't support VLAN we want interrupt on long packets */ + ENRXGD | + ENALIE | + ENRP | + ENMMP | + ENDFO | + /* ENDENI | */ /* We don't need interrupt on DMA Early Notification */ + ENRDU | /* We don't need interrupt on Receive Descriptor Unavailable Interrupt */ + ENRXBERR | + /* ENCFR | */ + ENTXINTR | /* Start of TX interrupts */ + ENTXEMP | + ENTXCP | + ENTXDEF | + ENNCS | + ENTXABT | + ENLC | + /* ENTDU | */ /* We don't need interrupt on Transmit Descriptor Unavailable at start of operation */ + ENTXBERR; + writel(val, (ether->reg + REG_MIEN)); +} + +static void npcm7xx_get_and_clear_int(struct net_device *dev, + __le32 *val, __le32 mask) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + + *val = readl((ether->reg + REG_MISTA)) & mask; + writel(*val, (ether->reg + REG_MISTA)); +} + +static void npcm7xx_set_global_maccmd(struct net_device *dev) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + __le32 val; + + val = readl((ether->reg + REG_MCMDR)); + + val |= MCMDR_SPCRC | MCMDR_ENMDC | MCMDR_ACP | MCMDR_NDEF; + if (IS_VLAN) { + /* + * we set ALP accept long packets since VLAN packets + * are 4 bytes longer than 1518 + */ + val |= MCMDR_ALP; + /* limit receive length to 1522 bytes due to VLAN */ + writel(MAX_PACKET_SIZE_W_CRC, (ether->reg + REG_DMARFC)); + } + writel(val, (ether->reg + REG_MCMDR)); +} + +static void npcm7xx_enable_cam(struct net_device *dev) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + __le32 val; + + npcm7xx_write_cam(dev, CAM0, dev->dev_addr); + + val = readl((ether->reg + REG_CAMEN)); + val |= CAM0EN; + writel(val, (ether->reg + REG_CAMEN)); +} + +static void npcm7xx_set_curdest(struct net_device *dev) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + + writel(ether->start_rx_ptr, (ether->reg + REG_RXDLSA)); + writel(ether->start_tx_ptr, (ether->reg + REG_TXDLSA)); +} + +static void npcm7xx_ether_set_rx_mode(struct net_device *dev) +{ + struct npcm7xx_ether *ether; + __le32 rx_mode; + + ether = netdev_priv(dev); + + dev_dbg(ðer->pdev->dev, "%s CAMCMR_AUP\n", + (dev->flags & IFF_PROMISC) ? "Set" : "Clear"); + if (dev->flags & IFF_PROMISC) + rx_mode = CAMCMR_AUP | CAMCMR_AMP | CAMCMR_ABP | CAMCMR_ECMP; + else if ((dev->flags & IFF_ALLMULTI) || !netdev_mc_empty(dev)) + rx_mode = CAMCMR_AMP | CAMCMR_ABP | CAMCMR_ECMP; + else + rx_mode = CAMCMR_ECMP | CAMCMR_ABP; + writel(rx_mode, (ether->reg + REG_CAMCMR)); + ether->camcmr = rx_mode; +} + +static void npcm7xx_reset_mac(struct net_device *dev, int need_free) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + + netif_tx_lock(dev); + + /* disable RX and TX */ + writel(readl((ether->reg + REG_MCMDR)) & ~(MCMDR_TXON | MCMDR_RXON), + (ether->reg + REG_MCMDR)); + + npcm7xx_return_default_idle(dev); + npcm7xx_set_fifo_threshold(dev); + + if (need_free) + npcm7xx_free_desc(dev, false); + + npcm7xx_init_desc(dev); + + ether->cur_tx = 0x0; + ether->finish_tx = 0x0; + ether->pending_tx = 0x0; + ether->cur_rx = 0x0; + ether->tx_tdu = 0; + ether->tx_tdu_i = 0; + ether->tx_cp_i = 0; + + npcm7xx_set_curdest(dev); + npcm7xx_enable_cam(dev); + npcm7xx_ether_set_rx_mode(dev); + npcm7xx_enable_mac_interrupt(dev); + npcm7xx_set_global_maccmd(dev); + + /* enable RX and TX */ + writel(readl((ether->reg + REG_MCMDR)) | MCMDR_TXON | MCMDR_RXON, + (ether->reg + REG_MCMDR)); + + /* trigger RX */ + writel(ENSTART, (ether->reg + REG_RSDR)); + + ether->need_reset = 0; + + netif_wake_queue(dev); + netif_tx_unlock(dev); +} + +static int npcm7xx_mdio_write(struct mii_bus *bus, int phy_id, int regnum, + u16 value) +{ + struct npcm7xx_ether *ether = bus->priv; + unsigned long timeout = jiffies + msecs_to_jiffies(MII_TIMEOUT * 100); + + writel(value, (ether->reg + REG_MIID)); + writel((phy_id << 0x08) | regnum | PHYBUSY | PHYWR, + (ether->reg + REG_MIIDA)); + + /* Wait for completion */ + while (readl((ether->reg + REG_MIIDA)) & PHYBUSY) { + if (time_after(jiffies, timeout)) { + dev_dbg(ðer->pdev->dev, "mdio read timed out\n ether->reg = 0x%x phy_id=0x%x REG_MIIDA=0x%x\n", + (unsigned int)ether->reg, phy_id + , readl((ether->reg + REG_MIIDA))); + return -ETIMEDOUT; + } + cpu_relax(); + } + + return 0; +} + +static int npcm7xx_mdio_read(struct mii_bus *bus, int phy_id, int regnum) +{ + struct npcm7xx_ether *ether = bus->priv; + unsigned long timeout = jiffies + msecs_to_jiffies(MII_TIMEOUT * 100); + + writel((phy_id << 0x08) | regnum | PHYBUSY, (ether->reg + REG_MIIDA)); + + /* Wait for completion */ + while (readl((ether->reg + REG_MIIDA)) & PHYBUSY) { + if (time_after(jiffies, timeout)) { + dev_dbg(ðer->pdev->dev, "mdio read timed out\n ether->reg = 0x%x phy_id=0x%x REG_MIIDA=0x%x\n", + (unsigned int)ether->reg, phy_id + , readl((ether->reg + REG_MIIDA))); + return -ETIMEDOUT; + } + cpu_relax(); + } + + return readl((ether->reg + REG_MIID)); +} + +static int npcm7xx_mdio_reset(struct mii_bus *bus) +{ + /* reset EMAC engine?? */ + return 0; +} + +static int npcm7xx_set_mac_address(struct net_device *dev, void *addr) +{ + struct sockaddr *address = addr; + + if (!is_valid_ether_addr((u8 *)address->sa_data)) + return -EADDRNOTAVAIL; + + memcpy(dev->dev_addr, address->sa_data, dev->addr_len); + npcm7xx_write_cam(dev, CAM0, dev->dev_addr); + + return 0; +} + +static int npcm7xx_ether_close(struct net_device *dev) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + + npcm7xx_return_default_idle(dev); + + if (ether->phy_dev) + phy_stop(ether->phy_dev); + else if (ether->use_ncsi) + ncsi_stop_dev(ether->ncsidev); + + msleep(20); + + free_irq(ether->txirq, dev); + free_irq(ether->rxirq, dev); + + netif_stop_queue(dev); + napi_disable(ðer->napi); + + npcm7xx_free_desc(dev, true); + + kfree(ether->dump_buf); + ether->dump_buf = NULL; + + return 0; +} + +static struct net_device_stats *npcm7xx_ether_stats(struct net_device *dev) +{ + struct npcm7xx_ether *ether; + + ether = netdev_priv(dev); + return ðer->stats; +} + +static int npcm7xx_clean_tx(struct net_device *dev, bool from_xmit) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + struct npcm7xx_txbd *txbd; + struct sk_buff *s; + dma_addr_t cur_entry, entry; + __le32 sl; + + if (ether->pending_tx == 0) + return (0); + + cur_entry = readl((ether->reg + REG_CTXDSA)); + + /* Release old used buffers */ + entry = ether->tdesc_phys + sizeof(struct npcm7xx_txbd) * + (ether->finish_tx); + + while (entry != cur_entry) { + txbd = (ether->tdesc + ether->finish_tx); + s = ether->tx_skb[ether->finish_tx]; + if (!s) + break; + + ether->count_finish++; + + dma_unmap_single(&dev->dev, txbd->buffer, s->len, + DMA_TO_DEVICE); + consume_skb(s); + ether->tx_skb[ether->finish_tx] = NULL; + + if (++ether->finish_tx >= TX_QUEUE_LEN) + ether->finish_tx = 0; + ether->pending_tx--; + + sl = txbd->sl; + if (sl & TXDS_TXCP) { + ether->stats.tx_packets++; + ether->stats.tx_bytes += (sl & 0xFFFF); + } else { + ether->stats.tx_errors++; + } + + entry = ether->tdesc_phys + sizeof(struct npcm7xx_txbd) * + (ether->finish_tx); + } + + if (!from_xmit && unlikely(netif_queue_stopped(dev) && + (TX_QUEUE_LEN - ether->pending_tx) > 1)) { + netif_tx_lock(dev); + if (netif_queue_stopped(dev) && + (TX_QUEUE_LEN - ether->pending_tx) > 1) { + netif_wake_queue(dev); + } + netif_tx_unlock(dev); + } + + return(0); +} + +static int npcm7xx_ether_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + struct npcm7xx_txbd *txbd; + unsigned long flags; + + ether->count_xmit++; + + /* Insert new buffer */ + txbd = (ether->tdesc + ether->cur_tx); + txbd->buffer = dma_map_single(&dev->dev, skb->data, skb->len, + DMA_TO_DEVICE); + ether->tx_skb[ether->cur_tx] = skb; + if (skb->len > MAX_PACKET_SIZE) + dev_err(ðer->pdev->dev, "skb->len (= %d) > MAX_PACKET_SIZE (= %d)\n", + skb->len, MAX_PACKET_SIZE); + + txbd->sl = skb->len > MAX_PACKET_SIZE ? MAX_PACKET_SIZE : skb->len; + dma_wmb(); + + txbd->mode = TX_OWN_DMA | PADDINGMODE | CRCMODE; + wmb(); + + /* trigger TX */ + writel(ENSTART, (ether->reg + REG_TSDR)); + + if (++ether->cur_tx >= TX_QUEUE_LEN) + ether->cur_tx = 0; + + spin_lock_irqsave(ðer->lock, flags); + ether->pending_tx++; + + npcm7xx_clean_tx(dev, true); + + if (ether->pending_tx >= TX_QUEUE_LEN - 1) { + __le32 reg_mien; + unsigned int index_to_wake = ether->cur_tx + + ((TX_QUEUE_LEN * 3) / 4); + + if (index_to_wake >= TX_QUEUE_LEN) + index_to_wake -= TX_QUEUE_LEN; + + txbd = (ether->tdesc + index_to_wake); + txbd->mode = TX_OWN_DMA | PADDINGMODE | CRCMODE | MACTXINTEN; + wmb(); + + writel(MISTA_TDU, (ether->reg + REG_MISTA)); + /* Clear TDU interrupt */ + reg_mien = readl((ether->reg + REG_MIEN)); + + if (reg_mien != 0) + /* Enable TDU interrupt */ + writel(reg_mien | ENTDU, (ether->reg + REG_MIEN)); + + ether->tx_tdu++; + netif_stop_queue(dev); + } + + spin_unlock_irqrestore(ðer->lock, flags); + + return 0; +} + +static irqreturn_t npcm7xx_tx_interrupt(int irq, void *dev_id) +{ + struct npcm7xx_ether *ether; + struct platform_device *pdev; + struct net_device *dev; + __le32 status; + unsigned long flags; + + dev = dev_id; + ether = netdev_priv(dev); + pdev = ether->pdev; + + npcm7xx_get_and_clear_int(dev, &status, 0xFFFF0000); + + ether->tx_int_count++; + + if (status & MISTA_EXDEF) + dev_err(&pdev->dev, "emc defer exceed interrupt status=0x%08X\n" + , status); + else if (status & MISTA_TXBERR) { + dev_err(&pdev->dev, "emc bus error interrupt status=0x%08X\n", + status); +#ifdef CONFIG_NPCM7XX_EMC_ETH_DEBUG + npcm7xx_info_print(dev); +#endif + spin_lock_irqsave(ðer->lock, flags); + writel(0, (ether->reg + REG_MIEN)); /* disable any interrupt */ + spin_unlock_irqrestore(ðer->lock, flags); + ether->need_reset = 1; + } else if (status & ~(MISTA_TXINTR | MISTA_TXCP | MISTA_TDU)) + dev_err(&pdev->dev, "emc other error interrupt status=0x%08X\n", + status); + + /* if we got MISTA_TXCP | MISTA_TDU remove those interrupt and call napi */ + if (status & (MISTA_TXCP | MISTA_TDU) & + readl((ether->reg + REG_MIEN))) { + __le32 reg_mien; + + spin_lock_irqsave(ðer->lock, flags); + reg_mien = readl((ether->reg + REG_MIEN)); + if (reg_mien & ENTDU) + /* Disable TDU interrupt */ + writel(reg_mien & (~ENTDU), (ether->reg + REG_MIEN)); + + spin_unlock_irqrestore(ðer->lock, flags); + + if (status & MISTA_TXCP) + ether->tx_cp_i++; + if (status & MISTA_TDU) + ether->tx_tdu_i++; + } else { + dev_dbg(&pdev->dev, "status=0x%08X\n", status); + } + + napi_schedule(ðer->napi); + + return IRQ_HANDLED; +} + +static irqreturn_t npcm7xx_rx_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = (struct net_device *)dev_id; + struct npcm7xx_ether *ether = netdev_priv(dev); + struct platform_device *pdev = ether->pdev; + __le32 status; + unsigned long flags; + unsigned int any_err = 0; + __le32 rxfsm; + + npcm7xx_get_and_clear_int(dev, &status, 0xFFFF); + ether->rx_int_count++; + + if (unlikely(status & MISTA_RXBERR)) { + ether->rx_berr++; + dev_err(&pdev->dev, "emc rx bus error status=0x%08X\n", status); +#ifdef CONFIG_NPCM7XX_EMC_ETH_DEBUG + npcm7xx_info_print(dev); +#endif + spin_lock_irqsave(ðer->lock, flags); + writel(0, (ether->reg + REG_MIEN)); /* disable any interrupt */ + spin_unlock_irqrestore(ðer->lock, flags); + ether->need_reset = 1; + napi_schedule(ðer->napi); + return IRQ_HANDLED; + } + + if (unlikely(status & (MISTA_RXOV | MISTA_RDU))) { + /* + * filter out all received packets until we have + * enough available buffer descriptors + */ + writel(0, (ether->reg + REG_CAMCMR)); + any_err = 1; + if (status & (MISTA_RXOV)) + ether->rxov++; + if (status & (MISTA_RDU)) + ether->rdu++; + + /* + * workaround Errata 1.36: EMC Hangs on receiving 253-256 + * byte packet + */ + rxfsm = readl((ether->reg + REG_RXFSM)); + + if ((rxfsm & 0xFFFFF000) == 0x08044000) { + int i; + + for (i = 0; i < 32; i++) { + rxfsm = readl((ether->reg + REG_RXFSM)); + if ((rxfsm & 0xFFFFF000) != 0x08044000) + break; + } + if (i == 32) { + ether->rx_stuck++; + spin_lock_irqsave(ðer->lock, flags); +#ifdef CONFIG_NPCM7XX_EMC_ETH_DEBUG + npcm7xx_info_print(dev); +#endif + writel(0, (ether->reg + REG_MIEN)); + spin_unlock_irqrestore(ðer->lock, flags); + ether->need_reset = 1; + napi_schedule(ðer->napi); + dev_err(&pdev->dev, "stuck on REG_RXFSM = 0x%08X status=%08X doing reset!\n", rxfsm, status); + return IRQ_HANDLED; + } + } + } + + /* echo MISTA status on unexpected flags although we don't do anithing with them */ + if (unlikely(status & + (/* MISTA_RXINTR | */ /* Receive - all RX interrupt set this */ + MISTA_CRCE | /* CRC Error */ + /* MISTA_RXOV | */ /* Receive FIFO Overflow - we alread handled it */ + (MISTA_PTLE * !IS_VLAN) | /* Packet Too Long is needed if VLAN is not supported */ + /* MISTA_RXGD | */ /* Receive Good - this is the common good case */ + MISTA_ALIE | /* Alignment Error */ + MISTA_RP | /* Runt Packet */ + MISTA_MMP | /* More Missed Packet */ + MISTA_DFOI | /* Maximum Frame Length */ + /* MISTA_DENI | */ /* DMA Early Notification - every packet get this */ + /* MISTA_RDU | */ /* Receive Descriptor Unavailable */ + /* MISTA_RXBERR | */ /* Receive Bus Error Interrupt - we alread handled it */ + /* MISTA_CFR | */ /* Control Frame Receive - not an error */ + 0))) { + dev_dbg(&pdev->dev, "emc rx MISTA status=0x%08X\n", status); + any_err = 1; + ether->rx_err++; + } + + if (!any_err && ((status & MISTA_RXGD) == 0)) + dev_err(&pdev->dev, "emc rx MISTA status=0x%08X\n", status); + + spin_lock_irqsave(ðer->lock, flags); + writel(readl((ether->reg + REG_MIEN)) & ~ENRXGD, + (ether->reg + REG_MIEN)); + spin_unlock_irqrestore(ðer->lock, flags); + napi_schedule(ðer->napi); + + return IRQ_HANDLED; +} + +static int npcm7xx_poll(struct napi_struct *napi, int budget) +{ + struct npcm7xx_ether *ether = + container_of(napi, struct npcm7xx_ether, napi); + struct npcm7xx_rxbd *rxbd; + struct net_device *dev = ether->ndev; + struct platform_device *pdev = ether->pdev; + struct sk_buff *skb, *s; + unsigned int length; + __le32 status; + unsigned long flags; + int rx_cnt = 0; + int complete = 0; + unsigned int rx_offset = (readl((ether->reg + REG_CRXDSA)) - + ether->start_rx_ptr) / + sizeof(struct npcm7xx_txbd); + unsigned int local_count = (rx_offset >= ether->cur_rx) ? + rx_offset - ether->cur_rx : rx_offset + + RX_QUEUE_LEN - ether->cur_rx; + + if (local_count > ether->max_waiting_rx) + ether->max_waiting_rx = local_count; + + if (local_count > (4 * RX_POLL_SIZE)) + /* + * we are porbably in a storm of short packets and we don't + * want to get into RDU since short packets in RDU cause + * many RXOV which may cause EMC halt, so we filter out all + * coming packets + */ + writel(0, (ether->reg + REG_CAMCMR)); + + if (local_count <= budget) + /* we can restore accepting of packets */ + writel(ether->camcmr, (ether->reg + REG_CAMCMR)); + + spin_lock_irqsave(ðer->lock, flags); + npcm7xx_clean_tx(dev, false); + spin_unlock_irqrestore(ðer->lock, flags); + + rxbd = (ether->rdesc + ether->cur_rx); + + while (rx_cnt < budget) { + status = rxbd->sl; + if ((status & RX_OWN_DMA) == RX_OWN_DMA) { + complete = 1; + break; + } + /* for debug puposes we save the previous value */ + rxbd->reserved = status; + s = ether->rx_skb[ether->cur_rx]; + length = status & 0xFFFF; + + /* + * If VLAN is not supporte RXDS_PTLE (packet too long) is also + * an error + */ + if (likely((status & (RXDS_RXGD | RXDS_CRCE | RXDS_ALIE | + RXDS_RP | (IS_VLAN ? 0 : RXDS_PTLE))) == + RXDS_RXGD) && likely(length <= MAX_PACKET_SIZE)) { + dma_unmap_single(&dev->dev, (dma_addr_t)rxbd->buffer, + roundup(MAX_PACKET_SIZE_W_CRC, 4), + DMA_FROM_DEVICE); + + skb_put(s, length); + s->protocol = eth_type_trans(s, dev); + netif_receive_skb(s); + ether->stats.rx_packets++; + ether->stats.rx_bytes += length; + rx_cnt++; + ether->rx_count_pool++; + + /* now we allocate new skb instead if the used one. */ + skb = dev_alloc_skb(roundup(MAX_PACKET_SIZE_W_CRC, 4)); + if (!skb) { + dev_err(&pdev->dev, "get skb buffer error\n"); + ether->stats.rx_dropped++; + goto rx_out; + } + + /* Do not unmark the following skb_reserve() Receive + * Buffer Starting Address must be aligned + * to 4 bytes and the following line if unmarked + * will make it align to 2 and this likely + * will hult the RX and crash the linux + * skb_reserve(skb, NET_IP_ALIGN); + */ + skb->dev = dev; + + rxbd->buffer = dma_map_single(&dev->dev, skb->data, + roundup(MAX_PACKET_SIZE_W_CRC, 4), + DMA_FROM_DEVICE); + ether->rx_skb[ether->cur_rx] = skb; + } else { + ether->rx_err_count++; + ether->stats.rx_errors++; + dev_dbg(&pdev->dev, "rx_errors = %lu status = 0x%08X\n", + ether->stats.rx_errors, status); + + if (status & RXDS_RP) { + ether->stats.rx_length_errors++; + dev_dbg(&pdev->dev, "rx_length_errors = %lu\n", + ether->stats.rx_length_errors); + } else if (status & RXDS_CRCE) { + ether->stats.rx_crc_errors++; + dev_dbg(&pdev->dev, "rx_crc_errors = %lu\n", + ether->stats.rx_crc_errors); + } else if (status & RXDS_ALIE) { + ether->stats.rx_frame_errors++; + dev_dbg(&pdev->dev, "rx_frame_errors = %lu\n", + ether->stats.rx_frame_errors); + } else if (((!IS_VLAN) && (status & RXDS_PTLE)) || + length > MAX_PACKET_SIZE) { + ether->stats.rx_length_errors++; + dev_dbg(&pdev->dev, "rx_length_errors = %lu\n", + ether->stats.rx_length_errors); + } + } + + wmb(); + rxbd->sl = RX_OWN_DMA; + wmb(); + + if (++ether->cur_rx >= RX_QUEUE_LEN) + ether->cur_rx = 0; + + rxbd = (ether->rdesc + ether->cur_rx); + } + + if (complete) { + napi_complete(napi); + + if (ether->need_reset) { + dev_dbg(&pdev->dev, "Reset\n"); + npcm7xx_reset_mac(dev, 1); + } + + spin_lock_irqsave(ðer->lock, flags); + writel(readl((ether->reg + REG_MIEN)) | ENRXGD, (ether->reg + + REG_MIEN)); + spin_unlock_irqrestore(ðer->lock, flags); + } else { + rx_offset = (readl((ether->reg + REG_CRXDSA)) - + ether->start_rx_ptr) / sizeof(struct npcm7xx_txbd); + local_count = (rx_offset >= ether->cur_rx) ? rx_offset - + ether->cur_rx : rx_offset + RX_QUEUE_LEN - + ether->cur_rx; + + if (local_count > ether->max_waiting_rx) + ether->max_waiting_rx = local_count; + + if (local_count > (3 * RX_POLL_SIZE)) + /* + * we are porbably in a storm of short packets and + * we don't want to get into RDU since short packets in + * RDU cause many RXOV which may cause + * EMC halt, so we filter out all coming packets + */ + writel(0, (ether->reg + REG_CAMCMR)); + if (local_count <= RX_POLL_SIZE) + /* we can restore accepting of packets */ + writel(ether->camcmr, (ether->reg + REG_CAMCMR)); + } +rx_out: + + /* trigger RX */ + writel(ENSTART, (ether->reg + REG_RSDR)); + return rx_cnt; +} + +static int npcm7xx_ether_open(struct net_device *dev) +{ + struct npcm7xx_ether *ether; + struct platform_device *pdev; + + ether = netdev_priv(dev); + pdev = ether->pdev; + + if (ether->use_ncsi) { + ether->speed = 100; + ether->duplex = DUPLEX_FULL; + npcm7xx_opmode(dev, 100, DUPLEX_FULL); + } + npcm7xx_reset_mac(dev, 0); + + if (request_irq(ether->txirq, npcm7xx_tx_interrupt, 0x0, pdev->name, + dev)) { + dev_err(&pdev->dev, "register irq tx failed\n"); + npcm7xx_ether_close(dev); + return -EAGAIN; + } + + if (request_irq(ether->rxirq, npcm7xx_rx_interrupt, 0x0, pdev->name, + dev)) { + dev_err(&pdev->dev, "register irq rx failed\n"); + npcm7xx_ether_close(dev); + return -EAGAIN; + } + + if (ether->phy_dev) + phy_start(ether->phy_dev); + else if (ether->use_ncsi) + netif_carrier_on(dev); + + netif_start_queue(dev); + napi_enable(ðer->napi); + + /* trigger RX */ + writel(ENSTART, (ether->reg + REG_RSDR)); + + /* Start the NCSI device */ + if (ether->use_ncsi) { + int err = ncsi_start_dev(ether->ncsidev); + + if (err) { + npcm7xx_ether_close(dev); + return err; + } + } + + dev_info(&pdev->dev, "%s is OPENED\n", dev->name); + + return 0; +} + +static int npcm7xx_ether_ioctl(struct net_device *dev, + struct ifreq *ifr, int cmd) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + struct phy_device *phydev = ether->phy_dev; + + if (!netif_running(dev)) + return -EINVAL; + + if (!phydev) + return -ENODEV; + + return phy_mii_ioctl(phydev, ifr, cmd); +} + +static void npcm7xx_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strlcpy(info->driver, DRV_MODULE_NAME, sizeof(info->driver)); + strlcpy(info->version, DRV_MODULE_VERSION, sizeof(info->version)); + strlcpy(info->fw_version, "N/A", sizeof(info->fw_version)); + strlcpy(info->bus_info, "N/A", sizeof(info->bus_info)); +} + +static int npcm7xx_get_settings(struct net_device *dev, + struct ethtool_link_ksettings *cmd) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + struct phy_device *phydev = ether->phy_dev; + + if (!phydev) + return -ENODEV; + + dev_info(ðer->pdev->dev, "\n\nnpcm7xx_get_settings\n"); + phy_ethtool_ksettings_get(phydev, cmd); + + return 0; +} + +static int npcm7xx_set_settings(struct net_device *dev, + const struct ethtool_link_ksettings *cmd) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + struct phy_device *phydev = ether->phy_dev; + int ret; + + if (!phydev) + return -ENODEV; + + dev_info(ðer->pdev->dev, "\n\nnpcm7xx_set_settings\n"); + ret = phy_ethtool_ksettings_set(phydev, cmd); + + return ret; +} + +static u32 npcm7xx_get_msglevel(struct net_device *dev) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + + return ether->msg_enable; +} + +static void npcm7xx_set_msglevel(struct net_device *dev, u32 level) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + + ether->msg_enable = level; +} + +static const struct ethtool_ops npcm7xx_ether_ethtool_ops = { + .get_link_ksettings = npcm7xx_get_settings, + .set_link_ksettings = npcm7xx_set_settings, + .get_drvinfo = npcm7xx_get_drvinfo, + .get_msglevel = npcm7xx_get_msglevel, + .set_msglevel = npcm7xx_set_msglevel, + .get_link = ethtool_op_get_link, +}; + +static const struct net_device_ops npcm7xx_ether_netdev_ops = { + .ndo_open = npcm7xx_ether_open, + .ndo_stop = npcm7xx_ether_close, + .ndo_start_xmit = npcm7xx_ether_start_xmit, + .ndo_get_stats = npcm7xx_ether_stats, + .ndo_set_rx_mode = npcm7xx_ether_set_rx_mode, + .ndo_set_mac_address = npcm7xx_set_mac_address, + .ndo_do_ioctl = npcm7xx_ether_ioctl, + .ndo_validate_addr = eth_validate_addr, +}; + +static void get_mac_address(struct net_device *dev) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + struct platform_device *pdev = ether->pdev; + struct device_node *np = ether->pdev->dev.of_node; + const u8 *mac_address = NULL; + + mac_address = of_get_mac_address(np); + + if (mac_address != 0) + ether_addr_copy(dev->dev_addr, mac_address); + + if (is_valid_ether_addr(dev->dev_addr)) { + dev_info(&pdev->dev, "%s: device MAC address : %pM\n", + pdev->name, dev->dev_addr); + } else { + eth_hw_addr_random(dev); + dev_info(&pdev->dev, "%s: device MAC address (random generator) %pM\n", + dev->name, dev->dev_addr); + } +} + +static int npcm7xx_mii_setup(struct net_device *dev) +{ + struct npcm7xx_ether *ether = netdev_priv(dev); + struct platform_device *pdev; + struct phy_device *phydev = NULL; + int i, err = 0; + + pdev = ether->pdev; + + ether->mii_bus = mdiobus_alloc(); + if (!ether->mii_bus) { + err = -ENOMEM; + dev_err(&pdev->dev, "mdiobus_alloc() failed\n"); + goto out0; + } + + ether->mii_bus->name = "npcm7xx_rmii"; + ether->mii_bus->read = &npcm7xx_mdio_read; + ether->mii_bus->write = &npcm7xx_mdio_write; + ether->mii_bus->reset = &npcm7xx_mdio_reset; + snprintf(ether->mii_bus->id, MII_BUS_ID_SIZE, "%s-%x", + ether->pdev->name, ether->pdev->id); + dev_dbg(&pdev->dev, "%s ether->mii_bus->id=%s\n", __func__, + ether->mii_bus->id); + ether->mii_bus->priv = ether; + ether->mii_bus->parent = ðer->pdev->dev; + + for (i = 0; i < PHY_MAX_ADDR; i++) + ether->mii_bus->irq[i] = PHY_POLL; + + platform_set_drvdata(ether->pdev, ether->mii_bus); + + /* Enable MDIO Clock */ + writel(readl((ether->reg + REG_MCMDR)) | MCMDR_ENMDC, + (ether->reg + REG_MCMDR)); + + if (mdiobus_register(ether->mii_bus)) { + dev_err(&pdev->dev, "mdiobus_register() failed\n"); + goto out2; + } + + phydev = phy_find_first(ether->mii_bus); + if (!phydev) { + dev_err(&pdev->dev, "phy_find_first() failed\n"); + goto out3; + } + + dev_info(&pdev->dev, " name = %s ETH-Phy-Id = 0x%x\n", + phydev_name(phydev), phydev->phy_id); + + phydev = phy_connect(dev, phydev_name(phydev), + &adjust_link, + PHY_INTERFACE_MODE_RMII); + + dev_info(&pdev->dev, " ETH-Phy-Id = 0x%x name = %s\n", + phydev->phy_id, phydev->drv->name); + + if (IS_ERR(phydev)) { + err = PTR_ERR(phydev); + dev_err(&pdev->dev, "phy_connect() failed - %d\n", err); + goto out3; + } + + linkmode_and(phydev->supported, phydev->supported, PHY_BASIC_FEATURES); + linkmode_copy(phydev->advertising, phydev->supported); + ether->phy_dev = phydev; + + return 0; + +out3: + mdiobus_unregister(ether->mii_bus); +out2: + kfree(ether->mii_bus->irq); + mdiobus_free(ether->mii_bus); +out0: + + return err; +} + +static const struct of_device_id emc_dt_id[] = { + { .compatible = "nuvoton,npcm750-emc", }, + {}, +}; +MODULE_DEVICE_TABLE(of, emc_dt_id); + +static void npcm7xx_ncsi_handler(struct ncsi_dev *nd) +{ + if (unlikely(nd->state != ncsi_dev_state_functional)) + return; + + netdev_info(nd->dev, "NCSI interface %s\n", + nd->link_up ? "up" : "down"); +} + +static int npcm7xx_ether_probe(struct platform_device *pdev) +{ + struct npcm7xx_ether *ether; + struct net_device *dev; + int error; + + struct clk *emc_clk = NULL; + struct device_node *np = pdev->dev.of_node; + + pdev->id = of_alias_get_id(np, "ethernet"); + if (pdev->id < 0) + pdev->id = 0; + + emc_clk = devm_clk_get(&pdev->dev, NULL); + + if (IS_ERR(emc_clk)) + return PTR_ERR(emc_clk); + + /* Enable Clock */ + clk_prepare_enable(emc_clk); + + error = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (error) + return -ENODEV; + + dev = alloc_etherdev(sizeof(struct npcm7xx_ether)); + if (!dev) + return -ENOMEM; + + ether = netdev_priv(dev); + + ether->rst_regmap = + syscon_regmap_lookup_by_compatible("nuvoton,npcm750-rst"); + if (IS_ERR(ether->rst_regmap)) { + dev_err(&pdev->dev, "%s: failed to find nuvoton,npcm750-rst\n", __func__); + return IS_ERR(ether->rst_regmap); + } + + /* Reset EMC module */ + if (pdev->id == 0) { + regmap_update_bits(ether->rst_regmap, IPSRST1_OFFSET, + (0x1 << 6), (0x1 << 6)); + regmap_update_bits(ether->rst_regmap, IPSRST1_OFFSET, + (0x1 << 6), 0); + } + if (pdev->id == 1) { + regmap_update_bits(ether->rst_regmap, IPSRST1_OFFSET, + (0x1 << 21), (0x1 << 21)); + regmap_update_bits(ether->rst_regmap, IPSRST1_OFFSET, + (0x1 << 21), 0); + } + + ether->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!ether->res) { + dev_err(&pdev->dev, "failed to get I/O memory\n"); + error = -ENXIO; + goto failed_free; + } + + if (!request_mem_region(ether->res->start, + resource_size(ether->res), pdev->name)) { + dev_err(&pdev->dev, "failed to request I/O memory\n"); + error = -EBUSY; + goto failed_free; + } + + ether->reg = ioremap(ether->res->start, resource_size(ether->res)); + dev_dbg(&pdev->dev, "%s ether->reg = 0x%x\n", __func__, + (unsigned int)ether->reg); + + if (!ether->reg) { + dev_err(&pdev->dev, "failed to remap I/O memory\n"); + error = -ENXIO; + goto failed_free_mem; + } + + ether->txirq = platform_get_irq(pdev, 0); + if (ether->txirq < 0) { + dev_err(&pdev->dev, "failed to get ether tx irq\n"); + error = -ENXIO; + goto failed_free_io; + } + + ether->rxirq = platform_get_irq(pdev, 1); + if (ether->rxirq < 0) { + dev_err(&pdev->dev, "failed to get ether rx irq\n"); + error = -ENXIO; + goto failed_free_io; + } + + SET_NETDEV_DEV(dev, &pdev->dev); + platform_set_drvdata(pdev, dev); + ether->ndev = dev; + + ether->pdev = pdev; + ether->msg_enable = NETIF_MSG_LINK; + + dev->netdev_ops = &npcm7xx_ether_netdev_ops; + dev->ethtool_ops = &npcm7xx_ether_ethtool_ops; + + dev->tx_queue_len = TX_QUEUE_LEN; + dev->dma = 0x0; + dev->watchdog_timeo = TX_TIMEOUT; + + get_mac_address(dev); + + ether->cur_tx = 0x0; + ether->cur_rx = 0x0; + ether->finish_tx = 0x0; + ether->pending_tx = 0x0; + ether->link = 0; + ether->speed = 100; + ether->duplex = DUPLEX_FULL; + ether->need_reset = 0; + ether->dump_buf = NULL; + ether->rx_berr = 0; + ether->rx_err = 0; + ether->rdu = 0; + ether->rxov = 0; + ether->rx_stuck = 0; + /* debug counters */ + ether->max_waiting_rx = 0; + ether->rx_count_pool = 0; + ether->count_xmit = 0; + ether->rx_int_count = 0; + ether->rx_err_count = 0; + ether->tx_int_count = 0; + ether->count_finish = 0; + ether->tx_tdu = 0; + ether->tx_tdu_i = 0; + ether->tx_cp_i = 0; + + spin_lock_init(ðer->lock); + + netif_napi_add(dev, ðer->napi, npcm7xx_poll, RX_POLL_SIZE); + + if (pdev->dev.of_node && + of_get_property(pdev->dev.of_node, "use-ncsi", NULL)) { + if (!IS_ENABLED(CONFIG_NET_NCSI)) { + dev_err(&pdev->dev, "CONFIG_NET_NCSI not enabled\n"); + error = -ENODEV; + goto failed_free_napi; + } + dev_info(&pdev->dev, "Using NCSI interface\n"); + ether->use_ncsi = true; + ether->ncsidev = ncsi_register_dev(dev, npcm7xx_ncsi_handler); + if (!ether->ncsidev) { + error = -ENODEV; + goto failed_free_napi; + } + } else { + ether->use_ncsi = false; + error = npcm7xx_mii_setup(dev); + if (error < 0) { + dev_err(&pdev->dev, "npcm7xx_mii_setup err\n"); + goto failed_free_napi; + } + } + + error = register_netdev(dev); + if (error != 0) { + dev_err(&pdev->dev, "register_netdev() failed\n"); + error = -ENODEV; + goto failed_free_napi; + } + +#ifdef CONFIG_DEBUG_FS + npcm7xx_debug_fs(ether); +#endif + + return 0; + +failed_free_napi: + netif_napi_del(ðer->napi); + platform_set_drvdata(pdev, NULL); +failed_free_io: + iounmap(ether->reg); +failed_free_mem: + release_mem_region(ether->res->start, resource_size(ether->res)); +failed_free: + free_netdev(dev); + + return error; +} + +static int npcm7xx_ether_remove(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct npcm7xx_ether *ether = netdev_priv(dev); + +#ifdef CONFIG_DEBUG_FS + debugfs_remove_recursive(ether->dbgfs_dir); +#endif + + unregister_netdev(dev); + + free_irq(ether->txirq, dev); + free_irq(ether->rxirq, dev); + + if (ether->phy_dev) + phy_disconnect(ether->phy_dev); + + mdiobus_unregister(ether->mii_bus); + kfree(ether->mii_bus->irq); + mdiobus_free(ether->mii_bus); + + platform_set_drvdata(pdev, NULL); + + free_netdev(dev); + return 0; +} + +static struct platform_driver npcm7xx_ether_driver = { + .probe = npcm7xx_ether_probe, + .remove = npcm7xx_ether_remove, + .driver = { + .name = DRV_MODULE_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(emc_dt_id), + }, +}; + +module_platform_driver(npcm7xx_ether_driver); + +MODULE_AUTHOR("Nuvoton Technology Corp."); +MODULE_DESCRIPTION("NPCM750 EMC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:npcm750-emc"); +MODULE_VERSION(DRV_MODULE_VERSION); diff --git a/drivers/peci/Kconfig b/drivers/peci/Kconfig new file mode 100644 index 000000000000..a64fed7bb367 --- /dev/null +++ b/drivers/peci/Kconfig @@ -0,0 +1,37 @@ +# +# Platform Environment Control Interface (PECI) subsystem configuration +# + +menu "PECI support" + +config PECI + tristate "PECI support" + select CRC8 + help + The Platform Environment Control Interface (PECI) is a one-wire bus + interface that provides a communication channel from Intel processors + and chipset components to external monitoring or control devices. + + If you want PECI support, you should say Y here and also to the + specific driver for your bus adapter(s) below. + + This support is also available as a module. If so, the module + will be called peci-core. + +if PECI + +config PECI_CHARDEV + tristate "PECI device interface" + help + Say Y here to use peci-* device files, usually found in the /dev + directory on your system. They make it possible to have user-space + programs use the PECI bus. + + This support is also available as a module. If so, the module + will be called peci-dev. + +source "drivers/peci/busses/Kconfig" + +endif # PECI + +endmenu diff --git a/drivers/peci/Makefile b/drivers/peci/Makefile new file mode 100644 index 000000000000..da8b0a33fa42 --- /dev/null +++ b/drivers/peci/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the PECI core drivers. +# + +# Core functionality +obj-$(CONFIG_PECI) += peci-core.o +obj-$(CONFIG_PECI_CHARDEV) += peci-dev.o + +# Hardware specific bus drivers +obj-y += busses/ diff --git a/drivers/peci/busses/Kconfig b/drivers/peci/busses/Kconfig new file mode 100644 index 000000000000..4316234db67c --- /dev/null +++ b/drivers/peci/busses/Kconfig @@ -0,0 +1,34 @@ +# +# PECI hardware bus configuration +# + +menu "PECI Hardware Bus support" + +config PECI_ASPEED + tristate "ASPEED PECI support" + depends on ARCH_ASPEED || COMPILE_TEST + depends on OF + depends on HAS_IOMEM + depends on PECI + help + Say Y here if you want support for the Platform Environment Control + Interface (PECI) bus adapter driver on the ASPEED SoCs. + + This support is also available as a module. If so, the module + will be called peci-aspeed. + +config PECI_NPCM + tristate "Nuvoton NPCM PECI support" + select REGMAP_MMIO + depends on OF + depends on HAS_IOMEM + depends on ARCH_NPCM || COMPILE_TEST + depends on PECI + help + Say Y here if you want support for the Platform Environment Control + Interface (PECI) bus adapter driver on the Nuvoton NPCM SoCs. + + This support is also available as a module. If so, the module + will be called peci-npcm. + +endmenu diff --git a/drivers/peci/busses/Makefile b/drivers/peci/busses/Makefile new file mode 100644 index 000000000000..aa8ce3ae5947 --- /dev/null +++ b/drivers/peci/busses/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the PECI hardware bus drivers. +# + +obj-$(CONFIG_PECI_ASPEED) += peci-aspeed.o +obj-$(CONFIG_PECI_NPCM) += peci-npcm.o diff --git a/drivers/peci/busses/peci-aspeed.c b/drivers/peci/busses/peci-aspeed.c new file mode 100644 index 000000000000..2673d4c4dcf9 --- /dev/null +++ b/drivers/peci/busses/peci-aspeed.c @@ -0,0 +1,484 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2012-2017 ASPEED Technology Inc. +// Copyright (c) 2018-2019 Intel Corporation + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/peci.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +/* ASPEED PECI Registers */ +/* Control Register */ +#define ASPEED_PECI_CTRL 0x00 +#define ASPEED_PECI_CTRL_SAMPLING_MASK GENMASK(19, 16) +#define ASPEED_PECI_CTRL_READ_MODE_MASK GENMASK(13, 12) +#define ASPEED_PECI_CTRL_READ_MODE_COUNT BIT(12) +#define ASPEED_PECI_CTRL_READ_MODE_DBG BIT(13) +#define ASPEED_PECI_CTRL_CLK_SOURCE_MASK BIT(11) +#define ASPEED_PECI_CTRL_CLK_DIV_MASK GENMASK(10, 8) +#define ASPEED_PECI_CTRL_INVERT_OUT BIT(7) +#define ASPEED_PECI_CTRL_INVERT_IN BIT(6) +#define ASPEED_PECI_CTRL_BUS_CONTENT_EN BIT(5) +#define ASPEED_PECI_CTRL_PECI_EN BIT(4) +#define ASPEED_PECI_CTRL_PECI_CLK_EN BIT(0) + +/* Timing Negotiation Register */ +#define ASPEED_PECI_TIMING_NEGOTIATION 0x04 +#define ASPEED_PECI_TIMING_MESSAGE_MASK GENMASK(15, 8) +#define ASPEED_PECI_TIMING_ADDRESS_MASK GENMASK(7, 0) + +/* Command Register */ +#define ASPEED_PECI_CMD 0x08 +#define ASPEED_PECI_CMD_PIN_MON BIT(31) +#define ASPEED_PECI_CMD_STS_MASK GENMASK(27, 24) +#define ASPEED_PECI_CMD_IDLE_MASK \ + (ASPEED_PECI_CMD_STS_MASK | ASPEED_PECI_CMD_PIN_MON) +#define ASPEED_PECI_CMD_FIRE BIT(0) + +/* Read/Write Length Register */ +#define ASPEED_PECI_RW_LENGTH 0x0c +#define ASPEED_PECI_AW_FCS_EN BIT(31) +#define ASPEED_PECI_READ_LEN_MASK GENMASK(23, 16) +#define ASPEED_PECI_WRITE_LEN_MASK GENMASK(15, 8) +#define ASPEED_PECI_TAGET_ADDR_MASK GENMASK(7, 0) + +/* Expected FCS Data Register */ +#define ASPEED_PECI_EXP_FCS 0x10 +#define ASPEED_PECI_EXP_READ_FCS_MASK GENMASK(23, 16) +#define ASPEED_PECI_EXP_AW_FCS_AUTO_MASK GENMASK(15, 8) +#define ASPEED_PECI_EXP_WRITE_FCS_MASK GENMASK(7, 0) + +/* Captured FCS Data Register */ +#define ASPEED_PECI_CAP_FCS 0x14 +#define ASPEED_PECI_CAP_READ_FCS_MASK GENMASK(23, 16) +#define ASPEED_PECI_CAP_WRITE_FCS_MASK GENMASK(7, 0) + +/* Interrupt Register */ +#define ASPEED_PECI_INT_CTRL 0x18 +#define ASPEED_PECI_TIMING_NEGO_SEL_MASK GENMASK(31, 30) +#define ASPEED_PECI_1ST_BIT_OF_ADDR_NEGO 0 +#define ASPEED_PECI_2ND_BIT_OF_ADDR_NEGO 1 +#define ASPEED_PECI_MESSAGE_NEGO 2 +#define ASPEED_PECI_INT_MASK GENMASK(4, 0) +#define ASPEED_PECI_INT_BUS_TIMEOUT BIT(4) +#define ASPEED_PECI_INT_BUS_CONNECT BIT(3) +#define ASPEED_PECI_INT_W_FCS_BAD BIT(2) +#define ASPEED_PECI_INT_W_FCS_ABORT BIT(1) +#define ASPEED_PECI_INT_CMD_DONE BIT(0) + +/* Interrupt Status Register */ +#define ASPEED_PECI_INT_STS 0x1c +#define ASPEED_PECI_INT_TIMING_RESULT_MASK GENMASK(29, 16) + /* bits[4..0]: Same bit fields in the 'Interrupt Register' */ + +/* Rx/Tx Data Buffer Registers */ +#define ASPEED_PECI_W_DATA0 0x20 +#define ASPEED_PECI_W_DATA1 0x24 +#define ASPEED_PECI_W_DATA2 0x28 +#define ASPEED_PECI_W_DATA3 0x2c +#define ASPEED_PECI_R_DATA0 0x30 +#define ASPEED_PECI_R_DATA1 0x34 +#define ASPEED_PECI_R_DATA2 0x38 +#define ASPEED_PECI_R_DATA3 0x3c +#define ASPEED_PECI_W_DATA4 0x40 +#define ASPEED_PECI_W_DATA5 0x44 +#define ASPEED_PECI_W_DATA6 0x48 +#define ASPEED_PECI_W_DATA7 0x4c +#define ASPEED_PECI_R_DATA4 0x50 +#define ASPEED_PECI_R_DATA5 0x54 +#define ASPEED_PECI_R_DATA6 0x58 +#define ASPEED_PECI_R_DATA7 0x5c +#define ASPEED_PECI_DATA_BUF_SIZE_MAX 32 + +/* Timing Negotiation */ +#define ASPEED_PECI_RD_SAMPLING_POINT_DEFAULT 8 +#define ASPEED_PECI_RD_SAMPLING_POINT_MAX 15 +#define ASPEED_PECI_CLK_DIV_DEFAULT 0 +#define ASPEED_PECI_CLK_DIV_MAX 7 +#define ASPEED_PECI_MSG_TIMING_DEFAULT 1 +#define ASPEED_PECI_MSG_TIMING_MAX 255 +#define ASPEED_PECI_ADDR_TIMING_DEFAULT 1 +#define ASPEED_PECI_ADDR_TIMING_MAX 255 + +/* Timeout */ +#define ASPEED_PECI_IDLE_CHECK_TIMEOUT_USEC 50000 +#define ASPEED_PECI_IDLE_CHECK_INTERVAL_USEC 10000 +#define ASPEED_PECI_CMD_TIMEOUT_MS_DEFAULT 1000 +#define ASPEED_PECI_CMD_TIMEOUT_MS_MAX 60000 + +struct aspeed_peci { + struct peci_adapter *adapter; + struct device *dev; + void __iomem *base; + struct clk *clk; + struct reset_control *rst; + int irq; + spinlock_t lock; /* to sync completion status handling */ + struct completion xfer_complete; + u32 status; + u32 cmd_timeout_ms; +}; + +static inline int aspeed_peci_check_idle(struct aspeed_peci *priv) +{ + u32 cmd_sts; + + return readl_poll_timeout(priv->base + ASPEED_PECI_CMD, + cmd_sts, + !(cmd_sts & ASPEED_PECI_CMD_IDLE_MASK), + ASPEED_PECI_IDLE_CHECK_INTERVAL_USEC, + ASPEED_PECI_IDLE_CHECK_TIMEOUT_USEC); +} + +static int aspeed_peci_xfer(struct peci_adapter *adapter, + struct peci_xfer_msg *msg) +{ + struct aspeed_peci *priv = peci_get_adapdata(adapter); + long err, timeout = msecs_to_jiffies(priv->cmd_timeout_ms); + u32 peci_head, peci_state, rx_data = 0; + ulong flags; + int i, ret; + uint reg; + + if (msg->tx_len > ASPEED_PECI_DATA_BUF_SIZE_MAX || + msg->rx_len > ASPEED_PECI_DATA_BUF_SIZE_MAX) + return -EINVAL; + + /* Check command sts and bus idle state */ + ret = aspeed_peci_check_idle(priv); + if (ret) + return ret; /* -ETIMEDOUT */ + + spin_lock_irqsave(&priv->lock, flags); + reinit_completion(&priv->xfer_complete); + + peci_head = FIELD_PREP(ASPEED_PECI_TAGET_ADDR_MASK, msg->addr) | + FIELD_PREP(ASPEED_PECI_WRITE_LEN_MASK, msg->tx_len) | + FIELD_PREP(ASPEED_PECI_READ_LEN_MASK, msg->rx_len); + + writel(peci_head, priv->base + ASPEED_PECI_RW_LENGTH); + + for (i = 0; i < msg->tx_len; i += 4) { + reg = i < 16 ? ASPEED_PECI_W_DATA0 + i % 16 : + ASPEED_PECI_W_DATA4 + i % 16; + writel(le32_to_cpup((__le32 *)&msg->tx_buf[i]), + priv->base + reg); + } + + dev_dbg(priv->dev, "HEAD : 0x%08x\n", peci_head); + print_hex_dump_debug("TX : ", DUMP_PREFIX_NONE, 16, 1, + msg->tx_buf, msg->tx_len, true); + + priv->status = 0; + writel(ASPEED_PECI_CMD_FIRE, priv->base + ASPEED_PECI_CMD); + spin_unlock_irqrestore(&priv->lock, flags); + + err = wait_for_completion_interruptible_timeout(&priv->xfer_complete, + timeout); + + spin_lock_irqsave(&priv->lock, flags); + dev_dbg(priv->dev, "INT_STS : 0x%08x\n", priv->status); + peci_state = readl(priv->base + ASPEED_PECI_CMD); + dev_dbg(priv->dev, "PECI_STATE : 0x%lx\n", + FIELD_GET(ASPEED_PECI_CMD_STS_MASK, peci_state)); + + writel(0, priv->base + ASPEED_PECI_CMD); + + if (err <= 0 || priv->status != ASPEED_PECI_INT_CMD_DONE) { + if (err < 0) { /* -ERESTARTSYS */ + ret = (int)err; + goto err_irqrestore; + } else if (err == 0) { + dev_dbg(priv->dev, "Timeout waiting for a response!\n"); + ret = -ETIMEDOUT; + goto err_irqrestore; + } + + dev_dbg(priv->dev, "No valid response!\n"); + ret = -EIO; + goto err_irqrestore; + } + + /* + * Note that rx_len and rx_buf size can be an odd number. + * Byte handling is more efficient. + */ + for (i = 0; i < msg->rx_len; i++) { + u8 byte_offset = i % 4; + + if (byte_offset == 0) { + reg = i < 16 ? ASPEED_PECI_R_DATA0 + i % 16 : + ASPEED_PECI_R_DATA4 + i % 16; + rx_data = readl(priv->base + reg); + } + + msg->rx_buf[i] = (u8)(rx_data >> (byte_offset << 3)); + } + + print_hex_dump_debug("RX : ", DUMP_PREFIX_NONE, 16, 1, + msg->rx_buf, msg->rx_len, true); + + peci_state = readl(priv->base + ASPEED_PECI_CMD); + dev_dbg(priv->dev, "PECI_STATE : 0x%lx\n", + FIELD_GET(ASPEED_PECI_CMD_STS_MASK, peci_state)); + dev_dbg(priv->dev, "------------------------\n"); + +err_irqrestore: + spin_unlock_irqrestore(&priv->lock, flags); + + return ret; +} + +static irqreturn_t aspeed_peci_irq_handler(int irq, void *arg) +{ + struct aspeed_peci *priv = arg; + u32 status; + + spin_lock(&priv->lock); + status = readl(priv->base + ASPEED_PECI_INT_STS); + writel(status, priv->base + ASPEED_PECI_INT_STS); + priv->status |= (status & ASPEED_PECI_INT_MASK); + + /* + * In most cases, interrupt bits will be set one by one but also note + * that multiple interrupt bits could be set at the same time. + */ + if (status & ASPEED_PECI_INT_BUS_TIMEOUT) + dev_dbg(priv->dev, "ASPEED_PECI_INT_BUS_TIMEOUT\n"); + + if (status & ASPEED_PECI_INT_BUS_CONNECT) + dev_dbg(priv->dev, "ASPEED_PECI_INT_BUS_CONNECT\n"); + + if (status & ASPEED_PECI_INT_W_FCS_BAD) + dev_dbg(priv->dev, "ASPEED_PECI_INT_W_FCS_BAD\n"); + + if (status & ASPEED_PECI_INT_W_FCS_ABORT) + dev_dbg(priv->dev, "ASPEED_PECI_INT_W_FCS_ABORT\n"); + + /* + * All commands should be ended up with a ASPEED_PECI_INT_CMD_DONE bit + * set even in an error case. + */ + if (status & ASPEED_PECI_INT_CMD_DONE) { + dev_dbg(priv->dev, "ASPEED_PECI_INT_CMD_DONE\n"); + complete(&priv->xfer_complete); + } + + spin_unlock(&priv->lock); + + return IRQ_HANDLED; +} + +static int aspeed_peci_init_ctrl(struct aspeed_peci *priv) +{ + u32 msg_timing, addr_timing, rd_sampling_point; + u32 clk_freq, clk_divisor, clk_div_val = 0; + int ret; + + priv->clk = devm_clk_get(priv->dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(priv->dev, "Failed to get clk source.\n"); + return PTR_ERR(priv->clk); + } + + ret = clk_prepare_enable(priv->clk); + if (ret) { + dev_err(priv->dev, "Failed to enable clock.\n"); + return ret; + } + + ret = device_property_read_u32(priv->dev, "clock-frequency", &clk_freq); + if (ret) { + dev_err(priv->dev, + "Could not read clock-frequency property.\n"); + clk_disable_unprepare(priv->clk); + return ret; + } + + clk_divisor = clk_get_rate(priv->clk) / clk_freq; + + while ((clk_divisor >> 1) && (clk_div_val < ASPEED_PECI_CLK_DIV_MAX)) + clk_div_val++; + + ret = device_property_read_u32(priv->dev, "msg-timing", &msg_timing); + if (ret || msg_timing > ASPEED_PECI_MSG_TIMING_MAX) { + if (!ret) + dev_warn(priv->dev, + "Invalid msg-timing : %u, Use default : %u\n", + msg_timing, ASPEED_PECI_MSG_TIMING_DEFAULT); + msg_timing = ASPEED_PECI_MSG_TIMING_DEFAULT; + } + + ret = device_property_read_u32(priv->dev, "addr-timing", &addr_timing); + if (ret || addr_timing > ASPEED_PECI_ADDR_TIMING_MAX) { + if (!ret) + dev_warn(priv->dev, + "Invalid addr-timing : %u, Use default : %u\n", + addr_timing, ASPEED_PECI_ADDR_TIMING_DEFAULT); + addr_timing = ASPEED_PECI_ADDR_TIMING_DEFAULT; + } + + ret = device_property_read_u32(priv->dev, "rd-sampling-point", + &rd_sampling_point); + if (ret || rd_sampling_point > ASPEED_PECI_RD_SAMPLING_POINT_MAX) { + if (!ret) + dev_warn(priv->dev, + "Invalid rd-sampling-point : %u. Use default : %u\n", + rd_sampling_point, + ASPEED_PECI_RD_SAMPLING_POINT_DEFAULT); + rd_sampling_point = ASPEED_PECI_RD_SAMPLING_POINT_DEFAULT; + } + + ret = device_property_read_u32(priv->dev, "cmd-timeout-ms", + &priv->cmd_timeout_ms); + if (ret || priv->cmd_timeout_ms > ASPEED_PECI_CMD_TIMEOUT_MS_MAX || + priv->cmd_timeout_ms == 0) { + if (!ret) + dev_warn(priv->dev, + "Invalid cmd-timeout-ms : %u. Use default : %u\n", + priv->cmd_timeout_ms, + ASPEED_PECI_CMD_TIMEOUT_MS_DEFAULT); + priv->cmd_timeout_ms = ASPEED_PECI_CMD_TIMEOUT_MS_DEFAULT; + } + + writel(FIELD_PREP(ASPEED_PECI_CTRL_CLK_DIV_MASK, + ASPEED_PECI_CLK_DIV_DEFAULT) | + ASPEED_PECI_CTRL_PECI_CLK_EN, priv->base + ASPEED_PECI_CTRL); + + /* + * Timing negotiation period setting. + * The unit of the programmed value is 4 times of PECI clock period. + */ + writel(FIELD_PREP(ASPEED_PECI_TIMING_MESSAGE_MASK, msg_timing) | + FIELD_PREP(ASPEED_PECI_TIMING_ADDRESS_MASK, addr_timing), + priv->base + ASPEED_PECI_TIMING_NEGOTIATION); + + /* Clear interrupts */ + writel(readl(priv->base + ASPEED_PECI_INT_STS) | ASPEED_PECI_INT_MASK, + priv->base + ASPEED_PECI_INT_STS); + + /* Set timing negotiation mode and enable interrupts */ + writel(FIELD_PREP(ASPEED_PECI_TIMING_NEGO_SEL_MASK, + ASPEED_PECI_1ST_BIT_OF_ADDR_NEGO) | + ASPEED_PECI_INT_MASK, priv->base + ASPEED_PECI_INT_CTRL); + + /* Read sampling point and clock speed setting */ + writel(FIELD_PREP(ASPEED_PECI_CTRL_SAMPLING_MASK, rd_sampling_point) | + FIELD_PREP(ASPEED_PECI_CTRL_CLK_DIV_MASK, clk_div_val) | + ASPEED_PECI_CTRL_PECI_EN | ASPEED_PECI_CTRL_PECI_CLK_EN, + priv->base + ASPEED_PECI_CTRL); + + return 0; +} + +static int aspeed_peci_probe(struct platform_device *pdev) +{ + struct peci_adapter *adapter; + struct aspeed_peci *priv; + int ret; + + adapter = peci_alloc_adapter(&pdev->dev, sizeof(*priv)); + if (!adapter) + return -ENOMEM; + + priv = peci_get_adapdata(adapter); + priv->adapter = adapter; + priv->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, priv); + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) { + ret = PTR_ERR(priv->base); + goto err_put_adapter_dev; + } + + priv->irq = platform_get_irq(pdev, 0); + if (!priv->irq) { + ret = -ENODEV; + goto err_put_adapter_dev; + } + + ret = devm_request_irq(&pdev->dev, priv->irq, aspeed_peci_irq_handler, + 0, "peci-aspeed-irq", priv); + if (ret) + goto err_put_adapter_dev; + + init_completion(&priv->xfer_complete); + spin_lock_init(&priv->lock); + + priv->adapter->owner = THIS_MODULE; + priv->adapter->dev.of_node = of_node_get(dev_of_node(priv->dev)); + strlcpy(priv->adapter->name, pdev->name, sizeof(priv->adapter->name)); + priv->adapter->xfer = aspeed_peci_xfer; + priv->adapter->use_dma = false; + + priv->rst = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(priv->rst)) { + dev_err(&pdev->dev, + "missing or invalid reset controller entry\n"); + ret = PTR_ERR(priv->rst); + goto err_put_adapter_dev; + } + reset_control_deassert(priv->rst); + + ret = aspeed_peci_init_ctrl(priv); + if (ret) + goto err_put_adapter_dev; + + ret = peci_add_adapter(priv->adapter); + if (ret) + goto err_put_adapter_dev; + + dev_info(&pdev->dev, "peci bus %d registered, irq %d\n", + priv->adapter->nr, priv->irq); + + return 0; + +err_put_adapter_dev: + put_device(&adapter->dev); + + return ret; +} + +static int aspeed_peci_remove(struct platform_device *pdev) +{ + struct aspeed_peci *priv = dev_get_drvdata(&pdev->dev); + + clk_disable_unprepare(priv->clk); + reset_control_assert(priv->rst); + peci_del_adapter(priv->adapter); + of_node_put(priv->adapter->dev.of_node); + + return 0; +} + +static const struct of_device_id aspeed_peci_of_table[] = { + { .compatible = "aspeed,ast2400-peci", }, + { .compatible = "aspeed,ast2500-peci", }, + { .compatible = "aspeed,ast2600-peci", }, + { } +}; +MODULE_DEVICE_TABLE(of, aspeed_peci_of_table); + +static struct platform_driver aspeed_peci_driver = { + .probe = aspeed_peci_probe, + .remove = aspeed_peci_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = of_match_ptr(aspeed_peci_of_table), + }, +}; +module_platform_driver(aspeed_peci_driver); + +MODULE_AUTHOR("Ryan Chen <ryan_chen@aspeedtech.com>"); +MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>"); +MODULE_DESCRIPTION("ASPEED PECI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/peci/busses/peci-npcm.c b/drivers/peci/busses/peci-npcm.c new file mode 100644 index 000000000000..bdebbf1ec7f1 --- /dev/null +++ b/drivers/peci/busses/peci-npcm.c @@ -0,0 +1,406 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019 Nuvoton Technology corporation. + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/peci.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> +#include <linux/reset.h> + +/* NPCM7xx GCR module */ +#define NPCM7XX_INTCR3_OFFSET 0x9C +#define NPCM7XX_INTCR3_PECIVSEL BIT(19) + +/* NPCM PECI Registers */ +#define NPCM_PECI_CTL_STS 0x00 +#define NPCM_PECI_RD_LENGTH 0x04 +#define NPCM_PECI_ADDR 0x08 +#define NPCM_PECI_CMD 0x0C +#define NPCM_PECI_CTL2 0x10 +#define NPCM_PECI_WR_LENGTH 0x1C +#define NPCM_PECI_PDDR 0x2C +#define NPCM_PECI_DAT_INOUT(n) (0x100 + ((n) * 4)) + +#define NPCM_PECI_MAX_REG 0x200 + +/* NPCM_PECI_CTL_STS - 0x00 : Control Register */ +#define NPCM_PECI_CTRL_DONE_INT_EN BIT(6) +#define NPCM_PECI_CTRL_ABRT_ERR BIT(4) +#define NPCM_PECI_CTRL_CRC_ERR BIT(3) +#define NPCM_PECI_CTRL_DONE BIT(1) +#define NPCM_PECI_CTRL_START_BUSY BIT(0) + +/* NPCM_PECI_RD_LENGTH - 0x04 : Command Register */ +#define NPCM_PECI_RD_LEN_MASK GENMASK(6, 0) + +/* NPCM_PECI_CMD - 0x10 : Command Register */ +#define NPCM_PECI_CTL2_MASK GENMASK(7, 6) + +/* NPCM_PECI_WR_LENGTH - 0x1C : Command Register */ +#define NPCM_PECI_WR_LEN_MASK GENMASK(6, 0) + +/* NPCM_PECI_PDDR - 0x2C : Command Register */ +#define NPCM_PECI_PDDR_MASK GENMASK(4, 0) + +#define NPCM_PECI_INT_MASK \ + (NPCM_PECI_CTRL_ABRT_ERR | NPCM_PECI_CTRL_CRC_ERR | NPCM_PECI_CTRL_DONE) + +#define NPCM_PECI_IDLE_CHECK_TIMEOUT_USEC 50000 +#define NPCM_PECI_IDLE_CHECK_INTERVAL_USEC 10000 +#define NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT 1000 +#define NPCM_PECI_CMD_TIMEOUT_MS_MAX 60000 +#define NPCM_PECI_HOST_NEG_BIT_RATE_MAX 31 +#define NPCM_PECI_HOST_NEG_BIT_RATE_MIN 7 +#define NPCM_PECI_HOST_NEG_BIT_RATE_DEFAULT 15 +#define NPCM_PECI_PULL_DOWN_DEFAULT 0 +#define NPCM_PECI_PULL_DOWN_MAX 2 + +struct npcm_peci { + u32 cmd_timeout_ms; + u32 host_bit_rate; + struct completion xfer_complete; + struct regmap *gcr_regmap; + struct peci_adapter *adapter; + struct regmap *regmap; + u32 status; + spinlock_t lock; /* to sync completion status handling */ + struct device *dev; + struct clk *clk; + int irq; +}; + +static int npcm_peci_xfer_native(struct npcm_peci *priv, + struct peci_xfer_msg *msg) +{ + long err, timeout = msecs_to_jiffies(priv->cmd_timeout_ms); + unsigned long flags; + unsigned int msg_rd; + u32 cmd_sts; + int i, rc; + + /* Check command sts and bus idle state */ + rc = regmap_read_poll_timeout(priv->regmap, NPCM_PECI_CTL_STS, cmd_sts, + !(cmd_sts & NPCM_PECI_CTRL_START_BUSY), + NPCM_PECI_IDLE_CHECK_INTERVAL_USEC, + NPCM_PECI_IDLE_CHECK_TIMEOUT_USEC); + if (rc) + return rc; /* -ETIMEDOUT */ + + spin_lock_irqsave(&priv->lock, flags); + reinit_completion(&priv->xfer_complete); + + regmap_write(priv->regmap, NPCM_PECI_ADDR, msg->addr); + regmap_write(priv->regmap, NPCM_PECI_RD_LENGTH, + NPCM_PECI_WR_LEN_MASK & msg->rx_len); + regmap_write(priv->regmap, NPCM_PECI_WR_LENGTH, + NPCM_PECI_WR_LEN_MASK & msg->tx_len); + + if (msg->tx_len) { + regmap_write(priv->regmap, NPCM_PECI_CMD, msg->tx_buf[0]); + + for (i = 0; i < (msg->tx_len - 1); i++) + regmap_write(priv->regmap, NPCM_PECI_DAT_INOUT(i), + msg->tx_buf[i + 1]); + } + + priv->status = 0; + regmap_update_bits(priv->regmap, NPCM_PECI_CTL_STS, + NPCM_PECI_CTRL_START_BUSY, + NPCM_PECI_CTRL_START_BUSY); + + spin_unlock_irqrestore(&priv->lock, flags); + + err = wait_for_completion_interruptible_timeout(&priv->xfer_complete, + timeout); + + spin_lock_irqsave(&priv->lock, flags); + + regmap_write(priv->regmap, NPCM_PECI_CMD, 0); + + if (err <= 0 || priv->status != NPCM_PECI_CTRL_DONE) { + if (err < 0) { /* -ERESTARTSYS */ + rc = (int)err; + goto err_irqrestore; + } else if (err == 0) { + dev_dbg(priv->dev, "Timeout waiting for a response!\n"); + rc = -ETIMEDOUT; + goto err_irqrestore; + } + + dev_dbg(priv->dev, "No valid response!\n"); + rc = -EIO; + goto err_irqrestore; + } + + for (i = 0; i < msg->rx_len; i++) { + regmap_read(priv->regmap, NPCM_PECI_DAT_INOUT(i), &msg_rd); + msg->rx_buf[i] = (u8)msg_rd; + } + +err_irqrestore: + spin_unlock_irqrestore(&priv->lock, flags); + return rc; +} + +static irqreturn_t npcm_peci_irq_handler(int irq, void *arg) +{ + struct npcm_peci *priv = arg; + u32 status_ack = 0; + u32 status; + + spin_lock(&priv->lock); + regmap_read(priv->regmap, NPCM_PECI_CTL_STS, &status); + priv->status |= (status & NPCM_PECI_INT_MASK); + + if (status & NPCM_PECI_CTRL_CRC_ERR) { + dev_dbg(priv->dev, "PECI_INT_W_FCS_BAD\n"); + status_ack |= NPCM_PECI_CTRL_CRC_ERR; + } + + if (status & NPCM_PECI_CTRL_ABRT_ERR) { + dev_dbg(priv->dev, "NPCM_PECI_CTRL_ABRT_ERR\n"); + status_ack |= NPCM_PECI_CTRL_ABRT_ERR; + } + + /* + * All commands should be ended up with a NPCM_PECI_CTRL_DONE + * bit set even in an error case. + */ + if (status & NPCM_PECI_CTRL_DONE) { + dev_dbg(priv->dev, "NPCM_PECI_CTRL_DONE\n"); + status_ack |= NPCM_PECI_CTRL_DONE; + complete(&priv->xfer_complete); + } + + regmap_write_bits(priv->regmap, NPCM_PECI_CTL_STS, + NPCM_PECI_INT_MASK, status_ack); + + spin_unlock(&priv->lock); + return IRQ_HANDLED; +} + +static int npcm_peci_init_ctrl(struct npcm_peci *priv) +{ + u32 cmd_sts, host_neg_bit_rate = 0, pull_down = 0; + int ret; + + priv->clk = devm_clk_get(priv->dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(priv->dev, "Failed to get clk source.\n"); + return PTR_ERR(priv->clk); + } + + ret = clk_prepare_enable(priv->clk); + if (ret) { + dev_err(priv->dev, "Failed to enable clock.\n"); + return ret; + } + + ret = of_property_read_u32(priv->dev->of_node, "cmd-timeout-ms", + &priv->cmd_timeout_ms); + if (ret || priv->cmd_timeout_ms > NPCM_PECI_CMD_TIMEOUT_MS_MAX || + priv->cmd_timeout_ms == 0) { + if (ret) + dev_warn(priv->dev, + "cmd-timeout-ms not found, use default : %u\n", + NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT); + else + dev_warn(priv->dev, + "Invalid cmd-timeout-ms : %u. Use default : %u\n", + priv->cmd_timeout_ms, + NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT); + + priv->cmd_timeout_ms = NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT; + } + + if (of_device_is_compatible(priv->dev->of_node, + "nuvoton,npcm750-peci")) { + priv->gcr_regmap = syscon_regmap_lookup_by_compatible + ("nuvoton,npcm750-gcr"); + if (!IS_ERR(priv->gcr_regmap)) { + bool volt = of_property_read_bool(priv->dev->of_node, + "high-volt-range"); + if (volt) + regmap_update_bits(priv->gcr_regmap, + NPCM7XX_INTCR3_OFFSET, + NPCM7XX_INTCR3_PECIVSEL, + NPCM7XX_INTCR3_PECIVSEL); + else + regmap_update_bits(priv->gcr_regmap, + NPCM7XX_INTCR3_OFFSET, + NPCM7XX_INTCR3_PECIVSEL, 0); + } + } + + ret = of_property_read_u32(priv->dev->of_node, "pull-down", + &pull_down); + if (ret || pull_down > NPCM_PECI_PULL_DOWN_MAX) { + if (ret) + dev_warn(priv->dev, + "pull-down not found, use default : %u\n", + NPCM_PECI_PULL_DOWN_DEFAULT); + else + dev_warn(priv->dev, + "Invalid pull-down : %u. Use default : %u\n", + pull_down, + NPCM_PECI_PULL_DOWN_DEFAULT); + pull_down = NPCM_PECI_PULL_DOWN_DEFAULT; + } + + regmap_update_bits(priv->regmap, NPCM_PECI_CTL2, NPCM_PECI_CTL2_MASK, + pull_down << 6); + + ret = of_property_read_u32(priv->dev->of_node, "host-neg-bit-rate", + &host_neg_bit_rate); + if (ret || host_neg_bit_rate > NPCM_PECI_HOST_NEG_BIT_RATE_MAX || + host_neg_bit_rate < NPCM_PECI_HOST_NEG_BIT_RATE_MIN) { + if (ret) + dev_warn(priv->dev, + "host-neg-bit-rate not found, use default : %u\n", + NPCM_PECI_HOST_NEG_BIT_RATE_DEFAULT); + else + dev_warn(priv->dev, + "Invalid host-neg-bit-rate : %u. Use default : %u\n", + host_neg_bit_rate, + NPCM_PECI_HOST_NEG_BIT_RATE_DEFAULT); + host_neg_bit_rate = NPCM_PECI_HOST_NEG_BIT_RATE_DEFAULT; + } + + regmap_update_bits(priv->regmap, NPCM_PECI_PDDR, NPCM_PECI_PDDR_MASK, + host_neg_bit_rate); + + priv->host_bit_rate = clk_get_rate(priv->clk) / + (4 * (host_neg_bit_rate + 1)); + + ret = regmap_read_poll_timeout(priv->regmap, NPCM_PECI_CTL_STS, cmd_sts, + !(cmd_sts & NPCM_PECI_CTRL_START_BUSY), + NPCM_PECI_IDLE_CHECK_INTERVAL_USEC, + NPCM_PECI_IDLE_CHECK_TIMEOUT_USEC); + if (ret) + return ret; /* -ETIMEDOUT */ + + /* PECI interrupt enable */ + regmap_update_bits(priv->regmap, NPCM_PECI_CTL_STS, + NPCM_PECI_CTRL_DONE_INT_EN, + NPCM_PECI_CTRL_DONE_INT_EN); + + return 0; +} + +static const struct regmap_config npcm_peci_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = NPCM_PECI_MAX_REG, + .fast_io = true, +}; + +static int npcm_peci_xfer(struct peci_adapter *adapter, + struct peci_xfer_msg *msg) +{ + struct npcm_peci *priv = peci_get_adapdata(adapter); + + return npcm_peci_xfer_native(priv, msg); +} + +static int npcm_peci_probe(struct platform_device *pdev) +{ + struct peci_adapter *adapter; + struct npcm_peci *priv; + void __iomem *base; + int ret; + + adapter = peci_alloc_adapter(&pdev->dev, sizeof(*priv)); + if (!adapter) + return -ENOMEM; + + priv = peci_get_adapdata(adapter); + priv->adapter = adapter; + priv->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, priv); + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) { + ret = PTR_ERR(base); + goto err_put_adapter_dev; + } + + priv->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &npcm_peci_regmap_config); + if (IS_ERR(priv->regmap)) { + ret = PTR_ERR(priv->regmap); + goto err_put_adapter_dev; + } + + priv->irq = platform_get_irq(pdev, 0); + if (!priv->irq) { + ret = -ENODEV; + goto err_put_adapter_dev; + } + + ret = devm_request_irq(&pdev->dev, priv->irq, npcm_peci_irq_handler, + 0, "peci-npcm-irq", priv); + if (ret) + goto err_put_adapter_dev; + + init_completion(&priv->xfer_complete); + spin_lock_init(&priv->lock); + + priv->adapter->owner = THIS_MODULE; + priv->adapter->dev.of_node = of_node_get(dev_of_node(priv->dev)); + strlcpy(priv->adapter->name, pdev->name, sizeof(priv->adapter->name)); + priv->adapter->xfer = npcm_peci_xfer; + + ret = npcm_peci_init_ctrl(priv); + if (ret) + goto err_put_adapter_dev; + + ret = peci_add_adapter(priv->adapter); + if (ret) + goto err_put_adapter_dev; + + dev_info(&pdev->dev, "peci bus %d registered, host negotiation bit rate %dHz", + priv->adapter->nr, priv->host_bit_rate); + + return 0; + +err_put_adapter_dev: + put_device(&adapter->dev); + return ret; +} + +static int npcm_peci_remove(struct platform_device *pdev) +{ + struct npcm_peci *priv = dev_get_drvdata(&pdev->dev); + + clk_disable_unprepare(priv->clk); + peci_del_adapter(priv->adapter); + of_node_put(priv->adapter->dev.of_node); + + return 0; +} + +static const struct of_device_id npcm_peci_of_table[] = { + { .compatible = "nuvoton,npcm750-peci", }, + { } +}; +MODULE_DEVICE_TABLE(of, npcm_peci_of_table); + +static struct platform_driver npcm_peci_driver = { + .probe = npcm_peci_probe, + .remove = npcm_peci_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = of_match_ptr(npcm_peci_of_table), + }, +}; +module_platform_driver(npcm_peci_driver); + +MODULE_AUTHOR("Tomer Maimon <tomer.maimon@nuvoton.com>"); +MODULE_DESCRIPTION("NPCM Platform Environment Control Interface (PECI) driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/peci/peci-core.c b/drivers/peci/peci-core.c new file mode 100644 index 000000000000..9aedb74710e6 --- /dev/null +++ b/drivers/peci/peci-core.c @@ -0,0 +1,2089 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018-2019 Intel Corporation + +#include <linux/bitfield.h> +#include <linux/crc8.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/peci.h> +#include <linux/pm_domain.h> +#include <linux/pm_runtime.h> +#include <linux/sched/task_stack.h> +#include <linux/slab.h> + +/* Mask for getting minor revision number from DIB */ +#define REVISION_NUM_MASK GENMASK(15, 8) + +/* CRC8 table for Assured Write Frame Check */ +#define PECI_CRC8_POLYNOMIAL 0x07 +DECLARE_CRC8_TABLE(peci_crc8_table); + +static bool is_registered; + +static DEFINE_MUTEX(core_lock); +static DEFINE_IDR(peci_adapter_idr); + +struct peci_adapter *peci_get_adapter(int nr) +{ + struct peci_adapter *adapter; + + mutex_lock(&core_lock); + adapter = idr_find(&peci_adapter_idr, nr); + if (!adapter) + goto out_unlock; + + if (try_module_get(adapter->owner)) + get_device(&adapter->dev); + else + adapter = NULL; + +out_unlock: + mutex_unlock(&core_lock); + + return adapter; +} +EXPORT_SYMBOL_GPL(peci_get_adapter); + +void peci_put_adapter(struct peci_adapter *adapter) +{ + if (!adapter) + return; + + put_device(&adapter->dev); + module_put(adapter->owner); +} +EXPORT_SYMBOL_GPL(peci_put_adapter); + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s\n", dev->type == &peci_client_type ? + to_peci_client(dev)->name : to_peci_adapter(dev)->name); +} +static DEVICE_ATTR_RO(name); + +static void peci_client_dev_release(struct device *dev) +{ + struct peci_client *client = to_peci_client(dev); + + dev_dbg(dev, "%s: %s\n", __func__, client->name); + peci_put_adapter(client->adapter); + kfree(client); +} + +static struct attribute *peci_device_attrs[] = { + &dev_attr_name.attr, + NULL +}; +ATTRIBUTE_GROUPS(peci_device); + +struct device_type peci_client_type = { + .groups = peci_device_groups, + .release = peci_client_dev_release, +}; +EXPORT_SYMBOL_GPL(peci_client_type); + +/** + * peci_verify_client - return parameter as peci_client, or NULL + * @dev: device, probably from some driver model iterator + * + * Return: pointer to peci_client on success, else NULL. + */ +struct peci_client *peci_verify_client(struct device *dev) +{ + return (dev->type == &peci_client_type) + ? to_peci_client(dev) + : NULL; +} +EXPORT_SYMBOL_GPL(peci_verify_client); + +/** + * peci_get_xfer_msg() - get a DMA safe peci_xfer_msg for the given tx and rx + * length + * @tx_len: the length of tx_buf. May be 0 if tx_buf isn't needed. + * @rx_len: the length of rx_buf. May be 0 if rx_buf isn't needed. + * + * Return: NULL if a DMA safe buffer was not obtained. + * Or a valid pointer to be used with DMA. After use, release it by + * calling peci_put_xfer_msg(). + * + * This function must only be called from process context! + */ +struct peci_xfer_msg *peci_get_xfer_msg(u8 tx_len, u8 rx_len) +{ + struct peci_xfer_msg *msg; + u8 *tx_buf, *rx_buf; + + if (tx_len) { + tx_buf = kzalloc(tx_len, GFP_KERNEL); + if (!tx_buf) + return NULL; + } else { + tx_buf = NULL; + } + + if (rx_len) { + rx_buf = kzalloc(rx_len, GFP_KERNEL); + if (!rx_buf) + goto err_free_tx_buf; + } else { + rx_buf = NULL; + } + + msg = kzalloc(sizeof(*msg), GFP_KERNEL); + if (!msg) + goto err_free_tx_rx_buf; + + msg->tx_len = tx_len; + msg->tx_buf = tx_buf; + msg->rx_len = rx_len; + msg->rx_buf = rx_buf; + + return msg; + +err_free_tx_rx_buf: + kfree(rx_buf); +err_free_tx_buf: + kfree(tx_buf); + + return NULL; +} +EXPORT_SYMBOL_GPL(peci_get_xfer_msg); + +/** + * peci_put_xfer_msg - release a DMA safe peci_xfer_msg + * @msg: the message obtained from peci_get_xfer_msg(). May be NULL. + */ +void peci_put_xfer_msg(struct peci_xfer_msg *msg) +{ + if (!msg) + return; + + kfree(msg->rx_buf); + kfree(msg->tx_buf); + kfree(msg); +} +EXPORT_SYMBOL_GPL(peci_put_xfer_msg); + +/* Calculate an Assured Write Frame Check Sequence byte */ +static int peci_aw_fcs(struct peci_xfer_msg *msg, int len, u8 *aw_fcs) +{ + u8 *tmp_buf; + + /* Allocate a temporary buffer to use a contiguous byte array */ + tmp_buf = kmalloc(len, GFP_KERNEL); + if (!tmp_buf) + return -ENOMEM; + + tmp_buf[0] = msg->addr; + tmp_buf[1] = msg->tx_len; + tmp_buf[2] = msg->rx_len; + memcpy(&tmp_buf[3], msg->tx_buf, len - 3); + + *aw_fcs = crc8(peci_crc8_table, tmp_buf, (size_t)len, 0); + + kfree(tmp_buf); + + return 0; +} + +static int __peci_xfer(struct peci_adapter *adapter, struct peci_xfer_msg *msg, + bool do_retry, bool has_aw_fcs) +{ + uint interval_ms = PECI_DEV_RETRY_INTERVAL_MIN_MSEC; + ulong timeout = jiffies; + u8 aw_fcs; + int ret; + + /* + * In case if adapter uses DMA, check at here whether tx and rx buffers + * are DMA capable or not. + */ + if (IS_ENABLED(CONFIG_HAS_DMA) && adapter->use_dma) { + if (is_vmalloc_addr(msg->tx_buf) || + is_vmalloc_addr(msg->rx_buf)) { + WARN_ONCE(1, "xfer msg is not dma capable\n"); + return -EAGAIN; + } else if (object_is_on_stack(msg->tx_buf) || + object_is_on_stack(msg->rx_buf)) { + WARN_ONCE(1, "xfer msg is on stack\n"); + return -EAGAIN; + } + } + + /* + * For some commands, the PECI originator may need to retry a command if + * the processor PECI client responds with a 0x8x completion code. In + * each instance, the processor PECI client may have started the + * operation but not completed it yet. When the 'retry' bit is set, the + * PECI client will ignore a new request if it exactly matches a + * previous valid request. For better performance and for reducing + * retry traffic, the interval time will be increased exponentially. + */ + + if (do_retry) + timeout += PECI_DEV_RETRY_TIMEOUT; + + for (;;) { + ret = adapter->xfer(adapter, msg); + + if (!do_retry || ret || !msg->rx_buf) + break; + + /* Retry is needed when completion code is 0x8x */ + if ((msg->rx_buf[0] & PECI_DEV_CC_RETRY_CHECK_MASK) != + PECI_DEV_CC_NEED_RETRY) + break; + + /* Set the retry bit to indicate a retry attempt */ + msg->tx_buf[1] |= PECI_DEV_RETRY_BIT; + + /* Recalculate the AW FCS if it has one */ + if (has_aw_fcs) { + ret = peci_aw_fcs(msg, 2 + msg->tx_len, &aw_fcs); + if (ret) + break; + + msg->tx_buf[msg->tx_len - 1] = 0x80 ^ aw_fcs; + } + + /* Retry it for 'timeout' before returning an error. */ + if (time_after(jiffies, timeout)) { + dev_dbg(&adapter->dev, "Timeout retrying xfer!\n"); + ret = -ETIMEDOUT; + break; + } + + set_current_state(TASK_INTERRUPTIBLE); + if (schedule_timeout(msecs_to_jiffies(interval_ms))) { + ret = -EINTR; + break; + } + + interval_ms *= 2; + if (interval_ms > PECI_DEV_RETRY_INTERVAL_MAX_MSEC) + interval_ms = PECI_DEV_RETRY_INTERVAL_MAX_MSEC; + } + + if (ret) + dev_dbg(&adapter->dev, "xfer error: %d\n", ret); + + return ret; +} + +static int peci_xfer(struct peci_adapter *adapter, struct peci_xfer_msg *msg) +{ + return __peci_xfer(adapter, msg, false, false); +} + +static int peci_xfer_with_retries(struct peci_adapter *adapter, + struct peci_xfer_msg *msg, + bool has_aw_fcs) +{ + return __peci_xfer(adapter, msg, true, has_aw_fcs); +} + +static int peci_scan_cmd_mask(struct peci_adapter *adapter) +{ + struct peci_xfer_msg *msg; + u8 revision; + int ret; + u64 dib; + + /* Update command mask just once */ + if (adapter->cmd_mask & BIT(PECI_CMD_XFER)) + return 0; + + msg = peci_get_xfer_msg(PECI_GET_DIB_WR_LEN, PECI_GET_DIB_RD_LEN); + if (!msg) + return -ENOMEM; + + msg->addr = PECI_BASE_ADDR; + msg->tx_buf[0] = PECI_GET_DIB_CMD; + + ret = peci_xfer(adapter, msg); + if (ret) + return ret; + + dib = le64_to_cpup((__le64 *)msg->rx_buf); + + /* Check special case for Get DIB command */ + if (dib == 0) { + dev_dbg(&adapter->dev, "DIB read as 0\n"); + ret = -EIO; + goto out; + } + + /* + * Setting up the supporting commands based on revision number. + * See PECI Spec Table 3-1. + */ + revision = FIELD_GET(REVISION_NUM_MASK, dib); + if (revision >= 0x40) { /* Rev. 4.0 */ + adapter->cmd_mask |= BIT(PECI_CMD_RD_IA_MSREX); + adapter->cmd_mask |= BIT(PECI_CMD_RD_END_PT_CFG); + adapter->cmd_mask |= BIT(PECI_CMD_WR_END_PT_CFG); + adapter->cmd_mask |= BIT(PECI_CMD_CRASHDUMP_DISC); + adapter->cmd_mask |= BIT(PECI_CMD_CRASHDUMP_GET_FRAME); + } + if (revision >= 0x36) /* Rev. 3.6 */ + adapter->cmd_mask |= BIT(PECI_CMD_WR_IA_MSR); + if (revision >= 0x35) /* Rev. 3.5 */ + adapter->cmd_mask |= BIT(PECI_CMD_WR_PCI_CFG); + if (revision >= 0x34) /* Rev. 3.4 */ + adapter->cmd_mask |= BIT(PECI_CMD_RD_PCI_CFG); + if (revision >= 0x33) { /* Rev. 3.3 */ + adapter->cmd_mask |= BIT(PECI_CMD_RD_PCI_CFG_LOCAL); + adapter->cmd_mask |= BIT(PECI_CMD_WR_PCI_CFG_LOCAL); + } + if (revision >= 0x32) /* Rev. 3.2 */ + adapter->cmd_mask |= BIT(PECI_CMD_RD_IA_MSR); + if (revision >= 0x31) { /* Rev. 3.1 */ + adapter->cmd_mask |= BIT(PECI_CMD_RD_PKG_CFG); + adapter->cmd_mask |= BIT(PECI_CMD_WR_PKG_CFG); + } + + adapter->cmd_mask |= BIT(PECI_CMD_XFER); + adapter->cmd_mask |= BIT(PECI_CMD_GET_TEMP); + adapter->cmd_mask |= BIT(PECI_CMD_GET_DIB); + adapter->cmd_mask |= BIT(PECI_CMD_PING); + +out: + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_check_cmd_support(struct peci_adapter *adapter, + enum peci_cmd cmd) +{ + if (!(adapter->cmd_mask & BIT(PECI_CMD_PING)) && + peci_scan_cmd_mask(adapter) < 0) { + dev_dbg(&adapter->dev, "Failed to scan command mask\n"); + return -EIO; + } + + if (!(adapter->cmd_mask & BIT(cmd))) { + dev_dbg(&adapter->dev, "Command %d is not supported\n", cmd); + return -EINVAL; + } + + return 0; +} + +static int peci_cmd_xfer(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_xfer_msg *msg = vmsg; + u8 aw_fcs; + int ret; + + if (!msg->tx_len) { + ret = peci_xfer(adapter, msg); + } else { + switch (msg->tx_buf[0]) { + case PECI_RDPKGCFG_CMD: + case PECI_RDIAMSR_CMD: + case PECI_RDIAMSREX_CMD: + case PECI_RDPCICFG_CMD: + case PECI_RDPCICFGLOCAL_CMD: + case PECI_RDENDPTCFG_CMD: + case PECI_CRASHDUMP_CMD: + ret = peci_xfer_with_retries(adapter, msg, false); + break; + case PECI_WRPKGCFG_CMD: + case PECI_WRIAMSR_CMD: + case PECI_WRPCICFG_CMD: + case PECI_WRPCICFGLOCAL_CMD: + case PECI_WRENDPTCFG_CMD: + /* Check if the AW FCS byte is already provided */ + ret = peci_aw_fcs(msg, 2 + msg->tx_len, &aw_fcs); + if (ret) + break; + + if (msg->tx_buf[msg->tx_len - 1] != (0x80 ^ aw_fcs)) { + /* + * Add an Assured Write Frame Check Sequence + * byte and increment the tx_len to include + * the new byte. + */ + msg->tx_len++; + ret = peci_aw_fcs(msg, 2 + msg->tx_len, + &aw_fcs); + if (ret) + break; + + msg->tx_buf[msg->tx_len - 1] = 0x80 ^ aw_fcs; + } + + ret = peci_xfer_with_retries(adapter, msg, true); + break; + case PECI_GET_DIB_CMD: + case PECI_GET_TEMP_CMD: + default: + ret = peci_xfer(adapter, msg); + break; + } + } + + return ret; +} + +static int peci_cmd_ping(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_ping_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + int ret; + + msg = peci_get_xfer_msg(0, 0); + if (!msg) + return -ENOMEM; + + msg->addr = umsg->addr; + + ret = peci_xfer(adapter, msg); + + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_get_dib(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_get_dib_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + int ret; + + msg = peci_get_xfer_msg(PECI_GET_DIB_WR_LEN, PECI_GET_DIB_RD_LEN); + if (!msg) + return -ENOMEM; + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_GET_DIB_CMD; + + ret = peci_xfer(adapter, msg); + if (ret) + goto out; + + umsg->dib = le64_to_cpup((__le64 *)msg->rx_buf); + +out: + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_get_temp(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_get_temp_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + int ret; + + msg = peci_get_xfer_msg(PECI_GET_TEMP_WR_LEN, PECI_GET_TEMP_RD_LEN); + if (!msg) + return -ENOMEM; + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_GET_TEMP_CMD; + + ret = peci_xfer(adapter, msg); + if (ret) + goto out; + + umsg->temp_raw = le16_to_cpup((__le16 *)msg->rx_buf); + +out: + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_rd_pkg_cfg(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_rd_pkg_cfg_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + int ret; + + /* Per the PECI spec, the read length must be a byte, word, or dword */ + if (umsg->rx_len != 1 && umsg->rx_len != 2 && umsg->rx_len != 4) { + dev_dbg(&adapter->dev, "Invalid read length, rx_len: %d\n", + umsg->rx_len); + return -EINVAL; + } + + msg = peci_get_xfer_msg(PECI_RDPKGCFG_WRITE_LEN, + PECI_RDPKGCFG_READ_LEN_BASE + umsg->rx_len); + if (!msg) + return -ENOMEM; + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_RDPKGCFG_CMD; + msg->tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ + /* Host ID is 0 for PECI 3.0 */ + msg->tx_buf[2] = umsg->index; /* RdPkgConfig index */ + msg->tx_buf[3] = (u8)umsg->param; /* LSB - Config parameter */ + msg->tx_buf[4] = (u8)(umsg->param >> 8); /* MSB - Config parameter */ + + ret = peci_xfer_with_retries(adapter, msg, false); + if (!ret) + memcpy(umsg->pkg_config, &msg->rx_buf[1], umsg->rx_len); + + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_wr_pkg_cfg(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_wr_pkg_cfg_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + int ret, i; + u8 aw_fcs; + + /* Per the PECI spec, the write length must be a dword */ + if (umsg->tx_len != 4) { + dev_dbg(&adapter->dev, "Invalid write length, tx_len: %d\n", + umsg->tx_len); + return -EINVAL; + } + + msg = peci_get_xfer_msg(PECI_WRPKGCFG_WRITE_LEN_BASE + umsg->tx_len, + PECI_WRPKGCFG_READ_LEN); + if (!msg) + return -ENOMEM; + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_WRPKGCFG_CMD; + msg->tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ + /* Host ID is 0 for PECI 3.0 */ + msg->tx_buf[2] = umsg->index; /* RdPkgConfig index */ + msg->tx_buf[3] = (u8)umsg->param; /* LSB - Config parameter */ + msg->tx_buf[4] = (u8)(umsg->param >> 8); /* MSB - Config parameter */ + for (i = 0; i < umsg->tx_len; i++) + msg->tx_buf[5 + i] = (u8)(umsg->value >> (i << 3)); + + /* Add an Assured Write Frame Check Sequence byte */ + ret = peci_aw_fcs(msg, 8 + umsg->tx_len, &aw_fcs); + if (ret) + goto out; + + msg->tx_buf[5 + i] = 0x80 ^ aw_fcs; + + ret = peci_xfer_with_retries(adapter, msg, true); + +out: + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_rd_ia_msr(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_rd_ia_msr_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + int ret; + + msg = peci_get_xfer_msg(PECI_RDIAMSR_WRITE_LEN, PECI_RDIAMSR_READ_LEN); + if (!msg) + return -ENOMEM; + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_RDIAMSR_CMD; + msg->tx_buf[1] = 0; + msg->tx_buf[2] = umsg->thread_id; + msg->tx_buf[3] = (u8)umsg->address; + msg->tx_buf[4] = (u8)(umsg->address >> 8); + + ret = peci_xfer_with_retries(adapter, msg, false); + if (!ret) + memcpy(&umsg->value, &msg->rx_buf[1], sizeof(uint64_t)); + + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_rd_ia_msrex(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_rd_ia_msrex_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + int ret; + + msg = peci_get_xfer_msg(PECI_RDIAMSREX_WRITE_LEN, + PECI_RDIAMSREX_READ_LEN); + if (!msg) + return -ENOMEM; + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_RDIAMSREX_CMD; + msg->tx_buf[1] = 0; + msg->tx_buf[2] = (u8)umsg->thread_id; + msg->tx_buf[3] = (u8)(umsg->thread_id >> 8); + msg->tx_buf[4] = (u8)umsg->address; + msg->tx_buf[5] = (u8)(umsg->address >> 8); + + ret = peci_xfer_with_retries(adapter, msg, false); + if (!ret) + memcpy(&umsg->value, &msg->rx_buf[1], sizeof(uint64_t)); + + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_wr_ia_msr(struct peci_adapter *adapter, void *vmsg) +{ + return -ENOSYS; /* Not implemented yet */ +} + +static int peci_cmd_rd_pci_cfg(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_rd_pci_cfg_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + u32 address; + int ret; + + msg = peci_get_xfer_msg(PECI_RDPCICFG_WRITE_LEN, + PECI_RDPCICFG_READ_LEN); + if (!msg) + return -ENOMEM; + + address = umsg->reg; /* [11:0] - Register */ + address |= (u32)umsg->function << 12; /* [14:12] - Function */ + address |= (u32)umsg->device << 15; /* [19:15] - Device */ + address |= (u32)umsg->bus << 20; /* [27:20] - Bus */ + /* [31:28] - Reserved */ + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_RDPCICFG_CMD; + msg->tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ + /* Host ID is 0 for PECI 3.0 */ + msg->tx_buf[2] = (u8)address; /* LSB - PCI Config Address */ + msg->tx_buf[3] = (u8)(address >> 8); /* PCI Config Address */ + msg->tx_buf[4] = (u8)(address >> 16); /* PCI Config Address */ + msg->tx_buf[5] = (u8)(address >> 24); /* MSB - PCI Config Address */ + + ret = peci_xfer_with_retries(adapter, msg, false); + if (!ret) + memcpy(umsg->pci_config, &msg->rx_buf[1], 4); + + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_wr_pci_cfg(struct peci_adapter *adapter, void *vmsg) +{ + return -ENOSYS; /* Not implemented yet */ +} + +static int peci_cmd_rd_pci_cfg_local(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_rd_pci_cfg_local_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + u32 address; + int ret; + + /* Per the PECI spec, the read length must be a byte, word, or dword */ + if (umsg->rx_len != 1 && umsg->rx_len != 2 && umsg->rx_len != 4) { + dev_dbg(&adapter->dev, "Invalid read length, rx_len: %d\n", + umsg->rx_len); + return -EINVAL; + } + + msg = peci_get_xfer_msg(PECI_RDPCICFGLOCAL_WRITE_LEN, + PECI_RDPCICFGLOCAL_READ_LEN_BASE + + umsg->rx_len); + if (!msg) + return -ENOMEM; + + address = umsg->reg; /* [11:0] - Register */ + address |= (u32)umsg->function << 12; /* [14:12] - Function */ + address |= (u32)umsg->device << 15; /* [19:15] - Device */ + address |= (u32)umsg->bus << 20; /* [23:20] - Bus */ + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_RDPCICFGLOCAL_CMD; + msg->tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ + /* Host ID is 0 for PECI 3.0 */ + msg->tx_buf[2] = (u8)address; /* LSB - PCI Configuration Address */ + msg->tx_buf[3] = (u8)(address >> 8); /* PCI Configuration Address */ + msg->tx_buf[4] = (u8)(address >> 16); /* PCI Configuration Address */ + + ret = peci_xfer_with_retries(adapter, msg, false); + if (!ret) + memcpy(umsg->pci_config, &msg->rx_buf[1], umsg->rx_len); + + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_wr_pci_cfg_local(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_wr_pci_cfg_local_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + u32 address; + int ret, i; + u8 aw_fcs; + + /* Per the PECI spec, the write length must be a byte, word, or dword */ + if (umsg->tx_len != 1 && umsg->tx_len != 2 && umsg->tx_len != 4) { + dev_dbg(&adapter->dev, "Invalid write length, tx_len: %d\n", + umsg->tx_len); + return -EINVAL; + } + + msg = peci_get_xfer_msg(PECI_WRPCICFGLOCAL_WRITE_LEN_BASE + + umsg->tx_len, PECI_WRPCICFGLOCAL_READ_LEN); + if (!msg) + return -ENOMEM; + + address = umsg->reg; /* [11:0] - Register */ + address |= (u32)umsg->function << 12; /* [14:12] - Function */ + address |= (u32)umsg->device << 15; /* [19:15] - Device */ + address |= (u32)umsg->bus << 20; /* [23:20] - Bus */ + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_WRPCICFGLOCAL_CMD; + msg->tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ + /* Host ID is 0 for PECI 3.0 */ + msg->tx_buf[2] = (u8)address; /* LSB - PCI Configuration Address */ + msg->tx_buf[3] = (u8)(address >> 8); /* PCI Configuration Address */ + msg->tx_buf[4] = (u8)(address >> 16); /* PCI Configuration Address */ + for (i = 0; i < umsg->tx_len; i++) + msg->tx_buf[5 + i] = (u8)(umsg->value >> (i << 3)); + + /* Add an Assured Write Frame Check Sequence byte */ + ret = peci_aw_fcs(msg, 8 + umsg->tx_len, &aw_fcs); + if (ret) + goto out; + + msg->tx_buf[5 + i] = 0x80 ^ aw_fcs; + + ret = peci_xfer_with_retries(adapter, msg, true); + +out: + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_rd_end_pt_cfg(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_rd_end_pt_cfg_msg *umsg = vmsg; + struct peci_xfer_msg *msg = NULL; + u32 address; + u8 tx_size; + int ret; + + switch (umsg->msg_type) { + case PECI_ENDPTCFG_TYPE_LOCAL_PCI: + case PECI_ENDPTCFG_TYPE_PCI: + /* + * Per the PECI spec, the read length must be a byte, word, + * or dword + */ + if (umsg->rx_len != 1 && umsg->rx_len != 2 && + umsg->rx_len != 4) { + dev_dbg(&adapter->dev, + "Invalid read length, rx_len: %d\n", + umsg->rx_len); + return -EINVAL; + } + + msg = peci_get_xfer_msg(PECI_RDENDPTCFG_PCI_WRITE_LEN, + PECI_RDENDPTCFG_READ_LEN_BASE + + umsg->rx_len); + if (!msg) + return -ENOMEM; + + address = umsg->params.pci_cfg.reg; /* [11:0] - Register */ + address |= (u32)umsg->params.pci_cfg.function + << 12; /* [14:12] - Function */ + address |= (u32)umsg->params.pci_cfg.device + << 15; /* [19:15] - Device */ + address |= (u32)umsg->params.pci_cfg.bus + << 20; /* [27:20] - Bus */ + /* [31:28] - Reserved */ + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_RDENDPTCFG_CMD; + msg->tx_buf[1] = 0x00; /* request byte for Host ID|Retry bit */ + msg->tx_buf[2] = umsg->msg_type; /* Message Type */ + msg->tx_buf[3] = 0x00; /* Endpoint ID */ + msg->tx_buf[4] = 0x00; /* Reserved */ + msg->tx_buf[5] = 0x00; /* Reserved */ + msg->tx_buf[6] = PECI_ENDPTCFG_ADDR_TYPE_PCI; /* Addr Type */ + msg->tx_buf[7] = umsg->params.pci_cfg.seg; /* PCI Segment */ + msg->tx_buf[8] = (u8)address; /* LSB - PCI Config Address */ + msg->tx_buf[9] = (u8)(address >> 8); /* PCI Config Address */ + msg->tx_buf[10] = (u8)(address >> 16); /* PCI Config Address */ + msg->tx_buf[11] = + (u8)(address >> 24); /* MSB - PCI Config Address */ + break; + + case PECI_ENDPTCFG_TYPE_MMIO: + /* + * Per the PECI spec, the read length must be a byte, word, + * dword, or qword + */ + if (umsg->rx_len != 1 && umsg->rx_len != 2 && + umsg->rx_len != 4 && umsg->rx_len != 8) { + dev_dbg(&adapter->dev, + "Invalid read length, rx_len: %d\n", + umsg->rx_len); + return -EINVAL; + } + /* + * Per the PECI spec, the address type must specify either DWORD + * or QWORD + */ + if (umsg->params.mmio.addr_type != + PECI_ENDPTCFG_ADDR_TYPE_MMIO_D && + umsg->params.mmio.addr_type != + PECI_ENDPTCFG_ADDR_TYPE_MMIO_Q) { + dev_dbg(&adapter->dev, + "Invalid address type, addr_type: %d\n", + umsg->params.mmio.addr_type); + return -EINVAL; + } + + if (umsg->params.mmio.addr_type == + PECI_ENDPTCFG_ADDR_TYPE_MMIO_D) + tx_size = PECI_RDENDPTCFG_MMIO_D_WRITE_LEN; + else + tx_size = PECI_RDENDPTCFG_MMIO_Q_WRITE_LEN; + msg = peci_get_xfer_msg(tx_size, + PECI_RDENDPTCFG_READ_LEN_BASE + + umsg->rx_len); + if (!msg) + return -ENOMEM; + + address = umsg->params.mmio.function; /* [2:0] - Function */ + address |= (u32)umsg->params.mmio.device + << 3; /* [7:3] - Device */ + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_RDENDPTCFG_CMD; + msg->tx_buf[1] = 0x00; /* request byte for Host ID|Retry bit */ + msg->tx_buf[2] = umsg->msg_type; /* Message Type */ + msg->tx_buf[3] = 0x00; /* Endpoint ID */ + msg->tx_buf[4] = 0x00; /* Reserved */ + msg->tx_buf[5] = umsg->params.mmio.bar; /* BAR # */ + msg->tx_buf[6] = umsg->params.mmio.addr_type; /* Address Type */ + msg->tx_buf[7] = umsg->params.mmio.seg; /* PCI Segment */ + msg->tx_buf[8] = (u8)address; /* Function/Device */ + msg->tx_buf[9] = umsg->params.mmio.bus; /* PCI Bus */ + msg->tx_buf[10] = (u8)umsg->params.mmio + .offset; /* LSB - Register Offset */ + msg->tx_buf[11] = (u8)(umsg->params.mmio.offset + >> 8); /* Register Offset */ + msg->tx_buf[12] = (u8)(umsg->params.mmio.offset + >> 16); /* Register Offset */ + msg->tx_buf[13] = (u8)(umsg->params.mmio.offset + >> 24); /* MSB - DWORD Register Offset */ + if (umsg->params.mmio.addr_type == + PECI_ENDPTCFG_ADDR_TYPE_MMIO_Q) { + msg->tx_buf[14] = (u8)(umsg->params.mmio.offset + >> 32); /* Register Offset */ + msg->tx_buf[15] = (u8)(umsg->params.mmio.offset + >> 40); /* Register Offset */ + msg->tx_buf[16] = (u8)(umsg->params.mmio.offset + >> 48); /* Register Offset */ + msg->tx_buf[17] = + (u8)(umsg->params.mmio.offset + >> 56); /* MSB - QWORD Register Offset */ + } + break; + + default: + return -EINVAL; + } + + ret = peci_xfer_with_retries(adapter, msg, false); + if (!ret) + memcpy(umsg->data, &msg->rx_buf[1], umsg->rx_len); + + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_wr_end_pt_cfg(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_wr_end_pt_cfg_msg *umsg = vmsg; + struct peci_xfer_msg *msg = NULL; + u8 tx_size, aw_fcs; + int ret, i, idx; + u32 address; + + switch (umsg->msg_type) { + case PECI_ENDPTCFG_TYPE_LOCAL_PCI: + case PECI_ENDPTCFG_TYPE_PCI: + /* + * Per the PECI spec, the write length must be a byte, word, + * or dword + */ + if (umsg->tx_len != 1 && umsg->tx_len != 2 && + umsg->tx_len != 4) { + dev_dbg(&adapter->dev, + "Invalid write length, tx_len: %d\n", + umsg->tx_len); + return -EINVAL; + } + + msg = peci_get_xfer_msg(PECI_WRENDPTCFG_PCI_WRITE_LEN_BASE + + umsg->tx_len, PECI_WRENDPTCFG_READ_LEN); + if (!msg) + return -ENOMEM; + + address = umsg->params.pci_cfg.reg; /* [11:0] - Register */ + address |= (u32)umsg->params.pci_cfg.function + << 12; /* [14:12] - Function */ + address |= (u32)umsg->params.pci_cfg.device + << 15; /* [19:15] - Device */ + address |= (u32)umsg->params.pci_cfg.bus + << 20; /* [27:20] - Bus */ + /* [31:28] - Reserved */ + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_WRENDPTCFG_CMD; + msg->tx_buf[1] = 0x00; /* request byte for Host ID|Retry bit */ + msg->tx_buf[2] = umsg->msg_type; /* Message Type */ + msg->tx_buf[3] = 0x00; /* Endpoint ID */ + msg->tx_buf[4] = 0x00; /* Reserved */ + msg->tx_buf[5] = 0x00; /* Reserved */ + msg->tx_buf[6] = PECI_ENDPTCFG_ADDR_TYPE_PCI; /* Addr Type */ + msg->tx_buf[7] = umsg->params.pci_cfg.seg; /* PCI Segment */ + msg->tx_buf[8] = (u8)address; /* LSB - PCI Config Address */ + msg->tx_buf[9] = (u8)(address >> 8); /* PCI Config Address */ + msg->tx_buf[10] = (u8)(address >> 16); /* PCI Config Address */ + msg->tx_buf[11] = + (u8)(address >> 24); /* MSB - PCI Config Address */ + for (i = 0; i < umsg->tx_len; i++) + msg->tx_buf[12 + i] = (u8)(umsg->value >> (i << 3)); + + /* Add an Assured Write Frame Check Sequence byte */ + ret = peci_aw_fcs(msg, 15 + umsg->tx_len, &aw_fcs); + if (ret) + goto out; + + msg->tx_buf[12 + i] = 0x80 ^ aw_fcs; + break; + + case PECI_ENDPTCFG_TYPE_MMIO: + /* + * Per the PECI spec, the write length must be a byte, word, + * dword, or qword + */ + if (umsg->tx_len != 1 && umsg->tx_len != 2 && + umsg->tx_len != 4 && umsg->tx_len != 8) { + dev_dbg(&adapter->dev, + "Invalid write length, tx_len: %d\n", + umsg->tx_len); + return -EINVAL; + } + /* + * Per the PECI spec, the address type must specify either DWORD + * or QWORD + */ + if (umsg->params.mmio.addr_type != + PECI_ENDPTCFG_ADDR_TYPE_MMIO_D && + umsg->params.mmio.addr_type != + PECI_ENDPTCFG_ADDR_TYPE_MMIO_Q) { + dev_dbg(&adapter->dev, + "Invalid address type, addr_type: %d\n", + umsg->params.mmio.addr_type); + return -EINVAL; + } + + if (umsg->params.mmio.addr_type == + PECI_ENDPTCFG_ADDR_TYPE_MMIO_D) + tx_size = PECI_WRENDPTCFG_MMIO_D_WRITE_LEN_BASE + + umsg->tx_len; + else + tx_size = PECI_WRENDPTCFG_MMIO_Q_WRITE_LEN_BASE + + umsg->tx_len; + msg = peci_get_xfer_msg(tx_size, PECI_WRENDPTCFG_READ_LEN); + if (!msg) + return -ENOMEM; + + address = umsg->params.mmio.function; /* [2:0] - Function */ + address |= (u32)umsg->params.mmio.device + << 3; /* [7:3] - Device */ + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_WRENDPTCFG_CMD; + msg->tx_buf[1] = 0x00; /* request byte for Host ID|Retry bit */ + msg->tx_buf[2] = umsg->msg_type; /* Message Type */ + msg->tx_buf[3] = 0x00; /* Endpoint ID */ + msg->tx_buf[4] = 0x00; /* Reserved */ + msg->tx_buf[5] = umsg->params.mmio.bar; /* BAR # */ + msg->tx_buf[6] = umsg->params.mmio.addr_type; /* Address Type */ + msg->tx_buf[7] = umsg->params.mmio.seg; /* PCI Segment */ + msg->tx_buf[8] = (u8)address; /* Function/Device */ + msg->tx_buf[9] = umsg->params.mmio.bus; /* PCI Bus */ + msg->tx_buf[10] = (u8)umsg->params.mmio + .offset; /* LSB - Register Offset */ + msg->tx_buf[11] = (u8)(umsg->params.mmio.offset + >> 8); /* Register Offset */ + msg->tx_buf[12] = (u8)(umsg->params.mmio.offset + >> 16); /* Register Offset */ + msg->tx_buf[13] = (u8)(umsg->params.mmio.offset + >> 24); /* MSB - DWORD Register Offset */ + if (umsg->params.mmio.addr_type == + PECI_ENDPTCFG_ADDR_TYPE_MMIO_Q) { + msg->tx_len = PECI_WRENDPTCFG_MMIO_Q_WRITE_LEN_BASE; + msg->tx_buf[14] = (u8)(umsg->params.mmio.offset + >> 32); /* Register Offset */ + msg->tx_buf[15] = (u8)(umsg->params.mmio.offset + >> 40); /* Register Offset */ + msg->tx_buf[16] = (u8)(umsg->params.mmio.offset + >> 48); /* Register Offset */ + msg->tx_buf[17] = + (u8)(umsg->params.mmio.offset + >> 56); /* MSB - QWORD Register Offset */ + idx = 18; + } else { + idx = 14; + } + for (i = 0; i < umsg->tx_len; i++) + msg->tx_buf[idx + i] = (u8)(umsg->value >> (i << 3)); + + /* Add an Assured Write Frame Check Sequence byte */ + ret = peci_aw_fcs(msg, idx + 3 + umsg->tx_len, &aw_fcs); + if (ret) + goto out; + + msg->tx_buf[idx + i] = 0x80 ^ aw_fcs; + break; + + default: + return -EINVAL; + } + + ret = peci_xfer_with_retries(adapter, msg, false); + +out: + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_crashdump_disc(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_crashdump_disc_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + int ret; + + /* Per the EDS, the read length must be a byte, word, or qword */ + if (umsg->rx_len != 1 && umsg->rx_len != 2 && umsg->rx_len != 8) { + dev_dbg(&adapter->dev, "Invalid read length, rx_len: %d\n", + umsg->rx_len); + return -EINVAL; + } + + msg = peci_get_xfer_msg(PECI_CRASHDUMP_DISC_WRITE_LEN, + PECI_CRASHDUMP_DISC_READ_LEN_BASE + + umsg->rx_len); + if (!msg) + return -ENOMEM; + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_CRASHDUMP_CMD; + msg->tx_buf[1] = 0x00; /* request byte for Host ID | Retry bit */ + /* Host ID is 0 for PECI 3.0 */ + msg->tx_buf[2] = PECI_CRASHDUMP_DISC_VERSION; + msg->tx_buf[3] = PECI_CRASHDUMP_DISC_OPCODE; + msg->tx_buf[4] = umsg->subopcode; + msg->tx_buf[5] = umsg->param0; + msg->tx_buf[6] = (u8)umsg->param1; + msg->tx_buf[7] = (u8)(umsg->param1 >> 8); + msg->tx_buf[8] = umsg->param2; + + ret = peci_xfer_with_retries(adapter, msg, false); + if (!ret) + memcpy(umsg->data, &msg->rx_buf[1], umsg->rx_len); + + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_crashdump_get_frame(struct peci_adapter *adapter, + void *vmsg) +{ + struct peci_crashdump_get_frame_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + int ret; + + /* Per the EDS, the read length must be a qword or dqword */ + if (umsg->rx_len != 8 && umsg->rx_len != 16) { + dev_dbg(&adapter->dev, "Invalid read length, rx_len: %d\n", + umsg->rx_len); + return -EINVAL; + } + + msg = peci_get_xfer_msg(PECI_CRASHDUMP_GET_FRAME_WRITE_LEN, + PECI_CRASHDUMP_GET_FRAME_READ_LEN_BASE + + umsg->rx_len); + if (!msg) + return -ENOMEM; + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_CRASHDUMP_CMD; + msg->tx_buf[1] = 0x00; /* request byte for Host ID | Retry bit */ + /* Host ID is 0 for PECI 3.0 */ + msg->tx_buf[2] = PECI_CRASHDUMP_GET_FRAME_VERSION; + msg->tx_buf[3] = PECI_CRASHDUMP_GET_FRAME_OPCODE; + msg->tx_buf[4] = (u8)umsg->param0; + msg->tx_buf[5] = (u8)(umsg->param0 >> 8); + msg->tx_buf[6] = (u8)umsg->param1; + msg->tx_buf[7] = (u8)(umsg->param1 >> 8); + msg->tx_buf[8] = (u8)umsg->param2; + msg->tx_buf[9] = (u8)(umsg->param2 >> 8); + + ret = peci_xfer_with_retries(adapter, msg, false); + if (!ret) + memcpy(umsg->data, &msg->rx_buf[1], umsg->rx_len); + + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +typedef int (*peci_cmd_fn_type)(struct peci_adapter *, void *); + +static const peci_cmd_fn_type peci_cmd_fn[PECI_CMD_MAX] = { + peci_cmd_xfer, + peci_cmd_ping, + peci_cmd_get_dib, + peci_cmd_get_temp, + peci_cmd_rd_pkg_cfg, + peci_cmd_wr_pkg_cfg, + peci_cmd_rd_ia_msr, + peci_cmd_wr_ia_msr, + peci_cmd_rd_ia_msrex, + peci_cmd_rd_pci_cfg, + peci_cmd_wr_pci_cfg, + peci_cmd_rd_pci_cfg_local, + peci_cmd_wr_pci_cfg_local, + peci_cmd_rd_end_pt_cfg, + peci_cmd_wr_end_pt_cfg, + peci_cmd_crashdump_disc, + peci_cmd_crashdump_get_frame, +}; + +/** + * peci_command - transfer function of a PECI command + * @adapter: pointer to peci_adapter + * @vmsg: pointer to PECI messages + * Context: can sleep + * + * This performs a transfer of a PECI command using PECI messages parameter + * which has various formats on each command. + * + * Return: zero on success, else a negative error code. + */ +int peci_command(struct peci_adapter *adapter, enum peci_cmd cmd, void *vmsg) +{ + int ret; + + if (cmd >= PECI_CMD_MAX || cmd < PECI_CMD_XFER) + return -ENOTTY; + + dev_dbg(&adapter->dev, "%s, cmd=0x%02x\n", __func__, cmd); + + if (!peci_cmd_fn[cmd]) + return -EINVAL; + + mutex_lock(&adapter->bus_lock); + + ret = peci_check_cmd_support(adapter, cmd); + if (!ret) + ret = peci_cmd_fn[cmd](adapter, vmsg); + + mutex_unlock(&adapter->bus_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(peci_command); + +static int peci_detect(struct peci_adapter *adapter, u8 addr) +{ + struct peci_ping_msg msg; + + msg.addr = addr; + + return peci_command(adapter, PECI_CMD_PING, &msg); +} + +static const struct of_device_id * +peci_of_match_device(const struct of_device_id *matches, + struct peci_client *client) +{ +#if IS_ENABLED(CONFIG_OF) + if (!(client && matches)) + return NULL; + + return of_match_device(matches, &client->dev); +#else /* CONFIG_OF */ + return NULL; +#endif /* CONFIG_OF */ +} + +static const struct peci_device_id * +peci_match_id(const struct peci_device_id *id, struct peci_client *client) +{ + if (!(id && client)) + return NULL; + + while (id->name[0]) { + if (!strncmp(client->name, id->name, PECI_NAME_SIZE)) + return id; + id++; + } + + return NULL; +} + +static int peci_device_match(struct device *dev, struct device_driver *drv) +{ + struct peci_client *client = peci_verify_client(dev); + struct peci_driver *driver; + + /* Attempt an OF style match */ + if (peci_of_match_device(drv->of_match_table, client)) + return 1; + + driver = to_peci_driver(drv); + + /* Finally an ID match */ + if (peci_match_id(driver->id_table, client)) + return 1; + + return 0; +} + +static int peci_device_probe(struct device *dev) +{ + struct peci_client *client = peci_verify_client(dev); + struct peci_driver *driver; + int status = -EINVAL; + + if (!client) + return 0; + + driver = to_peci_driver(dev->driver); + + if (!driver->id_table && + !peci_of_match_device(dev->driver->of_match_table, client)) + return -ENODEV; + + dev_dbg(dev, "%s: name:%s\n", __func__, client->name); + + status = dev_pm_domain_attach(&client->dev, true); + if (status == -EPROBE_DEFER) + return status; + + if (driver->probe) + status = driver->probe(client); + else + status = -EINVAL; + + if (status) + goto err_detach_pm_domain; + + return 0; + +err_detach_pm_domain: + dev_pm_domain_detach(&client->dev, true); + + return status; +} + +static int peci_device_remove(struct device *dev) +{ + struct peci_client *client = peci_verify_client(dev); + struct peci_driver *driver; + int status = 0; + + if (!client || !dev->driver) + return 0; + + driver = to_peci_driver(dev->driver); + if (driver->remove) { + dev_dbg(dev, "%s: name:%s\n", __func__, client->name); + status = driver->remove(client); + } + + dev_pm_domain_detach(&client->dev, true); + + return status; +} + +static void peci_device_shutdown(struct device *dev) +{ + struct peci_client *client = peci_verify_client(dev); + struct peci_driver *driver; + + if (!client || !dev->driver) + return; + + dev_dbg(dev, "%s: name:%s\n", __func__, client->name); + + driver = to_peci_driver(dev->driver); + if (driver->shutdown) + driver->shutdown(client); +} + +struct bus_type peci_bus_type = { + .name = "peci", + .match = peci_device_match, + .probe = peci_device_probe, + .remove = peci_device_remove, + .shutdown = peci_device_shutdown, +}; +EXPORT_SYMBOL_GPL(peci_bus_type); + +static int peci_check_addr_validity(u8 addr) +{ + if (addr < PECI_BASE_ADDR && addr > PECI_BASE_ADDR + PECI_OFFSET_MAX) + return -EINVAL; + + return 0; +} + +static int peci_check_client_busy(struct device *dev, void *client_new_p) +{ + struct peci_client *client = peci_verify_client(dev); + struct peci_client *client_new = client_new_p; + + if (client && client->addr == client_new->addr) + return -EBUSY; + + return 0; +} + +/** + * peci_get_cpu_id - read CPU ID from the Package Configuration Space of CPU + * @adapter: pointer to peci_adapter + * @addr: address of the PECI client CPU + * @cpu_id: where the CPU ID will be stored + * Context: can sleep + * + * Return: zero on success, else a negative error code. + */ +int peci_get_cpu_id(struct peci_adapter *adapter, u8 addr, u32 *cpu_id) +{ + struct peci_rd_pkg_cfg_msg msg; + int ret; + + msg.addr = addr; + msg.index = PECI_MBX_INDEX_CPU_ID; + msg.param = PECI_PKG_ID_CPU_ID; + msg.rx_len = 4; + + ret = peci_command(adapter, PECI_CMD_RD_PKG_CFG, &msg); + if (msg.cc != PECI_DEV_CC_SUCCESS) + ret = -EAGAIN; + if (ret) + return ret; + + *cpu_id = le32_to_cpup((__le32 *)msg.pkg_config); + + return 0; +} +EXPORT_SYMBOL_GPL(peci_get_cpu_id); + +static struct peci_client *peci_new_device(struct peci_adapter *adapter, + struct peci_board_info const *info) +{ + struct peci_client *client; + int ret; + + /* Increase reference count for the adapter assigned */ + if (!peci_get_adapter(adapter->nr)) + return NULL; + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + goto err_put_adapter; + + client->adapter = adapter; + client->addr = info->addr; + strlcpy(client->name, info->type, sizeof(client->name)); + + ret = peci_check_addr_validity(client->addr); + if (ret) { + dev_err(&adapter->dev, "Invalid PECI CPU address 0x%02hx\n", + client->addr); + goto err_free_client_silent; + } + + /* Check online status of client */ + ret = peci_detect(adapter, client->addr); + if (ret) + goto err_free_client; + + ret = device_for_each_child(&adapter->dev, client, + peci_check_client_busy); + if (ret) + goto err_free_client; + + client->dev.parent = &client->adapter->dev; + client->dev.bus = &peci_bus_type; + client->dev.type = &peci_client_type; + client->dev.of_node = of_node_get(info->of_node); + dev_set_name(&client->dev, "%d-%02x", adapter->nr, client->addr); + + ret = device_register(&client->dev); + if (ret) + goto err_put_of_node; + + dev_dbg(&adapter->dev, "client [%s] registered with bus id %s\n", + client->name, dev_name(&client->dev)); + + return client; + +err_put_of_node: + of_node_put(info->of_node); +err_free_client: + dev_err(&adapter->dev, + "Failed to register peci client %s at 0x%02x (%d)\n", + client->name, client->addr, ret); +err_free_client_silent: + kfree(client); +err_put_adapter: + peci_put_adapter(adapter); + + return NULL; +} + +static void peci_unregister_device(struct peci_client *client) +{ + if (!client) + return; + + if (client->dev.of_node) { + of_node_clear_flag(client->dev.of_node, OF_POPULATED); + of_node_put(client->dev.of_node); + } + + device_unregister(&client->dev); +} + +static int peci_unregister_client(struct device *dev, void *dummy) +{ + struct peci_client *client = peci_verify_client(dev); + + peci_unregister_device(client); + + return 0; +} + +static void peci_adapter_dev_release(struct device *dev) +{ + struct peci_adapter *adapter = to_peci_adapter(dev); + + dev_dbg(dev, "%s: %s\n", __func__, adapter->name); + mutex_destroy(&adapter->userspace_clients_lock); + mutex_destroy(&adapter->bus_lock); + kfree(adapter); +} + +static ssize_t peci_sysfs_new_device(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct peci_adapter *adapter = to_peci_adapter(dev); + struct peci_board_info info = {}; + struct peci_client *client; + char *blank, end; + short addr; + int ret; + + /* Parse device type */ + blank = strchr(buf, ' '); + if (!blank) { + dev_err(dev, "%s: Missing parameters\n", "new_device"); + return -EINVAL; + } + if (blank - buf > PECI_NAME_SIZE - 1) { + dev_err(dev, "%s: Invalid device type\n", "new_device"); + return -EINVAL; + } + memcpy(info.type, buf, blank - buf); + + /* Parse remaining parameters, reject extra parameters */ + ret = sscanf(++blank, "%hi%c", &addr, &end); + if (ret < 1) { + dev_err(dev, "%s: Can't parse client address\n", "new_device"); + return -EINVAL; + } + if (ret > 1 && end != '\n') { + dev_err(dev, "%s: Extra parameters\n", "new_device"); + return -EINVAL; + } + + info.addr = (u8)addr; + client = peci_new_device(adapter, &info); + if (!client) + return -EINVAL; + + /* Keep track of the added device */ + mutex_lock(&adapter->userspace_clients_lock); + list_add_tail(&client->detected, &adapter->userspace_clients); + mutex_unlock(&adapter->userspace_clients_lock); + dev_dbg(dev, "%s: Instantiated device %s at 0x%02hx\n", "new_device", + info.type, info.addr); + + return count; +} +static DEVICE_ATTR(new_device, 0200, NULL, peci_sysfs_new_device); + +static ssize_t peci_sysfs_delete_device(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct peci_adapter *adapter = to_peci_adapter(dev); + struct peci_client *client, *next; + struct peci_board_info info = {}; + char *blank, end; + short addr; + int ret; + + /* Parse device type */ + blank = strchr(buf, ' '); + if (!blank) { + dev_err(dev, "%s: Missing parameters\n", "delete_device"); + return -EINVAL; + } + if (blank - buf > PECI_NAME_SIZE - 1) { + dev_err(dev, "%s: Invalid device type\n", "delete_device"); + return -EINVAL; + } + memcpy(info.type, buf, blank - buf); + + /* Parse remaining parameters, reject extra parameters */ + ret = sscanf(++blank, "%hi%c", &addr, &end); + if (ret < 1) { + dev_err(dev, "%s: Can't parse client address\n", + "delete_device"); + return -EINVAL; + } + if (ret > 1 && end != '\n') { + dev_err(dev, "%s: Extra parameters\n", "delete_device"); + return -EINVAL; + } + + info.addr = (u8)addr; + + /* Make sure the device was added through sysfs */ + ret = -ENOENT; + mutex_lock(&adapter->userspace_clients_lock); + list_for_each_entry_safe(client, next, &adapter->userspace_clients, + detected) { + if (client->addr == info.addr && + !strncmp(client->name, info.type, PECI_NAME_SIZE)) { + dev_dbg(dev, "%s: Deleting device %s at 0x%02hx\n", + "delete_device", client->name, client->addr); + list_del(&client->detected); + peci_unregister_device(client); + ret = count; + break; + } + } + mutex_unlock(&adapter->userspace_clients_lock); + + if (ret < 0) + dev_dbg(dev, "%s: Can't find device in list\n", + "delete_device"); + + return ret; +} +static DEVICE_ATTR_IGNORE_LOCKDEP(delete_device, 0200, NULL, + peci_sysfs_delete_device); + +static struct attribute *peci_adapter_attrs[] = { + &dev_attr_name.attr, + &dev_attr_new_device.attr, + &dev_attr_delete_device.attr, + NULL +}; +ATTRIBUTE_GROUPS(peci_adapter); + +struct device_type peci_adapter_type = { + .groups = peci_adapter_groups, + .release = peci_adapter_dev_release, +}; +EXPORT_SYMBOL_GPL(peci_adapter_type); + +/** + * peci_verify_adapter - return parameter as peci_adapter, or NULL + * @dev: device, probably from some driver model iterator + * + * Return: pointer to peci_adapter on success, else NULL. + */ +struct peci_adapter *peci_verify_adapter(struct device *dev) +{ + return (dev->type == &peci_adapter_type) + ? to_peci_adapter(dev) + : NULL; +} +EXPORT_SYMBOL_GPL(peci_verify_adapter); + +#if IS_ENABLED(CONFIG_OF) +static struct peci_client *peci_of_register_device(struct peci_adapter *adapter, + struct device_node *node) +{ + struct peci_board_info info = {}; + struct peci_client *client; + u32 addr; + int ret; + + dev_dbg(&adapter->dev, "register %pOF\n", node); + + ret = of_property_read_u32(node, "reg", &addr); + if (ret) { + dev_err(&adapter->dev, "invalid reg on %pOF\n", node); + return ERR_PTR(ret); + } + + info.addr = addr; + info.of_node = node; + + client = peci_new_device(adapter, &info); + if (!client) + client = ERR_PTR(-EINVAL); + + return client; +} + +static void peci_of_register_devices(struct peci_adapter *adapter) +{ + struct device_node *bus, *node; + struct peci_client *client; + + /* Only register child devices if the adapter has a node pointer set */ + if (!adapter->dev.of_node) + return; + + bus = of_get_child_by_name(adapter->dev.of_node, "peci-bus"); + if (!bus) + bus = of_node_get(adapter->dev.of_node); + + for_each_available_child_of_node(bus, node) { + if (of_node_test_and_set_flag(node, OF_POPULATED)) + continue; + + client = peci_of_register_device(adapter, node); + if (IS_ERR(client)) { + dev_warn(&adapter->dev, + "Failed to create PECI device for %pOF\n", + node); + of_node_clear_flag(node, OF_POPULATED); + } + } + + of_node_put(bus); +} +#else /* CONFIG_OF */ +static void peci_of_register_devices(struct peci_adapter *adapter) { } +#endif /* CONFIG_OF */ + +#if IS_ENABLED(CONFIG_OF_DYNAMIC) +static int peci_of_match_node(struct device *dev, const void *data) +{ + return dev->of_node == data; +} + +/* must call put_device() when done with returned peci_client device */ +static struct peci_client *peci_of_find_device(struct device_node *node) +{ + struct peci_client *client; + struct device *dev; + + dev = bus_find_device(&peci_bus_type, NULL, node, peci_of_match_node); + if (!dev) + return NULL; + + client = peci_verify_client(dev); + if (!client) + put_device(dev); + + return client; +} + +/* must call put_device() when done with returned peci_adapter device */ +static struct peci_adapter *peci_of_find_adapter(struct device_node *node) +{ + struct peci_adapter *adapter; + struct device *dev; + + dev = bus_find_device(&peci_bus_type, NULL, node, peci_of_match_node); + if (!dev) + return NULL; + + adapter = peci_verify_adapter(dev); + if (!adapter) + put_device(dev); + + return adapter; +} + +static int peci_of_notify(struct notifier_block *nb, ulong action, void *arg) +{ + struct of_reconfig_data *rd = arg; + struct peci_adapter *adapter; + struct peci_client *client; + + switch (of_reconfig_get_state_change(action, rd)) { + case OF_RECONFIG_CHANGE_ADD: + adapter = peci_of_find_adapter(rd->dn->parent); + if (!adapter) + return NOTIFY_OK; /* not for us */ + + if (of_node_test_and_set_flag(rd->dn, OF_POPULATED)) { + put_device(&adapter->dev); + return NOTIFY_OK; + } + + client = peci_of_register_device(adapter, rd->dn); + put_device(&adapter->dev); + + if (IS_ERR(client)) { + dev_err(&adapter->dev, + "failed to create client for '%pOF'\n", rd->dn); + of_node_clear_flag(rd->dn, OF_POPULATED); + return notifier_from_errno(PTR_ERR(client)); + } + break; + case OF_RECONFIG_CHANGE_REMOVE: + /* already depopulated? */ + if (!of_node_check_flag(rd->dn, OF_POPULATED)) + return NOTIFY_OK; + + /* find our device by node */ + client = peci_of_find_device(rd->dn); + if (!client) + return NOTIFY_OK; /* no? not meant for us */ + + /* unregister takes one ref away */ + peci_unregister_device(client); + + /* and put the reference of the find */ + put_device(&client->dev); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block peci_of_notifier = { + .notifier_call = peci_of_notify, +}; +#else /* CONFIG_OF_DYNAMIC */ +extern struct notifier_block peci_of_notifier; +#endif /* CONFIG_OF_DYNAMIC */ + +/** + * peci_alloc_adapter - allocate a PECI adapter + * @dev: the adapter, possibly using the platform_bus + * @size: how much zeroed driver-private data to allocate; the pointer to this + * memory is in the driver_data field of the returned device, + * accessible with peci_get_adapdata(). + * Context: can sleep + * + * This call is used only by PECI adapter drivers, which are the only ones + * directly touching chip registers. It's how they allocate a peci_adapter + * structure, prior to calling peci_add_adapter(). + * + * This must be called from context that can sleep. + * + * The caller is responsible for initializing the adapter's methods before + * calling peci_add_adapter(); and (after errors while adding the device) + * calling put_device() to prevent a memory leak. + * + * Return: the peci_adapter structure on success, else NULL. + */ +struct peci_adapter *peci_alloc_adapter(struct device *dev, uint size) +{ + struct peci_adapter *adapter; + + if (!dev) + return NULL; + + adapter = kzalloc(size + sizeof(*adapter), GFP_KERNEL); + if (!adapter) + return NULL; + + device_initialize(&adapter->dev); + adapter->dev.parent = dev; + adapter->dev.bus = &peci_bus_type; + adapter->dev.type = &peci_adapter_type; + peci_set_adapdata(adapter, &adapter[1]); + + return adapter; +} +EXPORT_SYMBOL_GPL(peci_alloc_adapter); + +static int peci_register_adapter(struct peci_adapter *adapter) +{ + int ret = -EINVAL; + + /* Can't register until after driver model init */ + if (WARN_ON(!is_registered)) + goto err_free_idr; + + if (WARN(!adapter->name[0], "peci adapter has no name")) + goto err_free_idr; + + if (WARN(!adapter->xfer, "peci adapter has no xfer function\n")) + goto err_free_idr; + + mutex_init(&adapter->bus_lock); + mutex_init(&adapter->userspace_clients_lock); + INIT_LIST_HEAD(&adapter->userspace_clients); + + dev_set_name(&adapter->dev, "peci-%d", adapter->nr); + + ret = device_add(&adapter->dev); + if (ret) { + pr_err("adapter '%s': can't add device (%d)\n", + adapter->name, ret); + goto err_free_idr; + } + + dev_dbg(&adapter->dev, "adapter [%s] registered\n", adapter->name); + + pm_runtime_no_callbacks(&adapter->dev); + pm_suspend_ignore_children(&adapter->dev, true); + pm_runtime_enable(&adapter->dev); + + /* create pre-declared device nodes */ + peci_of_register_devices(adapter); + + return 0; + +err_free_idr: + mutex_lock(&core_lock); + idr_remove(&peci_adapter_idr, adapter->nr); + mutex_unlock(&core_lock); + return ret; +} + +static int peci_add_numbered_adapter(struct peci_adapter *adapter) +{ + int id; + + mutex_lock(&core_lock); + id = idr_alloc(&peci_adapter_idr, adapter, + adapter->nr, adapter->nr + 1, GFP_KERNEL); + mutex_unlock(&core_lock); + if (WARN(id < 0, "couldn't get idr")) + return id == -ENOSPC ? -EBUSY : id; + + return peci_register_adapter(adapter); +} + +/** + * peci_add_adapter - add a PECI adapter + * @adapter: initialized adapter, originally from peci_alloc_adapter() + * Context: can sleep + * + * PECI adapters connect to their drivers using some non-PECI bus, + * such as the platform bus. The final stage of probe() in that code + * includes calling peci_add_adapter() to hook up to this PECI bus glue. + * + * This must be called from context that can sleep. + * + * It returns zero on success, else a negative error code (dropping the + * adapter's refcount). After a successful return, the caller is responsible + * for calling peci_del_adapter(). + * + * Return: zero on success, else a negative error code. + */ +int peci_add_adapter(struct peci_adapter *adapter) +{ + struct device *dev = &adapter->dev; + int id; + + id = of_alias_get_id(dev->of_node, "peci"); + if (id >= 0) { + adapter->nr = id; + return peci_add_numbered_adapter(adapter); + } + + mutex_lock(&core_lock); + id = idr_alloc(&peci_adapter_idr, adapter, 0, 0, GFP_KERNEL); + mutex_unlock(&core_lock); + if (WARN(id < 0, "couldn't get idr")) + return id; + + adapter->nr = id; + + return peci_register_adapter(adapter); +} +EXPORT_SYMBOL_GPL(peci_add_adapter); + +/** + * peci_del_adapter - delete a PECI adapter + * @adapter: the adpater being deleted + * Context: can sleep + * + * This call is used only by PECI adpater drivers, which are the only ones + * directly touching chip registers. + * + * This must be called from context that can sleep. + * + * Note that this function also drops a reference to the adapter. + */ +void peci_del_adapter(struct peci_adapter *adapter) +{ + struct peci_client *client, *next; + struct peci_adapter *found; + int nr; + + /* First make sure that this adapter was ever added */ + mutex_lock(&core_lock); + found = idr_find(&peci_adapter_idr, adapter->nr); + mutex_unlock(&core_lock); + + if (found != adapter) + return; + + /* Remove devices instantiated from sysfs */ + mutex_lock(&adapter->userspace_clients_lock); + list_for_each_entry_safe(client, next, &adapter->userspace_clients, + detected) { + dev_dbg(&adapter->dev, "Removing %s at 0x%x\n", client->name, + client->addr); + list_del(&client->detected); + peci_unregister_device(client); + } + mutex_unlock(&adapter->userspace_clients_lock); + + /* + * Detach any active clients. This can't fail, thus we do not + * check the returned value. + */ + device_for_each_child(&adapter->dev, NULL, peci_unregister_client); + + /* device name is gone after device_unregister */ + dev_dbg(&adapter->dev, "adapter [%s] unregistered\n", adapter->name); + + pm_runtime_disable(&adapter->dev); + nr = adapter->nr; + device_unregister(&adapter->dev); + + /* free bus id */ + mutex_lock(&core_lock); + idr_remove(&peci_adapter_idr, nr); + mutex_unlock(&core_lock); +} +EXPORT_SYMBOL_GPL(peci_del_adapter); + +int peci_for_each_dev(void *data, int (*fn)(struct device *, void *)) +{ + int ret; + + mutex_lock(&core_lock); + ret = bus_for_each_dev(&peci_bus_type, NULL, data, fn); + mutex_unlock(&core_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(peci_for_each_dev); + +/** + * peci_register_driver - register a PECI driver + * @owner: owner module of the driver being registered + * @driver: the driver being registered + * Context: can sleep + * + * Return: zero on success, else a negative error code. + */ +int peci_register_driver(struct module *owner, struct peci_driver *driver) +{ + int ret; + + /* Can't register until after driver model init */ + if (WARN_ON(!is_registered)) + return -EAGAIN; + + /* add the driver to the list of peci drivers in the driver core */ + driver->driver.owner = owner; + driver->driver.bus = &peci_bus_type; + + /* + * When registration returns, the driver core + * will have called probe() for all matching-but-unbound devices. + */ + ret = driver_register(&driver->driver); + if (ret) + return ret; + + pr_debug("driver [%s] registered\n", driver->driver.name); + + return 0; +} +EXPORT_SYMBOL_GPL(peci_register_driver); + +/** + * peci_del_driver - unregister a PECI driver + * @driver: the driver being unregistered + * Context: can sleep + */ +void peci_del_driver(struct peci_driver *driver) +{ + driver_unregister(&driver->driver); + pr_debug("driver [%s] unregistered\n", driver->driver.name); +} +EXPORT_SYMBOL_GPL(peci_del_driver); + +static int __init peci_init(void) +{ + int ret; + + ret = bus_register(&peci_bus_type); + if (ret < 0) { + pr_err("peci: Failed to register PECI bus type!\n"); + return ret; + } + + crc8_populate_msb(peci_crc8_table, PECI_CRC8_POLYNOMIAL); + + if (IS_ENABLED(CONFIG_OF_DYNAMIC)) + WARN_ON(of_reconfig_notifier_register(&peci_of_notifier)); + + is_registered = true; + + return 0; +} + +static void __exit peci_exit(void) +{ + if (IS_ENABLED(CONFIG_OF_DYNAMIC)) + WARN_ON(of_reconfig_notifier_unregister(&peci_of_notifier)); + + bus_unregister(&peci_bus_type); +} + +subsys_initcall(peci_init); +module_exit(peci_exit); + +MODULE_AUTHOR("Jason M Biils <jason.m.bills@linux.intel.com>"); +MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>"); +MODULE_DESCRIPTION("PECI bus core module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/peci/peci-dev.c b/drivers/peci/peci-dev.c new file mode 100644 index 000000000000..e0fe09467a80 --- /dev/null +++ b/drivers/peci/peci-dev.c @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018-2019 Intel Corporation + +#include <linux/cdev.h> +#include <linux/fs.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/peci.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +/* + * A peci_dev represents an peci_adapter ... an PECI or SMBus master, not a + * slave (peci_client) with which messages will be exchanged. It's coupled + * with a character special file which is accessed by user mode drivers. + * + * The list of peci_dev structures is parallel to the peci_adapter lists + * maintained by the driver model, and is updated using bus notifications. + */ +struct peci_dev { + struct list_head list; + struct peci_adapter *adapter; + struct device *dev; + struct cdev cdev; +}; + +#define PECI_MINORS MINORMASK + +static dev_t peci_devt; +static LIST_HEAD(peci_dev_list); +static DEFINE_SPINLOCK(peci_dev_list_lock); + +static struct peci_dev *peci_dev_get_by_minor(uint index) +{ + struct peci_dev *peci_dev; + + spin_lock(&peci_dev_list_lock); + list_for_each_entry(peci_dev, &peci_dev_list, list) { + if (peci_dev->adapter->nr == index) + goto found; + } + peci_dev = NULL; +found: + spin_unlock(&peci_dev_list_lock); + + return peci_dev; +} + +static struct peci_dev *peci_dev_alloc(struct peci_adapter *adapter) +{ + struct peci_dev *peci_dev; + + if (adapter->nr >= PECI_MINORS) { + dev_err(&adapter->dev, "Out of device minors (%d)\n", + adapter->nr); + return ERR_PTR(-ENODEV); + } + + peci_dev = kzalloc(sizeof(*peci_dev), GFP_KERNEL); + if (!peci_dev) + return ERR_PTR(-ENOMEM); + peci_dev->adapter = adapter; + + spin_lock(&peci_dev_list_lock); + list_add_tail(&peci_dev->list, &peci_dev_list); + spin_unlock(&peci_dev_list_lock); + + return peci_dev; +} + +static void peci_dev_put(struct peci_dev *peci_dev) +{ + spin_lock(&peci_dev_list_lock); + list_del(&peci_dev->list); + spin_unlock(&peci_dev_list_lock); + kfree(peci_dev); +} + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct peci_dev *peci_dev = peci_dev_get_by_minor(MINOR(dev->devt)); + + if (!peci_dev) + return -ENODEV; + + return sprintf(buf, "%s\n", peci_dev->adapter->name); +} +static DEVICE_ATTR_RO(name); + +static struct attribute *peci_dev_attrs[] = { + &dev_attr_name.attr, + NULL, +}; +ATTRIBUTE_GROUPS(peci_dev); + +static long peci_dev_ioctl(struct file *file, uint iocmd, ulong arg) +{ + struct peci_dev *peci_dev = file->private_data; + void __user *umsg = (void __user *)arg; + struct peci_xfer_msg *xmsg = NULL; + struct peci_xfer_msg uxmsg; + enum peci_cmd cmd; + u8 *msg = NULL; + uint msg_len; + int ret; + + cmd = _IOC_NR(iocmd); + msg_len = _IOC_SIZE(iocmd); + + switch (cmd) { + case PECI_CMD_XFER: + if (msg_len != sizeof(struct peci_xfer_msg)) { + ret = -EFAULT; + break; + } + + if (copy_from_user(&uxmsg, umsg, msg_len)) { + ret = -EFAULT; + break; + } + + xmsg = peci_get_xfer_msg(uxmsg.tx_len, uxmsg.rx_len); + if (IS_ERR(xmsg)) { + ret = PTR_ERR(xmsg); + break; + } + + if (uxmsg.tx_len && + copy_from_user(xmsg->tx_buf, (__u8 __user *)uxmsg.tx_buf, + uxmsg.tx_len)) { + ret = -EFAULT; + break; + } + + xmsg->addr = uxmsg.addr; + xmsg->tx_len = uxmsg.tx_len; + xmsg->rx_len = uxmsg.rx_len; + + ret = peci_command(peci_dev->adapter, cmd, xmsg); + if (!ret && xmsg->rx_len && + copy_to_user((__u8 __user *)uxmsg.rx_buf, xmsg->rx_buf, + xmsg->rx_len)) + ret = -EFAULT; + + break; + + default: + msg = memdup_user(umsg, msg_len); + if (IS_ERR(msg)) { + ret = PTR_ERR(msg); + break; + } + + ret = peci_command(peci_dev->adapter, cmd, msg); + if ((!ret || ret == -ETIMEDOUT) && + copy_to_user(umsg, msg, msg_len)) + ret = -EFAULT; + + break; + } + + peci_put_xfer_msg(xmsg); + kfree(msg); + + return (long)ret; +} + +static int peci_dev_open(struct inode *inode, struct file *file) +{ + struct peci_adapter *adapter; + struct peci_dev *peci_dev; + + peci_dev = peci_dev_get_by_minor(iminor(inode)); + if (!peci_dev) + return -ENODEV; + + adapter = peci_get_adapter(peci_dev->adapter->nr); + if (!adapter) + return -ENODEV; + + file->private_data = peci_dev; + + return 0; +} + +static int peci_dev_release(struct inode *inode, struct file *file) +{ + struct peci_dev *peci_dev = file->private_data; + + peci_put_adapter(peci_dev->adapter); + file->private_data = NULL; + + return 0; +} + +static const struct file_operations peci_dev_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = peci_dev_ioctl, + .open = peci_dev_open, + .release = peci_dev_release, + .llseek = no_llseek, +}; + +static struct class *peci_dev_class; + +static int peci_dev_attach_adapter(struct device *dev, void *dummy) +{ + struct peci_adapter *adapter; + struct peci_dev *peci_dev; + dev_t devt; + int ret; + + if (dev->type != &peci_adapter_type) + return 0; + + adapter = to_peci_adapter(dev); + peci_dev = peci_dev_alloc(adapter); + if (IS_ERR(peci_dev)) + return PTR_ERR(peci_dev); + + cdev_init(&peci_dev->cdev, &peci_dev_fops); + peci_dev->cdev.owner = THIS_MODULE; + devt = MKDEV(MAJOR(peci_devt), adapter->nr); + + ret = cdev_add(&peci_dev->cdev, devt, 1); + if (ret) + goto err_put_dev; + + /* register this peci device with the driver core */ + peci_dev->dev = device_create(peci_dev_class, &adapter->dev, devt, NULL, + "peci-%d", adapter->nr); + if (IS_ERR(peci_dev->dev)) { + ret = PTR_ERR(peci_dev->dev); + goto err_del_cdev; + } + + dev_info(dev, "cdev of adapter [%s] registered as minor %d\n", + adapter->name, adapter->nr); + + return 0; + +err_del_cdev: + cdev_del(&peci_dev->cdev); +err_put_dev: + peci_dev_put(peci_dev); + + return ret; +} + +static int peci_dev_detach_adapter(struct device *dev, void *dummy) +{ + struct peci_adapter *adapter; + struct peci_dev *peci_dev; + dev_t devt; + + if (dev->type != &peci_adapter_type) + return 0; + + adapter = to_peci_adapter(dev); + peci_dev = peci_dev_get_by_minor(adapter->nr); + if (!peci_dev) + return 0; + + cdev_del(&peci_dev->cdev); + devt = peci_dev->dev->devt; + peci_dev_put(peci_dev); + device_destroy(peci_dev_class, devt); + + dev_info(dev, "cdev of adapter [%s] unregistered\n", adapter->name); + + return 0; +} + +static int peci_dev_notifier_call(struct notifier_block *nb, ulong action, + void *data) +{ + struct device *dev = data; + + switch (action) { + case BUS_NOTIFY_ADD_DEVICE: + return peci_dev_attach_adapter(dev, NULL); + case BUS_NOTIFY_DEL_DEVICE: + return peci_dev_detach_adapter(dev, NULL); + } + + return 0; +} + +static struct notifier_block peci_dev_notifier = { + .notifier_call = peci_dev_notifier_call, +}; + +static int __init peci_dev_init(void) +{ + int ret; + + pr_debug("peci /dev entries driver\n"); + + ret = alloc_chrdev_region(&peci_devt, 0, PECI_MINORS, "peci"); + if (ret < 0) { + pr_err("peci: Failed to allocate chr dev region!\n"); + bus_unregister(&peci_bus_type); + goto err; + } + + peci_dev_class = class_create(THIS_MODULE, KBUILD_MODNAME); + if (IS_ERR(peci_dev_class)) { + ret = PTR_ERR(peci_dev_class); + goto err_unreg_chrdev; + } + peci_dev_class->dev_groups = peci_dev_groups; + + /* Keep track of adapters which will be added or removed later */ + ret = bus_register_notifier(&peci_bus_type, &peci_dev_notifier); + if (ret) + goto err_destroy_class; + + /* Bind to already existing adapters right away */ + peci_for_each_dev(NULL, peci_dev_attach_adapter); + + return 0; + +err_destroy_class: + class_destroy(peci_dev_class); +err_unreg_chrdev: + unregister_chrdev_region(peci_devt, PECI_MINORS); +err: + pr_err("%s: Driver Initialization failed\n", __FILE__); + + return ret; +} + +static void __exit peci_dev_exit(void) +{ + bus_unregister_notifier(&peci_bus_type, &peci_dev_notifier); + peci_for_each_dev(NULL, peci_dev_detach_adapter); + class_destroy(peci_dev_class); + unregister_chrdev_region(peci_devt, PECI_MINORS); +} + +module_init(peci_dev_init); +module_exit(peci_dev_exit); + +MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>"); +MODULE_DESCRIPTION("PECI /dev entries driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pinctrl/aspeed/pinctrl-aspeed-g6.c b/drivers/pinctrl/aspeed/pinctrl-aspeed-g6.c index fa32c3e9c9d1..7efe6dbe4398 100644 --- a/drivers/pinctrl/aspeed/pinctrl-aspeed-g6.c +++ b/drivers/pinctrl/aspeed/pinctrl-aspeed-g6.c @@ -46,6 +46,7 @@ #define SCU634 0x634 /* Disable GPIO Internal Pull-Down #5 */ #define SCU638 0x638 /* Disable GPIO Internal Pull-Down #6 */ #define SCU694 0x694 /* Multi-function Pin Control #25 */ +#define SCU69C 0x69C /* Multi-function Pin Control #27 */ #define SCUC20 0xC20 /* PCIE configuration Setting Control */ #define ASPEED_G6_NR_PINS 256 @@ -819,11 +820,13 @@ FUNC_DECL_2(PWM14, PWM14G0, PWM14G1); #define Y23 127 SIG_EXPR_LIST_DECL_SEMG(Y23, PWM15, PWM15G1, PWM15, SIG_DESC_SET(SCU41C, 31)); SIG_EXPR_LIST_DECL_SESG(Y23, THRUOUT3, THRU3, SIG_DESC_SET(SCU4BC, 31)); -PIN_DECL_2(Y23, GPIOP7, PWM15, THRUOUT3); +SIG_EXPR_LIST_DECL_SESG(Y23, HEARTBEAT, HEARTBEAT, SIG_DESC_SET(SCU69C, 31)); +PIN_DECL_3(Y23, GPIOP7, PWM15, THRUOUT3, HEARTBEAT); GROUP_DECL(PWM15G1, Y23); FUNC_DECL_2(PWM15, PWM15G0, PWM15G1); FUNC_GROUP_DECL(THRU3, AB24, Y23); +FUNC_GROUP_DECL(HEARTBEAT, Y23); #define AA25 128 SSSF_PIN_DECL(AA25, GPIOQ0, TACH0, SIG_DESC_SET(SCU430, 0)); @@ -1920,6 +1923,7 @@ static const struct aspeed_pin_group aspeed_g6_groups[] = { ASPEED_PINCTRL_GROUP(GPIU5), ASPEED_PINCTRL_GROUP(GPIU6), ASPEED_PINCTRL_GROUP(GPIU7), + ASPEED_PINCTRL_GROUP(HEARTBEAT), ASPEED_PINCTRL_GROUP(HVI3C3), ASPEED_PINCTRL_GROUP(HVI3C4), ASPEED_PINCTRL_GROUP(I2C1), @@ -2158,6 +2162,7 @@ static const struct aspeed_pin_function aspeed_g6_functions[] = { ASPEED_PINCTRL_FUNC(GPIU5), ASPEED_PINCTRL_FUNC(GPIU6), ASPEED_PINCTRL_FUNC(GPIU7), + ASPEED_PINCTRL_FUNC(HEARTBEAT), ASPEED_PINCTRL_FUNC(I2C1), ASPEED_PINCTRL_FUNC(I2C10), ASPEED_PINCTRL_FUNC(I2C11), diff --git a/drivers/pinctrl/aspeed/pinctrl-aspeed.c b/drivers/pinctrl/aspeed/pinctrl-aspeed.c index b625a657171e..53f3f8aec695 100644 --- a/drivers/pinctrl/aspeed/pinctrl-aspeed.c +++ b/drivers/pinctrl/aspeed/pinctrl-aspeed.c @@ -76,6 +76,9 @@ static int aspeed_sig_expr_enable(struct aspeed_pinmux_data *ctx, { int ret; + pr_debug("Enabling signal %s for %s\n", expr->signal, + expr->function); + ret = aspeed_sig_expr_eval(ctx, expr, true); if (ret < 0) return ret; @@ -91,6 +94,9 @@ static int aspeed_sig_expr_disable(struct aspeed_pinmux_data *ctx, { int ret; + pr_debug("Disabling signal %s for %s\n", expr->signal, + expr->function); + ret = aspeed_sig_expr_eval(ctx, expr, true); if (ret < 0) return ret; @@ -229,7 +235,7 @@ int aspeed_pinmux_set_mux(struct pinctrl_dev *pctldev, unsigned int function, const struct aspeed_sig_expr **funcs; const struct aspeed_sig_expr ***prios; - pr_debug("Muxing pin %d for %s\n", pin, pfunc->name); + pr_debug("Muxing pin %s for %s\n", pdesc->name, pfunc->name); if (!pdesc) return -EINVAL; @@ -269,6 +275,9 @@ int aspeed_pinmux_set_mux(struct pinctrl_dev *pctldev, unsigned int function, ret = aspeed_sig_expr_enable(&pdata->pinmux, expr); if (ret) return ret; + + pr_debug("Muxed pin %s as %s for %s\n", pdesc->name, expr->signal, + expr->function); } return 0; @@ -317,6 +326,8 @@ int aspeed_gpio_request_enable(struct pinctrl_dev *pctldev, if (!prios) return -ENXIO; + pr_debug("Muxing pin %s for GPIO\n", pdesc->name); + /* Disable any functions of higher priority than GPIO */ while ((funcs = *prios)) { if (aspeed_gpio_in_exprs(funcs)) @@ -346,14 +357,22 @@ int aspeed_gpio_request_enable(struct pinctrl_dev *pctldev, * lowest-priority signal type. As such it has no associated * expression. */ - if (!expr) + if (!expr) { + pr_debug("Muxed pin %s as GPIO\n", pdesc->name); return 0; + } /* * If GPIO is not the lowest priority signal type, assume there is only * one expression defined to enable the GPIO function */ - return aspeed_sig_expr_enable(&pdata->pinmux, expr); + ret = aspeed_sig_expr_enable(&pdata->pinmux, expr); + if (ret) + return ret; + + pr_debug("Muxed pin %s as %s\n", pdesc->name, expr->signal); + + return 0; } int aspeed_pinctrl_probe(struct platform_device *pdev, diff --git a/drivers/reset/reset-simple.c b/drivers/reset/reset-simple.c index 067e7e7b34f1..795c9063fe7b 100644 --- a/drivers/reset/reset-simple.c +++ b/drivers/reset/reset-simple.c @@ -125,6 +125,7 @@ static const struct of_device_id reset_simple_dt_ids[] = { .data = &reset_simple_active_low }, { .compatible = "aspeed,ast2400-lpc-reset" }, { .compatible = "aspeed,ast2500-lpc-reset" }, + { .compatible = "aspeed,ast2600-lpc-reset" }, { .compatible = "bitmain,bm1880-reset", .data = &reset_simple_active_low }, { .compatible = "snps,dw-high-reset" }, diff --git a/drivers/soc/aspeed/Kconfig b/drivers/soc/aspeed/Kconfig index c95fa30f1a76..2665ca89bdf8 100644 --- a/drivers/soc/aspeed/Kconfig +++ b/drivers/soc/aspeed/Kconfig @@ -5,6 +5,14 @@ config SOC_ASPEED def_bool y depends on ARCH_ASPEED || COMPILE_TEST +config ASPEED_BMC_MISC + bool "Miscellaneous ASPEED BMC interfaces" + depends on ARCH_ASPEED || COMPILE_TEST + default ARCH_ASPEED + help + Say yes to expose VGA and LPC scratch registers, and other + miscellaneous control interfaces specific to the ASPEED BMC SoCs + config ASPEED_LPC_CTRL depends on SOC_ASPEED && REGMAP && MFD_SYSCON tristate "Aspeed ast2400/2500 HOST LPC to BMC bridge control" @@ -29,4 +37,12 @@ config ASPEED_P2A_CTRL ioctl()s, the driver also provides an interface for userspace mappings to a pre-defined region. +config ASPEED_XDMA + tristate "Aspeed XDMA Engine Driver" + depends on SOC_ASPEED && REGMAP && MFD_SYSCON && HAS_DMA + help + Enable support for the Aspeed XDMA Engine found on the Aspeed AST2XXX + SOCs. The XDMA engine can perform automatic PCI DMA operations + between the AST2XXX (acting as a BMC) and a host processor. + endmenu diff --git a/drivers/soc/aspeed/Makefile b/drivers/soc/aspeed/Makefile index b64be47f2b1f..217d876fec25 100644 --- a/drivers/soc/aspeed/Makefile +++ b/drivers/soc/aspeed/Makefile @@ -1,4 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_ASPEED_BMC_MISC) += aspeed-bmc-misc.o obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o obj-$(CONFIG_ASPEED_P2A_CTRL) += aspeed-p2a-ctrl.o +obj-$(CONFIG_ASPEED_XDMA) += aspeed-xdma.o diff --git a/drivers/soc/aspeed/aspeed-bmc-misc.c b/drivers/soc/aspeed/aspeed-bmc-misc.c new file mode 100644 index 000000000000..314007bad74f --- /dev/null +++ b/drivers/soc/aspeed/aspeed-bmc-misc.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2018 IBM Corp. + +#include <linux/kobject.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> + +#define DEVICE_NAME "aspeed-bmc-misc" + +struct aspeed_bmc_ctrl { + const char *name; + u32 offset; + u32 mask; + u32 shift; + struct regmap *map; + struct kobj_attribute attr; +}; + +struct aspeed_bmc_misc { + struct device *dev; + struct regmap *map; + struct aspeed_bmc_ctrl *ctrls; + int nr_ctrls; +}; + +static int aspeed_bmc_misc_parse_dt_child(struct device_node *child, + struct aspeed_bmc_ctrl *ctrl) +{ + int rc; + + /* Example child: + * + * ilpc2ahb { + * offset = <0x80>; + * bit-mask = <0x1>; + * bit-shift = <6>; + * label = "foo"; + * } + */ + if (of_property_read_string(child, "label", &ctrl->name)) + ctrl->name = child->name; + + rc = of_property_read_u32(child, "offset", &ctrl->offset); + if (rc < 0) + return rc; + + rc = of_property_read_u32(child, "bit-mask", &ctrl->mask); + if (rc < 0) + return rc; + + rc = of_property_read_u32(child, "bit-shift", &ctrl->shift); + if (rc < 0) + return rc; + + ctrl->mask <<= ctrl->shift; + + return 0; +} + +static int aspeed_bmc_misc_parse_dt(struct aspeed_bmc_misc *bmc, + struct device_node *parent) +{ + struct aspeed_bmc_ctrl *ctrl; + struct device_node *child; + int rc; + + bmc->nr_ctrls = of_get_child_count(parent); + bmc->ctrls = devm_kcalloc(bmc->dev, bmc->nr_ctrls, sizeof(*bmc->ctrls), + GFP_KERNEL); + if (!bmc->ctrls) + return -ENOMEM; + + ctrl = bmc->ctrls; + for_each_child_of_node(parent, child) { + rc = aspeed_bmc_misc_parse_dt_child(child, ctrl++); + if (rc < 0) { + of_node_put(child); + return rc; + } + } + + return 0; +} + +static ssize_t aspeed_bmc_misc_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct aspeed_bmc_ctrl *ctrl; + unsigned int val; + int rc; + + ctrl = container_of(attr, struct aspeed_bmc_ctrl, attr); + rc = regmap_read(ctrl->map, ctrl->offset, &val); + if (rc) + return rc; + + val &= ctrl->mask; + val >>= ctrl->shift; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t aspeed_bmc_misc_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct aspeed_bmc_ctrl *ctrl; + long val; + int rc; + + rc = kstrtol(buf, 0, &val); + if (rc) + return rc; + + ctrl = container_of(attr, struct aspeed_bmc_ctrl, attr); + val <<= ctrl->shift; + rc = regmap_update_bits(ctrl->map, ctrl->offset, ctrl->mask, val); + + return rc < 0 ? rc : count; +} + +static int aspeed_bmc_misc_add_sysfs_attr(struct aspeed_bmc_misc *bmc, + struct aspeed_bmc_ctrl *ctrl) +{ + ctrl->map = bmc->map; + + sysfs_attr_init(&ctrl->attr.attr); + ctrl->attr.attr.name = ctrl->name; + ctrl->attr.attr.mode = 0664; + ctrl->attr.show = aspeed_bmc_misc_show; + ctrl->attr.store = aspeed_bmc_misc_store; + + return sysfs_create_file(&bmc->dev->kobj, &ctrl->attr.attr); +} + +static int aspeed_bmc_misc_populate_sysfs(struct aspeed_bmc_misc *bmc) +{ + int rc; + int i; + + for (i = 0; i < bmc->nr_ctrls; i++) { + rc = aspeed_bmc_misc_add_sysfs_attr(bmc, &bmc->ctrls[i]); + if (rc < 0) + return rc; + } + + return 0; +} + +static int aspeed_bmc_misc_probe(struct platform_device *pdev) +{ + struct aspeed_bmc_misc *bmc; + int rc; + + bmc = devm_kzalloc(&pdev->dev, sizeof(*bmc), GFP_KERNEL); + if (!bmc) + return -ENOMEM; + + bmc->dev = &pdev->dev; + bmc->map = syscon_node_to_regmap(pdev->dev.parent->of_node); + if (IS_ERR(bmc->map)) + return PTR_ERR(bmc->map); + + rc = aspeed_bmc_misc_parse_dt(bmc, pdev->dev.of_node); + if (rc < 0) + return rc; + + return aspeed_bmc_misc_populate_sysfs(bmc); +} + +static const struct of_device_id aspeed_bmc_misc_match[] = { + { .compatible = "aspeed,bmc-misc" }, + { }, +}; + +static struct platform_driver aspeed_bmc_misc = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = aspeed_bmc_misc_match, + }, + .probe = aspeed_bmc_misc_probe, +}; + +module_platform_driver(aspeed_bmc_misc); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>"); diff --git a/drivers/soc/aspeed/aspeed-lpc-ctrl.c b/drivers/soc/aspeed/aspeed-lpc-ctrl.c index 01ed21e8bfee..ee2def4ffda3 100644 --- a/drivers/soc/aspeed/aspeed-lpc-ctrl.c +++ b/drivers/soc/aspeed/aspeed-lpc-ctrl.c @@ -4,6 +4,7 @@ */ #include <linux/clk.h> +#include <linux/log2.h> #include <linux/mfd/syscon.h> #include <linux/miscdevice.h> #include <linux/mm.h> @@ -21,6 +22,9 @@ #define HICR5_ENL2H BIT(8) #define HICR5_ENFWH BIT(10) +#define HICR6 0x4 +#define SW_FWH2AHB BIT(17) + #define HICR7 0x8 #define HICR8 0xc @@ -32,6 +36,7 @@ struct aspeed_lpc_ctrl { resource_size_t mem_size; u32 pnor_size; u32 pnor_base; + bool fwh2ahb; }; static struct aspeed_lpc_ctrl *file_aspeed_lpc_ctrl(struct file *file) @@ -177,6 +182,16 @@ static long aspeed_lpc_ctrl_ioctl(struct file *file, unsigned int cmd, return rc; /* + * Switch to FWH2AHB mode, AST2600 only. + * + * The other bits in this register are interrupt status bits + * that are cleared by writing 1. As we don't want to clear + * them, set only the bit of interest. + */ + if (lpc_ctrl->fwh2ahb) + regmap_write(lpc_ctrl->regmap, HICR6, SW_FWH2AHB); + + /* * Enable LPC FHW cycles. This is required for the host to * access the regions specified. */ @@ -241,6 +256,18 @@ static int aspeed_lpc_ctrl_probe(struct platform_device *pdev) lpc_ctrl->mem_size = resource_size(&resm); lpc_ctrl->mem_base = resm.start; + + if (!is_power_of_2(lpc_ctrl->mem_size)) { + dev_err(dev, "Reserved memory size must be a power of 2, got %u\n", + (unsigned int)lpc_ctrl->mem_size); + return -EINVAL; + } + + if (!IS_ALIGNED(lpc_ctrl->mem_base, lpc_ctrl->mem_size)) { + dev_err(dev, "Reserved memory must be naturally aligned for size %u\n", + (unsigned int)lpc_ctrl->mem_size); + return -EINVAL; + } } lpc_ctrl->regmap = syscon_node_to_regmap( @@ -261,6 +288,9 @@ static int aspeed_lpc_ctrl_probe(struct platform_device *pdev) return rc; } + if (of_device_is_compatible(dev->of_node, "aspeed,ast2600-lpc-ctrl")) + lpc_ctrl->fwh2ahb = true; + lpc_ctrl->miscdev.minor = MISC_DYNAMIC_MINOR; lpc_ctrl->miscdev.name = DEVICE_NAME; lpc_ctrl->miscdev.fops = &aspeed_lpc_ctrl_fops; @@ -291,6 +321,7 @@ static int aspeed_lpc_ctrl_remove(struct platform_device *pdev) static const struct of_device_id aspeed_lpc_ctrl_match[] = { { .compatible = "aspeed,ast2400-lpc-ctrl" }, { .compatible = "aspeed,ast2500-lpc-ctrl" }, + { .compatible = "aspeed,ast2600-lpc-ctrl" }, { }, }; diff --git a/drivers/soc/aspeed/aspeed-lpc-snoop.c b/drivers/soc/aspeed/aspeed-lpc-snoop.c index f3d8d53ab84d..682ba0eb4eba 100644 --- a/drivers/soc/aspeed/aspeed-lpc-snoop.c +++ b/drivers/soc/aspeed/aspeed-lpc-snoop.c @@ -325,6 +325,8 @@ static const struct of_device_id aspeed_lpc_snoop_match[] = { .data = &ast2400_model_data }, { .compatible = "aspeed,ast2500-lpc-snoop", .data = &ast2500_model_data }, + { .compatible = "aspeed,ast2600-lpc-snoop", + .data = &ast2500_model_data }, { }, }; diff --git a/drivers/soc/aspeed/aspeed-xdma.c b/drivers/soc/aspeed/aspeed-xdma.c new file mode 100644 index 000000000000..4d8af9e64471 --- /dev/null +++ b/drivers/soc/aspeed/aspeed-xdma.c @@ -0,0 +1,1205 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright IBM Corp 2019 + +#include <linux/aspeed-xdma.h> +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/fs.h> +#include <linux/genalloc.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/mfd/syscon.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_device.h> +#include <linux/of_reserved_mem.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/string.h> +#include <linux/uaccess.h> +#include <linux/wait.h> +#include <linux/workqueue.h> + +#define DEVICE_NAME "aspeed-xdma" + +#define SCU_AST2600_MISC_CTRL 0x0c0 +#define SCU_AST2600_MISC_CTRL_XDMA_BMC BIT(8) + +#define SCU_AST2500_PCIE_CONF 0x180 +#define SCU_AST2600_PCIE_CONF 0xc20 +#define SCU_PCIE_CONF_VGA_EN BIT(0) +#define SCU_PCIE_CONF_VGA_EN_MMIO BIT(1) +#define SCU_PCIE_CONF_VGA_EN_LPC BIT(2) +#define SCU_PCIE_CONF_VGA_EN_MSI BIT(3) +#define SCU_PCIE_CONF_VGA_EN_MCTP BIT(4) +#define SCU_PCIE_CONF_VGA_EN_IRQ BIT(5) +#define SCU_PCIE_CONF_VGA_EN_DMA BIT(6) +#define SCU_PCIE_CONF_BMC_EN BIT(8) +#define SCU_PCIE_CONF_BMC_EN_MMIO BIT(9) +#define SCU_PCIE_CONF_BMC_EN_MSI BIT(11) +#define SCU_PCIE_CONF_BMC_EN_MCTP BIT(12) +#define SCU_PCIE_CONF_BMC_EN_IRQ BIT(13) +#define SCU_PCIE_CONF_BMC_EN_DMA BIT(14) + +#define SCU_AST2500_BMC_CLASS_REV 0x19c +#define SCU_AST2600_BMC_CLASS_REV 0xc68 +#define SCU_BMC_CLASS_REV_XDMA 0xff000001 + +#define XDMA_CMDQ_SIZE PAGE_SIZE +#define XDMA_NUM_CMDS \ + (XDMA_CMDQ_SIZE / sizeof(struct aspeed_xdma_cmd)) + +/* Aspeed specification requires 100us after disabling the reset */ +#define XDMA_ENGINE_SETUP_TIME_MAX_US 1000 +#define XDMA_ENGINE_SETUP_TIME_MIN_US 100 + +#define XDMA_CMD_AST2500_PITCH_SHIFT 3 +#define XDMA_CMD_AST2500_PITCH_BMC GENMASK_ULL(62, 51) +#define XDMA_CMD_AST2500_PITCH_HOST GENMASK_ULL(46, 35) +#define XDMA_CMD_AST2500_PITCH_UPSTREAM BIT_ULL(31) +#define XDMA_CMD_AST2500_PITCH_ADDR GENMASK_ULL(29, 4) +#define XDMA_CMD_AST2500_PITCH_ID BIT_ULL(0) +#define XDMA_CMD_AST2500_CMD_IRQ_EN BIT_ULL(31) +#define XDMA_CMD_AST2500_CMD_LINE_NO GENMASK_ULL(27, 16) +#define XDMA_CMD_AST2500_CMD_IRQ_BMC BIT_ULL(15) +#define XDMA_CMD_AST2500_CMD_LINE_SIZE_SHIFT 4 +#define XDMA_CMD_AST2500_CMD_LINE_SIZE \ + GENMASK_ULL(14, XDMA_CMD_AST2500_CMD_LINE_SIZE_SHIFT) +#define XDMA_CMD_AST2500_CMD_ID BIT_ULL(1) + +#define XDMA_CMD_AST2600_PITCH_BMC GENMASK_ULL(62, 48) +#define XDMA_CMD_AST2600_PITCH_HOST GENMASK_ULL(46, 32) +#define XDMA_CMD_AST2600_PITCH_ADDR GENMASK_ULL(30, 0) +#define XDMA_CMD_AST2600_CMD_64_EN BIT_ULL(40) +#define XDMA_CMD_AST2600_CMD_IRQ_BMC BIT_ULL(37) +#define XDMA_CMD_AST2600_CMD_IRQ_HOST BIT_ULL(36) +#define XDMA_CMD_AST2600_CMD_UPSTREAM BIT_ULL(32) +#define XDMA_CMD_AST2600_CMD_LINE_NO GENMASK_ULL(27, 16) +#define XDMA_CMD_AST2600_CMD_LINE_SIZE GENMASK_ULL(14, 0) +#define XDMA_CMD_AST2600_CMD_MULTILINE_SIZE GENMASK_ULL(14, 12) + +#define XDMA_AST2500_QUEUE_ENTRY_SIZE 4 +#define XDMA_AST2500_HOST_CMDQ_ADDR0 0x00 +#define XDMA_AST2500_HOST_CMDQ_ENDP 0x04 +#define XDMA_AST2500_HOST_CMDQ_WRITEP 0x08 +#define XDMA_AST2500_HOST_CMDQ_READP 0x0c +#define XDMA_AST2500_BMC_CMDQ_ADDR 0x10 +#define XDMA_AST2500_BMC_CMDQ_ENDP 0x14 +#define XDMA_AST2500_BMC_CMDQ_WRITEP 0x18 +#define XDMA_AST2500_BMC_CMDQ_READP 0x1c +#define XDMA_BMC_CMDQ_READP_RESET 0xee882266 +#define XDMA_AST2500_CTRL 0x20 +#define XDMA_AST2500_CTRL_US_COMP BIT(4) +#define XDMA_AST2500_CTRL_DS_COMP BIT(5) +#define XDMA_AST2500_CTRL_DS_DIRTY BIT(6) +#define XDMA_AST2500_CTRL_DS_SIZE_256 BIT(17) +#define XDMA_AST2500_CTRL_DS_TIMEOUT BIT(28) +#define XDMA_AST2500_CTRL_DS_CHECK_ID BIT(29) +#define XDMA_AST2500_STATUS 0x24 +#define XDMA_AST2500_STATUS_US_COMP BIT(4) +#define XDMA_AST2500_STATUS_DS_COMP BIT(5) +#define XDMA_AST2500_STATUS_DS_DIRTY BIT(6) +#define XDMA_AST2500_INPRG_DS_CMD1 0x38 +#define XDMA_AST2500_INPRG_DS_CMD2 0x3c +#define XDMA_AST2500_INPRG_US_CMD00 0x40 +#define XDMA_AST2500_INPRG_US_CMD01 0x44 +#define XDMA_AST2500_INPRG_US_CMD10 0x48 +#define XDMA_AST2500_INPRG_US_CMD11 0x4c +#define XDMA_AST2500_INPRG_US_CMD20 0x50 +#define XDMA_AST2500_INPRG_US_CMD21 0x54 +#define XDMA_AST2500_HOST_CMDQ_ADDR1 0x60 +#define XDMA_AST2500_VGA_CMDQ_ADDR0 0x64 +#define XDMA_AST2500_VGA_CMDQ_ENDP 0x68 +#define XDMA_AST2500_VGA_CMDQ_WRITEP 0x6c +#define XDMA_AST2500_VGA_CMDQ_READP 0x70 +#define XDMA_AST2500_VGA_CMD_STATUS 0x74 +#define XDMA_AST2500_VGA_CMDQ_ADDR1 0x78 + +#define XDMA_AST2600_QUEUE_ENTRY_SIZE 2 +#define XDMA_AST2600_HOST_CMDQ_ADDR0 0x00 +#define XDMA_AST2600_HOST_CMDQ_ADDR1 0x04 +#define XDMA_AST2600_HOST_CMDQ_ENDP 0x08 +#define XDMA_AST2600_HOST_CMDQ_WRITEP 0x0c +#define XDMA_AST2600_HOST_CMDQ_READP 0x10 +#define XDMA_AST2600_BMC_CMDQ_ADDR 0x14 +#define XDMA_AST2600_BMC_CMDQ_ENDP 0x18 +#define XDMA_AST2600_BMC_CMDQ_WRITEP 0x1c +#define XDMA_AST2600_BMC_CMDQ_READP 0x20 +#define XDMA_AST2600_VGA_CMDQ_ADDR0 0x24 +#define XDMA_AST2600_VGA_CMDQ_ADDR1 0x28 +#define XDMA_AST2600_VGA_CMDQ_ENDP 0x2c +#define XDMA_AST2600_VGA_CMDQ_WRITEP 0x30 +#define XDMA_AST2600_VGA_CMDQ_READP 0x34 +#define XDMA_AST2600_CTRL 0x38 +#define XDMA_AST2600_CTRL_US_COMP BIT(16) +#define XDMA_AST2600_CTRL_DS_COMP BIT(17) +#define XDMA_AST2600_CTRL_DS_DIRTY BIT(18) +#define XDMA_AST2600_CTRL_DS_SIZE_256 BIT(20) +#define XDMA_AST2600_STATUS 0x3c +#define XDMA_AST2600_STATUS_US_COMP BIT(16) +#define XDMA_AST2600_STATUS_DS_COMP BIT(17) +#define XDMA_AST2600_STATUS_DS_DIRTY BIT(18) +#define XDMA_AST2600_INPRG_DS_CMD00 0x40 +#define XDMA_AST2600_INPRG_DS_CMD01 0x44 +#define XDMA_AST2600_INPRG_DS_CMD10 0x48 +#define XDMA_AST2600_INPRG_DS_CMD11 0x4c +#define XDMA_AST2600_INPRG_DS_CMD20 0x50 +#define XDMA_AST2600_INPRG_DS_CMD21 0x54 +#define XDMA_AST2600_INPRG_US_CMD00 0x60 +#define XDMA_AST2600_INPRG_US_CMD01 0x64 +#define XDMA_AST2600_INPRG_US_CMD10 0x68 +#define XDMA_AST2600_INPRG_US_CMD11 0x6c +#define XDMA_AST2600_INPRG_US_CMD20 0x70 +#define XDMA_AST2600_INPRG_US_CMD21 0x74 + +struct aspeed_xdma_cmd { + u64 host_addr; + u64 pitch; + u64 cmd; + u64 reserved; +}; + +struct aspeed_xdma_regs { + u8 bmc_cmdq_addr; + u8 bmc_cmdq_endp; + u8 bmc_cmdq_writep; + u8 bmc_cmdq_readp; + u8 control; + u8 status; +}; + +struct aspeed_xdma_status_bits { + u32 us_comp; + u32 ds_comp; + u32 ds_dirty; +}; + +struct aspeed_xdma; + +struct aspeed_xdma_chip { + u32 control; + u32 scu_bmc_class; + u32 scu_misc_ctrl; + u32 scu_pcie_conf; + unsigned int queue_entry_size; + struct aspeed_xdma_regs regs; + struct aspeed_xdma_status_bits status_bits; + unsigned int (*set_cmd)(struct aspeed_xdma *ctx, + struct aspeed_xdma_cmd cmds[2], + struct aspeed_xdma_op *op, u32 bmc_addr); +}; + +struct aspeed_xdma_client; + +struct aspeed_xdma { + struct kobject kobj; + const struct aspeed_xdma_chip *chip; + + int irq; + int pcie_irq; + struct clk *clock; + struct device *dev; + void __iomem *base; + resource_size_t res_size; + resource_size_t res_start; + struct reset_control *reset; + struct reset_control *reset_rc; + + /* Protects current_client */ + spinlock_t client_lock; + struct aspeed_xdma_client *current_client; + + /* Protects engine configuration */ + spinlock_t engine_lock; + struct aspeed_xdma_cmd *cmdq; + unsigned int cmd_idx; + bool in_reset; + bool upstream; + + /* Queue waiters for idle engine */ + wait_queue_head_t wait; + + struct work_struct reset_work; + + u32 mem_phys; + u32 mem_size; + void *mem_virt; + dma_addr_t mem_coherent; + dma_addr_t cmdq_phys; + struct gen_pool *pool; + + struct miscdevice misc; +}; + +struct aspeed_xdma_client { + struct aspeed_xdma *ctx; + + bool error; + bool in_progress; + void *virt; + dma_addr_t phys; + u32 size; +}; + +static u32 aspeed_xdma_readl(struct aspeed_xdma *ctx, u8 reg) +{ + u32 v = readl(ctx->base + reg); + + dev_dbg(ctx->dev, "read %02x[%08x]\n", reg, v); + return v; +} + +static void aspeed_xdma_writel(struct aspeed_xdma *ctx, u8 reg, u32 val) +{ + writel(val, ctx->base + reg); + dev_dbg(ctx->dev, "write %02x[%08x]\n", reg, val); +} + +static void aspeed_xdma_init_eng(struct aspeed_xdma *ctx) +{ + unsigned long flags; + + spin_lock_irqsave(&ctx->engine_lock, flags); + aspeed_xdma_writel(ctx, ctx->chip->regs.bmc_cmdq_endp, + ctx->chip->queue_entry_size * XDMA_NUM_CMDS); + aspeed_xdma_writel(ctx, ctx->chip->regs.bmc_cmdq_readp, + XDMA_BMC_CMDQ_READP_RESET); + aspeed_xdma_writel(ctx, ctx->chip->regs.bmc_cmdq_writep, 0); + aspeed_xdma_writel(ctx, ctx->chip->regs.control, ctx->chip->control); + aspeed_xdma_writel(ctx, ctx->chip->regs.bmc_cmdq_addr, ctx->cmdq_phys); + + ctx->cmd_idx = 0; + spin_unlock_irqrestore(&ctx->engine_lock, flags); +} + +static unsigned int aspeed_xdma_ast2500_set_cmd(struct aspeed_xdma *ctx, + struct aspeed_xdma_cmd cmds[2], + struct aspeed_xdma_op *op, + u32 bmc_addr) +{ + unsigned int rc = 1; + unsigned int pitch = 1; + unsigned int line_no = 1; + unsigned int line_size = op->len >> + XDMA_CMD_AST2500_CMD_LINE_SIZE_SHIFT; + u64 cmd = XDMA_CMD_AST2500_CMD_IRQ_EN | XDMA_CMD_AST2500_CMD_IRQ_BMC | + XDMA_CMD_AST2500_CMD_ID; + u64 cmd_pitch = (op->direction ? XDMA_CMD_AST2500_PITCH_UPSTREAM : 0) | + XDMA_CMD_AST2500_PITCH_ID; + + dev_dbg(ctx->dev, "xdma %s ast2500: bmc[%08x] len[%08x] host[%08x]\n", + op->direction ? "upstream" : "downstream", bmc_addr, op->len, + (u32)op->host_addr); + + if (op->len > XDMA_CMD_AST2500_CMD_LINE_SIZE) { + unsigned int rem; + unsigned int total; + + line_no = op->len / XDMA_CMD_AST2500_CMD_LINE_SIZE; + total = XDMA_CMD_AST2500_CMD_LINE_SIZE * line_no; + rem = (op->len - total) >> + XDMA_CMD_AST2500_CMD_LINE_SIZE_SHIFT; + line_size = XDMA_CMD_AST2500_CMD_LINE_SIZE; + pitch = line_size >> XDMA_CMD_AST2500_PITCH_SHIFT; + line_size >>= XDMA_CMD_AST2500_CMD_LINE_SIZE_SHIFT; + + if (rem) { + u32 rbmc = bmc_addr + total; + + cmds[1].host_addr = op->host_addr + (u64)total; + cmds[1].pitch = cmd_pitch | + ((u64)rbmc & XDMA_CMD_AST2500_PITCH_ADDR) | + FIELD_PREP(XDMA_CMD_AST2500_PITCH_HOST, 1) | + FIELD_PREP(XDMA_CMD_AST2500_PITCH_BMC, 1); + cmds[1].cmd = cmd | + FIELD_PREP(XDMA_CMD_AST2500_CMD_LINE_NO, 1) | + FIELD_PREP(XDMA_CMD_AST2500_CMD_LINE_SIZE, + rem); + cmds[1].reserved = 0ULL; + + print_hex_dump_debug("xdma rem ", DUMP_PREFIX_OFFSET, + 16, 1, &cmds[1], sizeof(*cmds), + true); + + cmd &= ~(XDMA_CMD_AST2500_CMD_IRQ_EN | + XDMA_CMD_AST2500_CMD_IRQ_BMC); + + rc++; + } + } + + cmds[0].host_addr = op->host_addr; + cmds[0].pitch = cmd_pitch | + ((u64)bmc_addr & XDMA_CMD_AST2500_PITCH_ADDR) | + FIELD_PREP(XDMA_CMD_AST2500_PITCH_HOST, pitch) | + FIELD_PREP(XDMA_CMD_AST2500_PITCH_BMC, pitch); + cmds[0].cmd = cmd | FIELD_PREP(XDMA_CMD_AST2500_CMD_LINE_NO, line_no) | + FIELD_PREP(XDMA_CMD_AST2500_CMD_LINE_SIZE, line_size); + cmds[0].reserved = 0ULL; + + print_hex_dump_debug("xdma cmd ", DUMP_PREFIX_OFFSET, 16, 1, cmds, + sizeof(*cmds), true); + + return rc; +} + +static unsigned int aspeed_xdma_ast2600_set_cmd(struct aspeed_xdma *ctx, + struct aspeed_xdma_cmd cmds[2], + struct aspeed_xdma_op *op, + u32 bmc_addr) +{ + unsigned int rc = 1; + unsigned int pitch = 1; + unsigned int line_no = 1; + unsigned int line_size = op->len; + u64 cmd = XDMA_CMD_AST2600_CMD_IRQ_BMC | + (op->direction ? XDMA_CMD_AST2600_CMD_UPSTREAM : 0); + + if (op->host_addr & 0xffffffff00000000ULL || + (op->host_addr + (u64)op->len) & 0xffffffff00000000ULL) + cmd |= XDMA_CMD_AST2600_CMD_64_EN; + + dev_dbg(ctx->dev, "xdma %s ast2600: bmc[%08x] len[%08x] " + "host[%016llx]\n", op->direction ? "upstream" : "downstream", + bmc_addr, op->len, op->host_addr); + + if (op->len > XDMA_CMD_AST2600_CMD_LINE_SIZE) { + unsigned int rem; + unsigned int total; + + line_no = op->len / XDMA_CMD_AST2600_CMD_MULTILINE_SIZE; + total = XDMA_CMD_AST2600_CMD_MULTILINE_SIZE * line_no; + rem = op->len - total; + line_size = XDMA_CMD_AST2600_CMD_MULTILINE_SIZE; + pitch = line_size; + + if (rem) { + u32 rbmc = bmc_addr + total; + + cmds[1].host_addr = op->host_addr + (u64)total; + cmds[1].pitch = + ((u64)rbmc & XDMA_CMD_AST2600_PITCH_ADDR) | + FIELD_PREP(XDMA_CMD_AST2600_PITCH_HOST, 1) | + FIELD_PREP(XDMA_CMD_AST2600_PITCH_BMC, 1); + cmds[1].cmd = cmd | + FIELD_PREP(XDMA_CMD_AST2600_CMD_LINE_NO, 1) | + FIELD_PREP(XDMA_CMD_AST2600_CMD_LINE_SIZE, + rem); + cmds[1].reserved = 0ULL; + + print_hex_dump_debug("xdma rem ", DUMP_PREFIX_OFFSET, + 16, 1, &cmds[1], sizeof(*cmds), + true); + + cmd &= ~XDMA_CMD_AST2600_CMD_IRQ_BMC; + + rc++; + } + } + + cmds[0].host_addr = op->host_addr; + cmds[0].pitch = ((u64)bmc_addr & XDMA_CMD_AST2600_PITCH_ADDR) | + FIELD_PREP(XDMA_CMD_AST2600_PITCH_HOST, pitch) | + FIELD_PREP(XDMA_CMD_AST2600_PITCH_BMC, pitch); + cmds[0].cmd = cmd | FIELD_PREP(XDMA_CMD_AST2600_CMD_LINE_NO, line_no) | + FIELD_PREP(XDMA_CMD_AST2600_CMD_LINE_SIZE, line_size); + cmds[0].reserved = 0ULL; + + print_hex_dump_debug("xdma cmd ", DUMP_PREFIX_OFFSET, 16, 1, cmds, + sizeof(*cmds), true); + + return rc; +} + +static int aspeed_xdma_start(struct aspeed_xdma *ctx, unsigned int num_cmds, + struct aspeed_xdma_cmd cmds[2], bool upstream, + struct aspeed_xdma_client *client) +{ + unsigned int i; + int rc = -EBUSY; + unsigned long flags; + + spin_lock_irqsave(&ctx->engine_lock, flags); + if (ctx->in_reset) + goto unlock; + + spin_lock(&ctx->client_lock); + if (ctx->current_client) { + spin_unlock(&ctx->client_lock); + goto unlock; + } + + client->error = false; + client->in_progress = true; + ctx->current_client = client; + spin_unlock(&ctx->client_lock); + + ctx->upstream = upstream; + for (i = 0; i < num_cmds; ++i) { + /* + * Use memcpy_toio here to get some barriers before starting + * the operation. The command(s) need to be in physical memory + * before the XDMA engine starts. + */ + memcpy_toio(&ctx->cmdq[ctx->cmd_idx], &cmds[i], + sizeof(struct aspeed_xdma_cmd)); + ctx->cmd_idx = (ctx->cmd_idx + 1) % XDMA_NUM_CMDS; + } + + aspeed_xdma_writel(ctx, ctx->chip->regs.bmc_cmdq_writep, + ctx->cmd_idx * ctx->chip->queue_entry_size); + rc = 0; + +unlock: + spin_unlock_irqrestore(&ctx->engine_lock, flags); + return rc; +} + +static void aspeed_xdma_done(struct aspeed_xdma *ctx, bool error) +{ + unsigned long flags; + + spin_lock_irqsave(&ctx->client_lock, flags); + if (ctx->current_client) { + ctx->current_client->error = error; + ctx->current_client->in_progress = false; + ctx->current_client = NULL; + } + spin_unlock_irqrestore(&ctx->client_lock, flags); + + wake_up_interruptible_all(&ctx->wait); +} + +static irqreturn_t aspeed_xdma_irq(int irq, void *arg) +{ + struct aspeed_xdma *ctx = arg; + u32 status; + + spin_lock(&ctx->engine_lock); + status = aspeed_xdma_readl(ctx, ctx->chip->regs.status); + + if (status & ctx->chip->status_bits.ds_dirty) { + aspeed_xdma_done(ctx, true); + } else { + if (status & ctx->chip->status_bits.us_comp) { + if (ctx->upstream) + aspeed_xdma_done(ctx, false); + } + + if (status & ctx->chip->status_bits.ds_comp) { + if (!ctx->upstream) + aspeed_xdma_done(ctx, false); + } + } + + aspeed_xdma_writel(ctx, ctx->chip->regs.status, status); + spin_unlock(&ctx->engine_lock); + + return IRQ_HANDLED; +} + +static void aspeed_xdma_reset(struct aspeed_xdma *ctx) +{ + unsigned long flags; + + reset_control_assert(ctx->reset); + usleep_range(XDMA_ENGINE_SETUP_TIME_MIN_US, + XDMA_ENGINE_SETUP_TIME_MAX_US); + reset_control_deassert(ctx->reset); + usleep_range(XDMA_ENGINE_SETUP_TIME_MIN_US, + XDMA_ENGINE_SETUP_TIME_MAX_US); + + aspeed_xdma_init_eng(ctx); + + aspeed_xdma_done(ctx, true); + + spin_lock_irqsave(&ctx->engine_lock, flags); + ctx->in_reset = false; + spin_unlock_irqrestore(&ctx->engine_lock, flags); + + wake_up_interruptible(&ctx->wait); +} + +static void aspeed_xdma_reset_work(struct work_struct *work) +{ + struct aspeed_xdma *ctx = container_of(work, struct aspeed_xdma, + reset_work); + + aspeed_xdma_reset(ctx); +} + +static irqreturn_t aspeed_xdma_pcie_irq(int irq, void *arg) +{ + struct aspeed_xdma *ctx = arg; + + dev_dbg(ctx->dev, "PCI-E reset requested.\n"); + + spin_lock(&ctx->engine_lock); + if (ctx->in_reset) { + spin_unlock(&ctx->engine_lock); + return IRQ_HANDLED; + } + + ctx->in_reset = true; + spin_unlock(&ctx->engine_lock); + + schedule_work(&ctx->reset_work); + return IRQ_HANDLED; +} + +static ssize_t aspeed_xdma_write(struct file *file, const char __user *buf, + size_t len, loff_t *offset) +{ + int rc; + unsigned int num_cmds; + struct aspeed_xdma_op op; + struct aspeed_xdma_cmd cmds[2]; + struct aspeed_xdma_client *client = file->private_data; + struct aspeed_xdma *ctx = client->ctx; + + if (len != sizeof(op)) + return -EINVAL; + + rc = copy_from_user(&op, buf, len); + if (rc) + return rc; + + if (!op.len || op.len > client->size || + op.direction > ASPEED_XDMA_DIRECTION_UPSTREAM) + return -EINVAL; + + num_cmds = ctx->chip->set_cmd(ctx, cmds, &op, client->phys); + do { + rc = aspeed_xdma_start(ctx, num_cmds, cmds, !!op.direction, + client); + if (!rc) + break; + + if ((file->f_flags & O_NONBLOCK) || rc != -EBUSY) + return rc; + + rc = wait_event_interruptible(ctx->wait, + !(ctx->current_client || + ctx->in_reset)); + } while (!rc); + + if (rc) + return -EINTR; + + if (!(file->f_flags & O_NONBLOCK)) { + rc = wait_event_interruptible(ctx->wait, !client->in_progress); + if (rc) + return -EINTR; + + if (client->error) + return -EIO; + } + + return len; +} + +static __poll_t aspeed_xdma_poll(struct file *file, + struct poll_table_struct *wait) +{ + __poll_t mask = 0; + __poll_t req = poll_requested_events(wait); + struct aspeed_xdma_client *client = file->private_data; + struct aspeed_xdma *ctx = client->ctx; + + if (req & (EPOLLIN | EPOLLRDNORM)) { + if (READ_ONCE(client->in_progress)) + poll_wait(file, &ctx->wait, wait); + + if (!READ_ONCE(client->in_progress)) { + if (READ_ONCE(client->error)) + mask |= EPOLLERR; + else + mask |= EPOLLIN | EPOLLRDNORM; + } + } + + if (req & (EPOLLOUT | EPOLLWRNORM)) { + if (READ_ONCE(ctx->current_client)) + poll_wait(file, &ctx->wait, wait); + + if (!READ_ONCE(ctx->current_client)) + mask |= EPOLLOUT | EPOLLWRNORM; + } + + return mask; +} + +static long aspeed_xdma_ioctl(struct file *file, unsigned int cmd, + unsigned long param) +{ + unsigned long flags; + struct aspeed_xdma_client *client = file->private_data; + struct aspeed_xdma *ctx = client->ctx; + + switch (cmd) { + case ASPEED_XDMA_IOCTL_RESET: + spin_lock_irqsave(&ctx->engine_lock, flags); + if (ctx->in_reset) { + spin_unlock_irqrestore(&ctx->engine_lock, flags); + return 0; + } + + ctx->in_reset = true; + spin_unlock_irqrestore(&ctx->engine_lock, flags); + + if (READ_ONCE(ctx->current_client)) + dev_warn(ctx->dev, + "User reset with transfer in progress.\n"); + + aspeed_xdma_reset(ctx); + break; + default: + return -EINVAL; + } + + return 0; +} + +static void aspeed_xdma_vma_close(struct vm_area_struct *vma) +{ + int rc; + struct aspeed_xdma_client *client = vma->vm_private_data; + + rc = wait_event_interruptible(client->ctx->wait, !client->in_progress); + if (rc) + return; + + gen_pool_free(client->ctx->pool, (unsigned long)client->virt, + client->size); + + client->virt = NULL; + client->phys = 0; + client->size = 0; +} + +static const struct vm_operations_struct aspeed_xdma_vm_ops = { + .close = aspeed_xdma_vma_close, +}; + +static int aspeed_xdma_mmap(struct file *file, struct vm_area_struct *vma) +{ + int rc; + struct aspeed_xdma_client *client = file->private_data; + struct aspeed_xdma *ctx = client->ctx; + + /* restrict file to one mapping */ + if (client->size) + return -EBUSY; + + client->size = vma->vm_end - vma->vm_start; + client->virt = gen_pool_dma_alloc(ctx->pool, client->size, + &client->phys); + if (!client->virt) { + client->phys = 0; + client->size = 0; + return -ENOMEM; + } + + vma->vm_pgoff = (client->phys - ctx->mem_phys) >> PAGE_SHIFT; + vma->vm_ops = &aspeed_xdma_vm_ops; + vma->vm_private_data = client; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + rc = io_remap_pfn_range(vma, vma->vm_start, client->phys >> PAGE_SHIFT, + client->size, vma->vm_page_prot); + if (rc) { + dev_warn(ctx->dev, "mmap err: v[%08lx] to p[%08x], s[%08x]\n", + vma->vm_start, (u32)client->phys, client->size); + + gen_pool_free(ctx->pool, (unsigned long)client->virt, + client->size); + + client->virt = NULL; + client->phys = 0; + client->size = 0; + return rc; + } + + dev_dbg(ctx->dev, "mmap: v[%08lx] to p[%08x], s[%08x]\n", + vma->vm_start, (u32)client->phys, client->size); + + return 0; +} + +static int aspeed_xdma_open(struct inode *inode, struct file *file) +{ + struct miscdevice *misc = file->private_data; + struct aspeed_xdma *ctx = container_of(misc, struct aspeed_xdma, misc); + struct aspeed_xdma_client *client = kzalloc(sizeof(*client), + GFP_KERNEL); + + if (!client) + return -ENOMEM; + + kobject_get(&ctx->kobj); + client->ctx = ctx; + file->private_data = client; + return 0; +} + +static int aspeed_xdma_release(struct inode *inode, struct file *file) +{ + bool reset = false; + unsigned long flags; + struct aspeed_xdma_client *client = file->private_data; + struct aspeed_xdma *ctx = client->ctx; + + spin_lock_irqsave(&ctx->client_lock, flags); + if (client == ctx->current_client) { + spin_lock(&ctx->engine_lock); + if (ctx->in_reset) { + ctx->current_client = NULL; + } else { + ctx->in_reset = true; + reset = true; + } + spin_unlock(&ctx->engine_lock); + } + spin_unlock_irqrestore(&ctx->client_lock, flags); + + if (reset) + aspeed_xdma_reset(ctx); + + if (client->virt) + gen_pool_free(ctx->pool, (unsigned long)client->virt, + client->size); + + kfree(client); + kobject_put(&ctx->kobj); + return 0; +} + +static const struct file_operations aspeed_xdma_fops = { + .owner = THIS_MODULE, + .write = aspeed_xdma_write, + .poll = aspeed_xdma_poll, + .unlocked_ioctl = aspeed_xdma_ioctl, + .mmap = aspeed_xdma_mmap, + .open = aspeed_xdma_open, + .release = aspeed_xdma_release, +}; + +static int aspeed_xdma_init_scu(struct aspeed_xdma *ctx, struct device *dev) +{ + struct regmap *scu = syscon_regmap_lookup_by_phandle(dev->of_node, + "aspeed,scu"); + + if (!IS_ERR(scu)) { + u32 selection; + bool pcie_device_bmc = true; + const u32 bmc = SCU_PCIE_CONF_BMC_EN | + SCU_PCIE_CONF_BMC_EN_MSI | SCU_PCIE_CONF_BMC_EN_IRQ | + SCU_PCIE_CONF_BMC_EN_DMA; + const u32 vga = SCU_PCIE_CONF_VGA_EN | + SCU_PCIE_CONF_VGA_EN_MSI | SCU_PCIE_CONF_VGA_EN_IRQ | + SCU_PCIE_CONF_VGA_EN_DMA; + const char *pcie = NULL; + + if (!of_property_read_string(dev->of_node, + "aspeed,pcie-device", &pcie)) { + if (!strcmp(pcie, "vga")) { + pcie_device_bmc = false; + } else if (strcmp(pcie, "bmc")) { + dev_err(dev, + "Invalid pcie-device property %s.\n", + pcie); + return -EINVAL; + } + } + + if (pcie_device_bmc) { + selection = bmc; + regmap_write(scu, ctx->chip->scu_bmc_class, + SCU_BMC_CLASS_REV_XDMA); + } else { + selection = vga; + } + + regmap_update_bits(scu, ctx->chip->scu_pcie_conf, bmc | vga, + selection); + + if (ctx->chip->scu_misc_ctrl) + regmap_update_bits(scu, ctx->chip->scu_misc_ctrl, + SCU_AST2600_MISC_CTRL_XDMA_BMC, + SCU_AST2600_MISC_CTRL_XDMA_BMC); + } else { + dev_warn(dev, "Unable to configure PCIe: %ld; continuing.\n", + PTR_ERR(scu)); + } + + return 0; +} + +static void aspeed_xdma_kobject_release(struct kobject *kobj) +{ + struct aspeed_xdma *ctx = container_of(kobj, struct aspeed_xdma, kobj); + + if (ctx->pcie_irq >= 0) + free_irq(ctx->pcie_irq, ctx); + + gen_pool_free(ctx->pool, (unsigned long)ctx->cmdq, XDMA_CMDQ_SIZE); + + gen_pool_destroy(ctx->pool); + + dma_free_coherent(ctx->dev, ctx->mem_size, ctx->mem_virt, + ctx->mem_coherent); + + if (ctx->reset_rc) + reset_control_put(ctx->reset_rc); + reset_control_put(ctx->reset); + + clk_put(ctx->clock); + + free_irq(ctx->irq, ctx); + + iounmap(ctx->base); + release_mem_region(ctx->res_start, ctx->res_size); + + kfree(ctx); +} + +static struct kobj_type aspeed_xdma_kobject_type = { + .release = aspeed_xdma_kobject_release, +}; + +static int aspeed_xdma_iomap(struct aspeed_xdma *ctx, + struct platform_device *pdev) +{ + resource_size_t size; + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!res) + return -ENOMEM; + + size = resource_size(res); + if (!request_mem_region(res->start, size, dev_name(ctx->dev))) + return -ENOMEM; + + ctx->base = ioremap(res->start, size); + if (!ctx->base) { + release_mem_region(res->start, size); + return -ENOMEM; + } + + ctx->res_start = res->start; + ctx->res_size = size; + + return 0; +} + +static int aspeed_xdma_probe(struct platform_device *pdev) +{ + int rc; + struct aspeed_xdma *ctx; + struct reserved_mem *mem; + struct device *dev = &pdev->dev; + struct device_node *memory_region; + const void *md = of_device_get_match_data(dev); + + if (!md) + return -ENODEV; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->chip = md; + ctx->dev = dev; + platform_set_drvdata(pdev, ctx); + spin_lock_init(&ctx->client_lock); + spin_lock_init(&ctx->engine_lock); + INIT_WORK(&ctx->reset_work, aspeed_xdma_reset_work); + init_waitqueue_head(&ctx->wait); + + rc = aspeed_xdma_iomap(ctx, pdev); + if (rc) { + dev_err(dev, "Failed to map registers.\n"); + goto err_nomap; + } + + ctx->irq = platform_get_irq(pdev, 0); + if (ctx->irq < 0) { + dev_err(dev, "Failed to find IRQ.\n"); + rc = ctx->irq; + goto err_noirq; + } + + rc = request_irq(ctx->irq, aspeed_xdma_irq, 0, DEVICE_NAME, ctx); + if (rc < 0) { + dev_err(dev, "Failed to request IRQ %d.\n", ctx->irq); + goto err_noirq; + } + + ctx->clock = clk_get(dev, NULL); + if (IS_ERR(ctx->clock)) { + dev_err(dev, "Failed to request clock.\n"); + rc = PTR_ERR(ctx->clock); + goto err_noclk; + } + + ctx->reset = reset_control_get_exclusive(dev, NULL); + if (IS_ERR(ctx->reset)) { + dev_err(dev, "Failed to request reset control.\n"); + rc = PTR_ERR(ctx->reset); + goto err_noreset; + } + + ctx->reset_rc = reset_control_get_exclusive(dev, "root-complex"); + if (IS_ERR(ctx->reset_rc)) { + dev_dbg(dev, "Failed to request reset RC control.\n"); + ctx->reset_rc = NULL; + } + + memory_region = of_parse_phandle(dev->of_node, "memory-region", 0); + if (!memory_region) { + dev_err(dev, "Failed to find memory-region.\n"); + rc = -ENOMEM; + goto err_nomem; + } + + mem = of_reserved_mem_lookup(memory_region); + of_node_put(memory_region); + if (!mem) { + dev_err(dev, "Failed to find reserved memory.\n"); + rc = -ENOMEM; + goto err_nomem; + } + + ctx->mem_phys = mem->base; + ctx->mem_size = mem->size; + + rc = of_reserved_mem_device_init(dev); + if (rc) { + dev_err(dev, "Failed to init reserved memory.\n"); + goto err_nomem; + } + + rc = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (rc) { + dev_err(dev, "Failed to mask DMA.\n"); + goto err_nomem; + } + + ctx->mem_virt = dma_alloc_coherent(dev, ctx->mem_size, + &ctx->mem_coherent, 0); + if (!ctx->mem_virt) { + dev_err(dev, "Failed to allocate reserved memory.\n"); + rc = -ENOMEM; + goto err_nomem; + } + + ctx->pool = gen_pool_create(ilog2(PAGE_SIZE), -1); + if (!ctx->pool) { + dev_err(dev, "Failed to setup genalloc pool.\n"); + rc = -ENOMEM; + goto err_nopool; + } + + rc = gen_pool_add_virt(ctx->pool, (unsigned long)ctx->mem_virt, + ctx->mem_phys, ctx->mem_size, -1); + if (rc) { + dev_err(ctx->dev, "Failed to add memory to genalloc pool.\n"); + goto err_pool_scu_clk; + } + + rc = aspeed_xdma_init_scu(ctx, dev); + if (rc) + goto err_pool_scu_clk; + + rc = clk_prepare_enable(ctx->clock); + if (rc) { + dev_err(dev, "Failed to enable the clock.\n"); + goto err_pool_scu_clk; + } + + if (ctx->reset_rc) { + rc = reset_control_deassert(ctx->reset_rc); + if (rc) { + dev_err(dev, "Failed to clear the RC reset.\n"); + goto err_reset_rc; + } + usleep_range(XDMA_ENGINE_SETUP_TIME_MIN_US, + XDMA_ENGINE_SETUP_TIME_MAX_US); + } + + rc = reset_control_deassert(ctx->reset); + if (rc) { + dev_err(dev, "Failed to clear the reset.\n"); + goto err_reset; + } + usleep_range(XDMA_ENGINE_SETUP_TIME_MIN_US, + XDMA_ENGINE_SETUP_TIME_MAX_US); + + ctx->cmdq = gen_pool_dma_alloc(ctx->pool, XDMA_CMDQ_SIZE, + &ctx->cmdq_phys); + if (!ctx->cmdq) { + dev_err(ctx->dev, "Failed to genalloc cmdq.\n"); + rc = -ENOMEM; + goto err_pool; + } + + aspeed_xdma_init_eng(ctx); + + ctx->misc.minor = MISC_DYNAMIC_MINOR; + ctx->misc.fops = &aspeed_xdma_fops; + ctx->misc.name = "aspeed-xdma"; + ctx->misc.parent = dev; + rc = misc_register(&ctx->misc); + if (rc) { + dev_err(dev, "Failed to register xdma miscdevice.\n"); + goto err_misc; + } + + /* + * This interrupt could fire immediately so only request it once the + * engine and driver are initialized. + */ + ctx->pcie_irq = platform_get_irq(pdev, 1); + if (ctx->pcie_irq < 0) { + dev_warn(dev, "Failed to find PCI-E IRQ.\n"); + } else { + rc = request_irq(ctx->pcie_irq, aspeed_xdma_pcie_irq, + IRQF_SHARED, DEVICE_NAME, ctx); + if (rc < 0) { + dev_warn(dev, "Failed to request PCI-E IRQ %d.\n", rc); + ctx->pcie_irq = -1; + } + } + + kobject_init(&ctx->kobj, &aspeed_xdma_kobject_type); + return 0; + +err_misc: + gen_pool_free(ctx->pool, (unsigned long)ctx->cmdq, XDMA_CMDQ_SIZE); +err_pool: + reset_control_assert(ctx->reset); +err_reset: + if (ctx->reset_rc) + reset_control_assert(ctx->reset_rc); +err_reset_rc: + clk_disable_unprepare(ctx->clock); +err_pool_scu_clk: + gen_pool_destroy(ctx->pool); +err_nopool: + dma_free_coherent(ctx->dev, ctx->mem_size, ctx->mem_virt, + ctx->mem_coherent); +err_nomem: + if (ctx->reset_rc) + reset_control_put(ctx->reset_rc); + reset_control_put(ctx->reset); +err_noreset: + clk_put(ctx->clock); +err_noclk: + free_irq(ctx->irq, ctx); +err_noirq: + iounmap(ctx->base); + release_mem_region(ctx->res_start, ctx->res_size); +err_nomap: + kfree(ctx); + return rc; +} + +static int aspeed_xdma_remove(struct platform_device *pdev) +{ + struct aspeed_xdma *ctx = platform_get_drvdata(pdev); + + reset_control_assert(ctx->reset); + if (ctx->reset_rc) + reset_control_assert(ctx->reset_rc); + clk_disable_unprepare(ctx->clock); + + aspeed_xdma_done(ctx, true); + + misc_deregister(&ctx->misc); + kobject_put(&ctx->kobj); + + return 0; +} + +static const struct aspeed_xdma_chip aspeed_ast2500_xdma_chip = { + .control = XDMA_AST2500_CTRL_US_COMP | XDMA_AST2500_CTRL_DS_COMP | + XDMA_AST2500_CTRL_DS_DIRTY | XDMA_AST2500_CTRL_DS_SIZE_256 | + XDMA_AST2500_CTRL_DS_TIMEOUT | XDMA_AST2500_CTRL_DS_CHECK_ID, + .scu_bmc_class = SCU_AST2500_BMC_CLASS_REV, + .scu_misc_ctrl = 0, + .scu_pcie_conf = SCU_AST2500_PCIE_CONF, + .queue_entry_size = XDMA_AST2500_QUEUE_ENTRY_SIZE, + .regs = { + .bmc_cmdq_addr = XDMA_AST2500_BMC_CMDQ_ADDR, + .bmc_cmdq_endp = XDMA_AST2500_BMC_CMDQ_ENDP, + .bmc_cmdq_writep = XDMA_AST2500_BMC_CMDQ_WRITEP, + .bmc_cmdq_readp = XDMA_AST2500_BMC_CMDQ_READP, + .control = XDMA_AST2500_CTRL, + .status = XDMA_AST2500_STATUS, + }, + .status_bits = { + .us_comp = XDMA_AST2500_STATUS_US_COMP, + .ds_comp = XDMA_AST2500_STATUS_DS_COMP, + .ds_dirty = XDMA_AST2500_STATUS_DS_DIRTY, + }, + .set_cmd = aspeed_xdma_ast2500_set_cmd, +}; + +static const struct aspeed_xdma_chip aspeed_ast2600_xdma_chip = { + .control = XDMA_AST2600_CTRL_US_COMP | XDMA_AST2600_CTRL_DS_COMP | + XDMA_AST2600_CTRL_DS_DIRTY | XDMA_AST2600_CTRL_DS_SIZE_256, + .scu_bmc_class = SCU_AST2600_BMC_CLASS_REV, + .scu_misc_ctrl = SCU_AST2600_MISC_CTRL, + .scu_pcie_conf = SCU_AST2600_PCIE_CONF, + .queue_entry_size = XDMA_AST2600_QUEUE_ENTRY_SIZE, + .regs = { + .bmc_cmdq_addr = XDMA_AST2600_BMC_CMDQ_ADDR, + .bmc_cmdq_endp = XDMA_AST2600_BMC_CMDQ_ENDP, + .bmc_cmdq_writep = XDMA_AST2600_BMC_CMDQ_WRITEP, + .bmc_cmdq_readp = XDMA_AST2600_BMC_CMDQ_READP, + .control = XDMA_AST2600_CTRL, + .status = XDMA_AST2600_STATUS, + }, + .status_bits = { + .us_comp = XDMA_AST2600_STATUS_US_COMP, + .ds_comp = XDMA_AST2600_STATUS_DS_COMP, + .ds_dirty = XDMA_AST2600_STATUS_DS_DIRTY, + }, + .set_cmd = aspeed_xdma_ast2600_set_cmd, +}; + +static const struct of_device_id aspeed_xdma_match[] = { + { + .compatible = "aspeed,ast2500-xdma", + .data = &aspeed_ast2500_xdma_chip, + }, + { + .compatible = "aspeed,ast2600-xdma", + .data = &aspeed_ast2600_xdma_chip, + }, + { }, +}; + +static struct platform_driver aspeed_xdma_driver = { + .probe = aspeed_xdma_probe, + .remove = aspeed_xdma_remove, + .driver = { + .name = DEVICE_NAME, + .of_match_table = aspeed_xdma_match, + }, +}; + +module_platform_driver(aspeed_xdma_driver); + +MODULE_AUTHOR("Eddie James"); +MODULE_DESCRIPTION("Aspeed XDMA Engine Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/spi/spi-fsi.c b/drivers/spi/spi-fsi.c index 37a3e0f8e752..8949a64ec87d 100644 --- a/drivers/spi/spi-fsi.c +++ b/drivers/spi/spi-fsi.c @@ -12,6 +12,7 @@ #define FSI_ENGID_SPI 0x23 #define FSI_MBOX_ROOT_CTRL_8 0x2860 +#define FSI_MBOX_ROOT_CTRL_8_SPI 0xf0000000 #define FSI2SPI_DATA0 0x00 #define FSI2SPI_DATA1 0x04 @@ -24,11 +25,16 @@ #define SPI_FSI_BASE 0x70000 #define SPI_FSI_INIT_TIMEOUT_MS 1000 -#define SPI_FSI_MAX_TRANSFER_SIZE 2048 +#define SPI_FSI_MAX_XFR_SIZE 2048 +#define SPI_FSI_MAX_XFR_SIZE_RESTRICTED 32 #define SPI_FSI_ERROR 0x0 #define SPI_FSI_COUNTER_CFG 0x1 #define SPI_FSI_COUNTER_CFG_LOOPS(x) (((u64)(x) & 0xffULL) << 32) +#define SPI_FSI_COUNTER_CFG_N2_RX BIT_ULL(8) +#define SPI_FSI_COUNTER_CFG_N2_TX BIT_ULL(9) +#define SPI_FSI_COUNTER_CFG_N2_IMPLICIT BIT_ULL(10) +#define SPI_FSI_COUNTER_CFG_N2_RELOAD BIT_ULL(11) #define SPI_FSI_CFG1 0x2 #define SPI_FSI_CLOCK_CFG 0x3 #define SPI_FSI_CLOCK_CFG_MM_ENABLE BIT_ULL(32) @@ -61,7 +67,7 @@ #define SPI_FSI_STATUS_RDR_OVERRUN BIT_ULL(62) #define SPI_FSI_STATUS_RDR_FULL BIT_ULL(63) #define SPI_FSI_STATUS_ANY_ERROR \ - (SPI_FSI_STATUS_ERROR | SPI_FSI_STATUS_TDR_UNDERRUN | \ + (SPI_FSI_STATUS_ERROR | \ SPI_FSI_STATUS_TDR_OVERRUN | SPI_FSI_STATUS_RDR_UNDERRUN | \ SPI_FSI_STATUS_RDR_OVERRUN) #define SPI_FSI_PORT_CTRL 0x9 @@ -70,6 +76,8 @@ struct fsi_spi { struct device *dev; /* SPI controller device */ struct fsi_device *fsi; /* FSI2SPI CFAM engine device */ u32 base; + size_t max_xfr_size; + bool restricted; }; struct fsi_spi_sequence { @@ -77,6 +85,26 @@ struct fsi_spi_sequence { u64 data; }; +static int fsi_spi_check_mux(struct fsi_device *fsi, struct device *dev) +{ + int rc; + u32 root_ctrl_8; + __be32 root_ctrl_8_be; + + rc = fsi_slave_read(fsi->slave, FSI_MBOX_ROOT_CTRL_8, &root_ctrl_8_be, + sizeof(root_ctrl_8_be)); + if (rc) + return rc; + + root_ctrl_8 = be32_to_cpu(root_ctrl_8_be); + dev_dbg(dev, "Root control register 8: %08x\n", root_ctrl_8); + if ((root_ctrl_8 & FSI_MBOX_ROOT_CTRL_8_SPI) == + FSI_MBOX_ROOT_CTRL_8_SPI) + return 0; + + return -ENOLINK; +} + static int fsi_spi_check_status(struct fsi_spi *ctx) { int rc; @@ -205,8 +233,12 @@ static int fsi_spi_reset(struct fsi_spi *ctx) if (rc) return rc; - return fsi_spi_write_reg(ctx, SPI_FSI_CLOCK_CFG, - SPI_FSI_CLOCK_CFG_RESET2); + rc = fsi_spi_write_reg(ctx, SPI_FSI_CLOCK_CFG, + SPI_FSI_CLOCK_CFG_RESET2); + if (rc) + return rc; + + return fsi_spi_write_reg(ctx, SPI_FSI_STATUS, 0ULL); } static int fsi_spi_sequence_add(struct fsi_spi_sequence *seq, u8 val) @@ -214,8 +246,8 @@ static int fsi_spi_sequence_add(struct fsi_spi_sequence *seq, u8 val) /* * Add the next byte of instruction to the 8-byte sequence register. * Then decrement the counter so that the next instruction will go in - * the right place. Return the number of "slots" left in the sequence - * register. + * the right place. Return the index of the slot we just filled in the + * sequence register. */ seq->data |= (u64)val << seq->bit; seq->bit -= 8; @@ -233,40 +265,71 @@ static int fsi_spi_sequence_transfer(struct fsi_spi *ctx, struct fsi_spi_sequence *seq, struct spi_transfer *transfer) { + bool docfg = false; int loops; int idx; int rc; + u8 val = 0; u8 len = min(transfer->len, 8U); u8 rem = transfer->len % len; + u64 cfg = 0ULL; loops = transfer->len / len; if (transfer->tx_buf) { - idx = fsi_spi_sequence_add(seq, - SPI_FSI_SEQUENCE_SHIFT_OUT(len)); + val = SPI_FSI_SEQUENCE_SHIFT_OUT(len); + idx = fsi_spi_sequence_add(seq, val); + if (rem) rem = SPI_FSI_SEQUENCE_SHIFT_OUT(rem); } else if (transfer->rx_buf) { - idx = fsi_spi_sequence_add(seq, - SPI_FSI_SEQUENCE_SHIFT_IN(len)); + val = SPI_FSI_SEQUENCE_SHIFT_IN(len); + idx = fsi_spi_sequence_add(seq, val); + if (rem) rem = SPI_FSI_SEQUENCE_SHIFT_IN(rem); } else { return -EINVAL; } + if (ctx->restricted) { + const int eidx = rem ? 5 : 6; + + while (loops > 1 && idx <= eidx) { + idx = fsi_spi_sequence_add(seq, val); + loops--; + docfg = true; + } + + if (loops > 1) { + dev_warn(ctx->dev, "No sequencer slots; aborting.\n"); + return -EINVAL; + } + } + if (loops > 1) { fsi_spi_sequence_add(seq, SPI_FSI_SEQUENCE_BRANCH(idx)); + docfg = true; + } - if (rem) - fsi_spi_sequence_add(seq, rem); + if (docfg) { + cfg = SPI_FSI_COUNTER_CFG_LOOPS(loops - 1); + if (transfer->rx_buf) + cfg |= SPI_FSI_COUNTER_CFG_N2_RX | + SPI_FSI_COUNTER_CFG_N2_TX | + SPI_FSI_COUNTER_CFG_N2_IMPLICIT | + SPI_FSI_COUNTER_CFG_N2_RELOAD; - rc = fsi_spi_write_reg(ctx, SPI_FSI_COUNTER_CFG, - SPI_FSI_COUNTER_CFG_LOOPS(loops - 1)); + rc = fsi_spi_write_reg(ctx, SPI_FSI_COUNTER_CFG, cfg); if (rc) return rc; + } else { + fsi_spi_write_reg(ctx, SPI_FSI_COUNTER_CFG, 0ULL); } + if (rem) + fsi_spi_sequence_add(seq, rem); + return 0; } @@ -275,6 +338,7 @@ static int fsi_spi_transfer_data(struct fsi_spi *ctx, { int rc = 0; u64 status = 0ULL; + u64 cfg = 0ULL; if (transfer->tx_buf) { int nb; @@ -312,6 +376,16 @@ static int fsi_spi_transfer_data(struct fsi_spi *ctx, u64 in = 0ULL; u8 *rx = transfer->rx_buf; + rc = fsi_spi_read_reg(ctx, SPI_FSI_COUNTER_CFG, &cfg); + if (rc) + return rc; + + if (cfg & SPI_FSI_COUNTER_CFG_N2_IMPLICIT) { + rc = fsi_spi_write_reg(ctx, SPI_FSI_DATA_TX, 0); + if (rc) + return rc; + } + while (transfer->len > recv) { do { rc = fsi_spi_read_reg(ctx, SPI_FSI_STATUS, @@ -350,7 +424,7 @@ static int fsi_spi_transfer_init(struct fsi_spi *ctx) u64 status = 0ULL; u64 wanted_clock_cfg = SPI_FSI_CLOCK_CFG_ECC_DISABLE | SPI_FSI_CLOCK_CFG_SCK_NO_DEL | - FIELD_PREP(SPI_FSI_CLOCK_CFG_SCK_DIV, 4); + FIELD_PREP(SPI_FSI_CLOCK_CFG_SCK_DIV, 19); end = jiffies + msecs_to_jiffies(SPI_FSI_INIT_TIMEOUT_MS); do { @@ -396,18 +470,22 @@ static int fsi_spi_transfer_init(struct fsi_spi *ctx) static int fsi_spi_transfer_one_message(struct spi_controller *ctlr, struct spi_message *mesg) { - int rc = 0; + int rc; u8 seq_slave = SPI_FSI_SEQUENCE_SEL_SLAVE(mesg->spi->chip_select + 1); struct spi_transfer *transfer; struct fsi_spi *ctx = spi_controller_get_devdata(ctlr); + rc = fsi_spi_check_mux(ctx->fsi, ctx->dev); + if (rc) + return rc; + list_for_each_entry(transfer, &mesg->transfers, transfer_list) { struct fsi_spi_sequence seq; struct spi_transfer *next = NULL; /* Sequencer must do shift out (tx) first. */ if (!transfer->tx_buf || - transfer->len > SPI_FSI_MAX_TRANSFER_SIZE) { + transfer->len > (ctx->max_xfr_size + 8)) { rc = -EINVAL; goto error; } @@ -431,7 +509,7 @@ static int fsi_spi_transfer_one_message(struct spi_controller *ctlr, /* Sequencer can only do shift in (rx) after tx. */ if (next->rx_buf) { - if (next->len > SPI_FSI_MAX_TRANSFER_SIZE) { + if (next->len > ctx->max_xfr_size) { rc = -EINVAL; goto error; } @@ -476,30 +554,21 @@ error: static size_t fsi_spi_max_transfer_size(struct spi_device *spi) { - return SPI_FSI_MAX_TRANSFER_SIZE; + struct fsi_spi *ctx = spi_controller_get_devdata(spi->controller); + + return ctx->max_xfr_size; } static int fsi_spi_probe(struct device *dev) { int rc; - u32 root_ctrl_8; struct device_node *np; int num_controllers_registered = 0; struct fsi_device *fsi = to_fsi_dev(dev); - /* - * Check the SPI mux before attempting to probe. If the mux isn't set - * then the SPI controllers can't access their slave devices. - */ - rc = fsi_slave_read(fsi->slave, FSI_MBOX_ROOT_CTRL_8, &root_ctrl_8, - sizeof(root_ctrl_8)); + rc = fsi_spi_check_mux(fsi, dev); if (rc) - return rc; - - if (!root_ctrl_8) { - dev_dbg(dev, "SPI mux not set, aborting probe.\n"); return -ENODEV; - } for_each_available_child_of_node(dev->of_node, np) { u32 base; @@ -524,6 +593,14 @@ static int fsi_spi_probe(struct device *dev) ctx->fsi = fsi; ctx->base = base + SPI_FSI_BASE; + if (of_property_read_bool(np, "fsi2spi,restricted")) { + ctx->restricted = true; + ctx->max_xfr_size = SPI_FSI_MAX_XFR_SIZE_RESTRICTED; + } else { + ctx->restricted = false; + ctx->max_xfr_size = SPI_FSI_MAX_XFR_SIZE; + } + rc = devm_spi_register_controller(dev, ctlr); if (rc) spi_controller_put(ctlr); |