diff --git a/Documentation/devicetree/bindings/sound/phytium,hda.yaml b/Documentation/devicetree/bindings/sound/phytium,hda.yaml new file mode 100644 index 0000000000000..99df5d4bcb354 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/phytium,hda.yaml @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/phytium,hda.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium HD Audio controller + +maintainers: + - Chen Baozi + +properties: + compatible: + const: phytium,hda + + reg: + minItems: 1 + description: address and length of the HDA controller registers + + interrupts: + maxItems: 1 + description: HDA controller interrupt + + clocks: + maxItems: 1 + description: clock phandle for the HDA controller + +required: + - compatible + - reg + - interrupts + - clocks + +unevaluatedProperties: false + +examples: + - | + #include + + hda: hda@2800d000 { + compatible = "phytium,hda"; + reg = <0x0 0x2800d000 0x0 0x1000>; + interrupts = ; + clocks = <&sysclk_48mhz>; + }; diff --git a/Documentation/devicetree/bindings/spi/phytium,qspi-nor.yaml b/Documentation/devicetree/bindings/spi/phytium,qspi-nor.yaml new file mode 100644 index 0000000000000..99a842d0d48d7 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/phytium,qspi-nor.yaml @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/phytium,qspi-nor.yaml# +$schema: http://devicetree.org/schemas/meta-schemas/core.yaml# + +title: Phytium Quad Serial Peripheral Interface (QSPI) bindings + +maintainers: + - Chen Baozi + +allOf: + - $ref: "spi-controller.yaml#" + +properties: + compatible: + const: phytium,qspi-nor + + reg: + items: + - description: registers + - description: memory mapping region + + reg-names: + items: + - const: qspi + - const: qspi_mm + + clocks: + maxItems: 1 + + no-direct-mapping: + $ref: /schemas/types.yaml#/definitions/flag + description: + Indicates if we can use direct mapping to access the flash + +required: + - compatible + - reg + - reg-names + - clocks + +unevaluatedProperties: false + +examples: + - | + qspi: qspi@28014000 { + compatible = "phytium,qspi-nor"; + reg = <0x0 0x28014000 0x0 0x1000>, + <0x0 0x0 0x0 0x02000000>; + reg-names = "qspi", "qspi_mm"; + clocks = <&sysclk_600mhz>; + + #address-cells = <1>; + #size-cells = <0>; + + flash@0 { + compatible = "jedec,spi-nor"; + reg = <0>; + spi-rx-bus-width = <4>; + spi-max-frequency = <600000000>; + }; + + flash@1 { + compatible = "jedec,spi-nor"; + reg = <1>; + spi-rx-bus-width = <4>; + spi-max-frequency = <600000000>; + }; + }; diff --git a/Documentation/devicetree/bindings/spi/phytium,spi.yaml b/Documentation/devicetree/bindings/spi/phytium,spi.yaml new file mode 100644 index 0000000000000..703562bcbbfab --- /dev/null +++ b/Documentation/devicetree/bindings/spi/phytium,spi.yaml @@ -0,0 +1,106 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/phytium,spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Phytium SPI controller + +maintainers: + - Chen Baozi + +allOf: + - $ref: spi-controller.yaml# + - if: + properties: + compatible: + contains: + const: phytium,spi-2.0 + then: + properties: + reg: + minItems: 2 + maxItems: 2 + description: + reg[0]: register file window; + reg[1]: AP-RV shared memory window + required: + - reg + else: + properties: + reg: + minItems: 1 + maxItems: 1 + +properties: + compatible: + oneOf: + - const: phytium,spi + - const: phytium,spi-2.0 + description: Phytium SPI controller v2.0 interface + + reg: + minItems: 1 + description: address and length of the spi master registers + + interrupts: + maxItems: 1 + description: should contain one interrupt + + clocks: + maxItems: 1 + description: spi clock phandle + + num-cs: + $ref: /schemas/types.yaml#/definitions/uint32 + description: number of chip selects used + default: 4 + + reg-io-width: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [ 4 ] + description: I/O register width in bytes + default: 4 + + global-cs: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [ 0, 1 ] + description: | + When set to 1, the controller drives all chip selects through a + single global chip-select line (GCSR mode). + default: 1 + + dmas: + minItems: 2 + maxItems: 2 + description: TX and RX DMA channel specifiers + + dma-names: + items: + - const: rx + - const: tx + +required: + - compatible + - "#address-cells" + - "#size-cells" + - reg + - interrupts + - clocks + - num-cs + +unevaluatedProperties: false + +examples: + - | + #include + + spi0: spi@2800c000 { + compatible = "phytium,spi"; + #address-cells = <1>; + #size-cells = <0>; + interrupts = ; + reg = <0x0 0x2800c000 0x0 0x1000>; + clocks = <&sysclk_48mhz>; + num-cs = <4>; + }; diff --git a/drivers/firmware/arm_scmi/transports/mailbox.c b/drivers/firmware/arm_scmi/transports/mailbox.c index ae0f67e6cc45f..df50b34699b56 100644 --- a/drivers/firmware/arm_scmi/transports/mailbox.c +++ b/drivers/firmware/arm_scmi/transports/mailbox.c @@ -241,6 +241,30 @@ static int mailbox_chan_setup(struct scmi_chan_info *cinfo, struct device *dev, } } + /* + * The Phytium mailbox controller never raises the transfer-complete + * interrupt, so synchronous commands must be polled. Inspect the + * controller behind the a2p reply mailbox; a phandle that cannot be + * resolved is not fatal - just skip the quirk then. + */ + if (tx) { + struct of_phandle_args args; + + /* + * mboxes is a phandle-with-args list; of_parse_phandle() + * does not account for #mbox-cells, so it can land on an + * argument cell instead of the controller node when the + * specifier has arguments. Use the full parser. + */ + if (!of_parse_phandle_with_args(cdev->of_node, "mboxes", + "#mbox-cells", a2p_rx_chan, + &args)) { + if (of_device_is_compatible(args.np, "phytium,mbox")) + cinfo->no_completion_irq = true; + of_node_put(args.np); + } + } + cinfo->transport_info = smbox; smbox->cinfo = cinfo; mutex_init(&smbox->chan_lock); diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index 4b41550fd374e..e0b709aba5755 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -20,6 +20,8 @@ #include #include +#include +#include #include "mtdcore.h" /* @@ -90,6 +92,7 @@ static struct mtd_info *allocate_partition(struct mtd_info *parent, child->dev.parent = IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER) || mtd_is_partition(parent) ? &parent->dev : parent->dev.parent; child->dev.of_node = part->of_node; + child->dev.fwnode = part->fwnode; child->parent = parent; child->part.offset = part->offset; INIT_LIST_HEAD(&child->partitions); @@ -525,12 +528,14 @@ EXPORT_SYMBOL_GPL(deregister_mtd_parser); static const char * const default_mtd_part_types[] = { "cmdlinepart", "ofpart", + "acpipart", NULL }; /* Check DT only when looking for subpartitions. */ static const char * const default_subpartition_types[] = { "ofpart", + "acpipart", NULL }; @@ -594,6 +599,39 @@ static struct mtd_part_parser *mtd_part_get_compatible_parser(const char *compat return ret; } +static int mtd_part_acpi_parse(struct mtd_info *master, + struct mtd_partitions *pparts) +{ + struct mtd_part_parser *parser; + const char *compat = NULL; + const char *fixed = "acpi-fixed-partitions"; + int ret, err = 0; + struct device *dev = &master->dev; + + /* + * Only the master carries the "fixed" marker property; on + * subpartitions it is absent and no ACPI parsing is done. + */ + if (!mtd_is_partition(master)) + fwnode_property_read_string(dev->fwnode, "fixed", &compat); + + if (compat && !strcmp(compat, fixed)) { + parser = mtd_part_parser_get(fixed); + if (!parser && !request_module("%s", fixed)) + parser = mtd_part_parser_get(fixed); + if (parser) { + ret = mtd_part_do_parse(parser, master, pparts, NULL); + if (ret > 0) + return ret; + mtd_part_parser_put(parser); + if (ret < 0 && !err) + err = ret; + } + } + + return err; +} + static int mtd_part_of_parse(struct mtd_info *master, struct mtd_partitions *pparts) { @@ -699,7 +737,9 @@ int parse_mtd_partitions(struct mtd_info *master, const char *const *types, * should be used. It requires a bit different logic so it is * handled in a separated function. */ - if (!strcmp(*types, "ofpart")) { + if (!strcmp(*types, "acpipart")) { + ret = mtd_part_acpi_parse(master, &pparts); + } else if (!strcmp(*types, "ofpart")) { ret = mtd_part_of_parse(master, &pparts); } else { pr_debug("%s: parsing partitions %s\n", master->name, diff --git a/drivers/mtd/parsers/Kconfig b/drivers/mtd/parsers/Kconfig index da03ab6efe04c..77e1d1ba5e6a6 100644 --- a/drivers/mtd/parsers/Kconfig +++ b/drivers/mtd/parsers/Kconfig @@ -91,6 +91,14 @@ config MTD_OF_PARTS_LINKSYS_NS two "firmware" partitions. Currently used firmware has to be detected using CFE environment variable. +config MTD_ACPI_PARTS + tristate "ACPI partitioning parser" + depends on ACPI && (ARCH_PHYTIUM || COMPILE_TEST) + help + This provides an acpi partition parser, which is used to parse the + partition map described in ACPI table, as the children of the flash + memory struct. + config MTD_PARSER_IMAGETAG tristate "Parser for BCM963XX Image Tag format partitions" depends on BCM63XX || BMIPS_GENERIC || COMPILE_TEST diff --git a/drivers/mtd/parsers/Makefile b/drivers/mtd/parsers/Makefile index 9b00c62b837ae..5136ad62f4f42 100644 --- a/drivers/mtd/parsers/Makefile +++ b/drivers/mtd/parsers/Makefile @@ -7,6 +7,8 @@ obj-$(CONFIG_MTD_OF_PARTS) += ofpart.o ofpart-y += ofpart_core.o ofpart-$(CONFIG_MTD_OF_PARTS_BCM4908) += ofpart_bcm4908.o ofpart-$(CONFIG_MTD_OF_PARTS_LINKSYS_NS)+= ofpart_linksys_ns.o +obj-$(CONFIG_MTD_ACPI_PARTS) += acpipart.o +acpipart-y += acpipart_core.o obj-$(CONFIG_MTD_PARSER_IMAGETAG) += parser_imagetag.o obj-$(CONFIG_MTD_AFS_PARTS) += afs.o obj-$(CONFIG_MTD_PARSER_TPLINK_SAFELOADER) += tplink_safeloader.o diff --git a/drivers/mtd/parsers/acpipart_core.c b/drivers/mtd/parsers/acpipart_core.c new file mode 100644 index 0000000000000..f3aeb2392ed7f --- /dev/null +++ b/drivers/mtd/parsers/acpipart_core.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Flash partitions described by the acpi table + * + * Author: Wang Hanmo + */ + +#include +#include +#include +#include +#include +#include +#include + +static const struct acpi_device_id parse_acpipart_match_table[]; + +static int parse_acpi_fixed_partitions(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + const char *partname; + int nr_parts, i, ret = 0; + struct fwnode_handle *child_handle = NULL; + struct fwnode_handle *partitions_node = NULL; + const struct fwnode_handle *parse_root; + bool dedicated = true; + struct device *dev; + + dev = &master->dev; + + if (!master->parent) {/*master*/ + /* + * A conventional "partitions" subnode groups the actual + * partition children; descend into it when present, + * mirroring the OF parser. Only such a container enables + * strict dedicated mode — an unrelated child node must + * not, and without the container the direct subnodes are + * parsed leniently as partitions. + */ + partitions_node = fwnode_get_named_child_node(dev->fwnode, + "partitions"); + if (!partitions_node) { + pr_debug("%s: 'partitions' subnode not found on %pfw. Trying to parse direct subnodes as partitions.\n", + master->name, dev->fwnode); + dedicated = false; + } + } + + /* + * The caller already selected this parser by name through the + * "fixed" property, so there is nothing to match here: matching + * the MTD device against the parser's ACPI IDs would fail, as the + * flash device itself carries a different _HID (e.g. PHYT8009 or + * a JEDEC ID). + */ + + parse_root = partitions_node ? partitions_node : dev->fwnode; + + nr_parts = 0; + fwnode_for_each_child_node(parse_root, child_handle) + nr_parts++; + + if (nr_parts == 0) { + ret = 0; + goto out_put; + } + parts = kcalloc(nr_parts, sizeof(*parts), GFP_KERNEL); + if (!parts) { + ret = -ENOMEM; + goto out_put; + } + + i = 0; + fwnode_for_each_child_node(parse_root, child_handle) { + u64 offset = 0, length = 0; + bool bool_match; + + if (fwnode_property_read_u64(child_handle, "offset", &offset) || + fwnode_property_read_u64(child_handle, "length", &length) || + (!offset && !length)) { + if (dedicated) { + pr_debug("%s: acpipart partition %pfw (%pfw) missing offset/length property.\n", + master->name, child_handle, + dev->fwnode); + goto acpipart_fail; + } else { + nr_parts--; + continue; + } + } + + parts[i].offset = offset; + parts[i].size = length; + parts[i].fwnode = fwnode_handle_get(child_handle); + if (!fwnode_property_read_string(child_handle, "label", &partname)) + parts[i].name = partname; + else + /* fall back to the node name, as ofpart does */ + parts[i].name = fwnode_get_name(child_handle); + bool_match = fwnode_property_read_bool(child_handle, "read-only"); + if (bool_match) + parts[i].mask_flags |= MTD_WRITEABLE; + bool_match = fwnode_property_read_bool(child_handle, "lock"); + if (bool_match) + parts[i].mask_flags |= MTD_POWERUP_LOCK; + bool_match = fwnode_property_read_bool(child_handle, "slc-mode"); + if (bool_match) + parts[i].add_flags |= MTD_SLC_ON_MLC_EMULATION; + i++; + } + + if (!nr_parts) + goto acpipart_none; + + *pparts = parts; + ret = nr_parts; + goto out_put; + +acpipart_fail: + pr_err("%s: error parsing acpipart partition %pfw (%pfw)\n", + master->name, child_handle, dev->fwnode); + ret = -EINVAL; + /* the iterator still holds a reference to the current child */ + fwnode_handle_put(child_handle); +acpipart_none: + /* entries collected so far keep their own references */ + for (i = 0; i < nr_parts; i++) + fwnode_handle_put(parts[i].fwnode); + kfree(parts); +out_put: + /* NULL-safe when no container node was found */ + fwnode_handle_put(partitions_node); + return ret; +} + +static const struct acpi_device_id parse_acpipart_match_table[] = { + /* + * Note: the historical ID "acpi-fixed-partitions" exceeds the + * 16-byte ACPI_ID_LEN and was silently truncated by the compiler, + * which is why the ID was renamed to "acpi-partitions" in the + * -Werror fixes; firmware can never have matched the full old ID. + */ + /* Generic */ + { "acpi-partitions", 0 }, + /* Customized */ + {}, +}; + +MODULE_DEVICE_TABLE(acpi, parse_acpipart_match_table); + +static void acpipart_cleanup(const struct mtd_partition *pparts, int nr_parts) +{ + int i; + + for (i = 0; i < nr_parts; i++) + fwnode_handle_put(pparts[i].fwnode); + kfree(pparts); +} + +static struct mtd_part_parser acpipart_parser = { + .parse_fn = parse_acpi_fixed_partitions, + .cleanup = acpipart_cleanup, + .name = "acpi-fixed-partitions", + .acpi_match_table = ACPI_PTR(parse_acpipart_match_table), +}; + +static int __init acpipart_parser_init(void) +{ + register_mtd_parser(&acpipart_parser); + return 0; +} + +static void __exit acpipart_parser_exit(void) +{ + deregister_mtd_parser(&acpipart_parser); +} + +module_init(acpipart_parser_init); +module_exit(acpipart_parser_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parser for MTD partitioning information in acpi table"); +MODULE_AUTHOR("wanghanmo "); +MODULE_ALIAS("acpi-fixed-partitions"); diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index ccf4396cdcd04..19c4511271773 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -7,6 +7,7 @@ * Copyright (C) 2014, Freescale Semiconductor, Inc. */ +#include #include #include #include @@ -18,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -3795,6 +3797,9 @@ static int spi_nor_probe(struct spi_mem *spimem) nor->spimem = spimem; nor->dev = dev; spi_nor_set_flash_node(nor, dev->of_node); + nor->mtd.dev.fwnode = spi->dev.fwnode; + + device_property_read_string(&spi->dev, "_HID", &nor->mtd.name); spi_mem_set_drvdata(spimem, nor); @@ -3933,6 +3938,11 @@ static const struct of_device_id spi_nor_of_table[] = { }; MODULE_DEVICE_TABLE(of, spi_nor_of_table); +static const struct acpi_device_id spi_nor_acpi_table[] = { + {"PHYT8009", 0}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, spi_nor_acpi_table); /* * REVISIT: many of these chips have deep power-down modes, which * should clearly be entered on suspend() to minimize power use. @@ -3944,6 +3954,7 @@ static struct spi_mem_driver spi_nor_driver = { .name = "spi-nor", .of_match_table = spi_nor_of_table, .dev_groups = spi_nor_sysfs_groups, + .acpi_match_table = spi_nor_acpi_table, }, .id_table = spi_nor_dev_ids, }, diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 8782514bb89b0..ba09ca40674be 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -852,6 +852,57 @@ config SPI_PCI1XXXX This driver can be built as module. If so, the module will be called as spi-pci1xxxx. +config SPI_PHYTIUM + tristate + +config SPI_PHYTIUM_PLAT + tristate "Phytium SPI controller platform support" + depends on ARCH_PHYTIUM || COMPILE_TEST + select SPI_PHYTIUM + help + This selects a platform driver for Phytium SPI controller. + + If you say yes to this option, support will be included for + Phytium SoC families of SPI controller. + +config SPI_PHYTIUM_PCI + tristate "Phytium SPI controller PCI support" + depends on ARCH_PHYTIUM || COMPILE_TEST + depends on PCI + select SPI_PHYTIUM + help + This selects a PCI driver for Phytium SPI controller. + + If you say yes to this option, support will be included for + Phytium PCIe chipsets of SPI controller. + + If unsure, say N. + +config SPI_PHYTIUM_QSPI + tristate "Phytium Quad SPI controller" + depends on ARCH_PHYTIUM || COMPILE_TEST + depends on OF || ACPI + depends on SPI_MEM + help + This enables support for Phytium Quad SPI flash controller. + + This driver does not support generic SPI. The implementation only + supports spi-mem interface. + + If unsure, say N. + +config SPI_PHYTIUM_V2 + tristate "spi phytium v2" + help + This config is similar to the "SPI_PHYTIUM" config. + +config SPI_PHYTIUM_PLAT_V2 + tristate "Phytium SPI-v2 controller platform support" + select SPI_PHYTIUM_V2 + depends on ARCH_PHYTIUM || COMPILE_TEST + help + This config is similar to the "SPI_PHYTIUM_PLAT" config. + config SPI_PIC32 tristate "Microchip PIC32 series SPI" depends on MACH_PIC32 || COMPILE_TEST diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 9fa12498ce8c0..0a4a6c47f2e10 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -112,6 +112,13 @@ obj-$(CONFIG_SPI_OMAP24XX) += spi-omap2-mcspi.o obj-$(CONFIG_SPI_TI_QSPI) += spi-ti-qspi.o obj-$(CONFIG_SPI_ORION) += spi-orion.o obj-$(CONFIG_SPI_PCI1XXXX) += spi-pci1xxxx.o +obj-$(CONFIG_SPI_PHYTIUM) += spi-phytium.o +obj-$(CONFIG_SPI_PHYTIUM_PLAT) += spi-phytium-plat.o +obj-$(CONFIG_SPI_PHYTIUM_PCI) += spi-phytium-pci.o +obj-$(CONFIG_SPI_PHYTIUM_QSPI) += spi-phytium-qspi.o +obj-$(CONFIG_SPI_PHYTIUM) += spi-phytium-dma.o +obj-$(CONFIG_SPI_PHYTIUM_V2) += spi-phytium-common.o spi-phytium-v2.o +obj-$(CONFIG_SPI_PHYTIUM_PLAT_V2) += spi-phytium-plat-v2.o obj-$(CONFIG_SPI_PIC32) += spi-pic32.o obj-$(CONFIG_SPI_PIC32_SQI) += spi-pic32-sqi.o obj-$(CONFIG_SPI_PL022) += spi-pl022.o diff --git a/drivers/spi/spi-phytium-common.c b/drivers/spi/spi-phytium-common.c new file mode 100644 index 0000000000000..c2c4661d566f2 --- /dev/null +++ b/drivers/spi/spi-phytium-common.c @@ -0,0 +1,569 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SPI core controller driver. + * + * Copyright (c) 2023-2024, Phytium Technology Co., Ltd.. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "spi-phytium.h" + +#define SPI_SHOW_MSG_DEBUG 0 + +static void spi_phytium_show_msg(struct msg *info) +{ + if (SPI_SHOW_MSG_DEBUG) { + pr_err("module:0x%4x, cmd:0x%04x, sub:0x%04x\n", + info->seq, info->cmd_id, info->cmd_subid); + + pr_err("0x%02x 0x%02x 0x%02x 0x%02x", info->data[0], + info->data[1], info->data[2], info->data[3]); + + pr_err("0x%02x 0x%02x 0x%02x 0x%02x", info->data[4], + info->data[5], info->data[6], info->data[7]); + + pr_err("0x%02x 0x%02x 0x%02x 0x%02x", info->data[8], + info->data[9], info->data[10], info->data[11]); + + pr_err("0x%02x 0x%02x 0x%02x 0x%02x", info->data[12], + info->data[13], info->data[14], info->data[15]); + } +} + +static void *memcpy_byte(void *_dest, const void *_src, size_t sz) +{ + while (sz >= 8) { + /* _dest/_src are not guaranteed to be 8-byte aligned */ + put_unaligned(get_unaligned((const u64 *)_src), (u64 *)_dest); + _dest += 8; + _src += 8; + sz -= 8; + } + + while (sz) { + *(u8 *)_dest = *(u8 *)_src; + _dest++; + _src++; + sz--; + } + + return _dest; +} + +static int spi_phytium_print_status(struct phytium_spi *fts, u8 status0, + u8 status1) +{ + if (status1 == 0) + return 0; + + switch (status1) { + case 1: + pr_err("SPI Bus is busy.\n"); + break; + case 2: + pr_err("DMA queue transfer error.\n"); + break; + case 3: + pr_err("DMA transfer timeout.\n"); + break; + case 4: + pr_err("Operating flash timeout.\n"); + break; + case 5: + pr_err("spi_tx channel:DMA initialization failure.\n"); + break; + case 6: + pr_err("spi_rx channel:DMA initialization failure.\n"); + break; + case 7: + pr_err("DMA queue initialization failure.\n"); + break; + default: + pr_err("status=0x%x,Unknown error\n", status1); + break; + } + + if (fts->debug_enabled) + fts->handle_debug_err(fts); + + return -1; +} + +static int spi_phytium_check_result(struct phytium_spi *fts) +{ + unsigned long ms; + struct msg *msg = &fts->msg_buf; + + ms = wait_for_completion_timeout(&fts->cmd_completion, + msecs_to_jiffies(SPI_MASTER_TIMEOUT)); + + if (ms == 0) { + dev_err(&fts->master->dev, "SPI controller timed out\n"); + return -1; + } + + /* read the response status only after the firmware completed */ + memcpy_fromio(&fts->msg_buf, fts->msg, sizeof(struct msg)); + + return spi_phytium_print_status(fts, msg->status0, msg->status1); +} + +static int spi_phytium_set(struct phytium_spi *fts) +{ + int ret; + + spi_phytium_show_msg(&fts->msg_buf); + memcpy_toio(fts->msg, &fts->msg_buf, sizeof(struct msg)); + reinit_completion(&fts->cmd_completion); + phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); + ret = spi_phytium_check_result(fts); + + return ret; +} + +int spi_phytium_default(struct phytium_spi *fts) +{ + int ret; + + memset(&fts->msg_buf, 0, sizeof(struct msg)); + + fts->msg_buf.cmd_id = PHYTSPI_MSG_CMD_DEFAULT; + + spi_phytium_show_msg(&fts->msg_buf); + memcpy_toio(fts->msg, &fts->msg_buf, sizeof(struct msg)); + reinit_completion(&fts->cmd_completion); + phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); + ret = spi_phytium_check_result(fts); + + return ret; +} +EXPORT_SYMBOL_GPL(spi_phytium_default); + +static void spi_phytium_set_subid(struct phytium_spi *fts, u16 sub_cmd) +{ + fts->msg_buf.cmd_id = PHYTSPI_MSG_CMD_SET; + fts->msg_buf.cmd_subid = sub_cmd; +} + +int spi_phytium_set_cmd8(struct phytium_spi *fts, u16 sub_cmd, + u8 data) +{ + memset(&fts->msg_buf, 0, sizeof(struct msg)); + spi_phytium_set_subid(fts, sub_cmd); + fts->msg_buf.data[0] = data; + spi_phytium_show_msg(&fts->msg_buf); + memcpy_toio(fts->msg, &fts->msg_buf, sizeof(struct msg)); + reinit_completion(&fts->cmd_completion); + phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); + return spi_phytium_check_result(fts); +} +EXPORT_SYMBOL_GPL(spi_phytium_set_cmd8); + +int spi_phytium_set_cmd16(struct phytium_spi *fts, u16 sub_cmd, + u16 data) +{ + u16 *cp_data = (u16 *)&fts->msg_buf.data[0]; + + memset(&fts->msg_buf, 0, sizeof(struct msg)); + spi_phytium_set_subid(fts, sub_cmd); + *cp_data = data; + spi_phytium_show_msg(&fts->msg_buf); + memcpy_toio(fts->msg, &fts->msg_buf, sizeof(struct msg)); + reinit_completion(&fts->cmd_completion); + phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); + return spi_phytium_check_result(fts); +} +EXPORT_SYMBOL_GPL(spi_phytium_set_cmd16); + +int spi_phytium_set_cmd32(struct phytium_spi *fts, u16 sub_cmd, + u32 data) +{ + u32 *cp_data = (u32 *)&fts->msg_buf.data[0]; + + memset(&fts->msg_buf, 0, sizeof(struct msg)); + spi_phytium_set_subid(fts, sub_cmd); + *cp_data = data; + spi_phytium_show_msg(&fts->msg_buf); + memcpy_toio(fts->msg, &fts->msg_buf, sizeof(struct msg)); + reinit_completion(&fts->cmd_completion); + phytium_write_regfile(fts, SPI_REGFILE_AP2RV_INTR_STATE, 0x10); + return spi_phytium_check_result(fts); +} +EXPORT_SYMBOL_GPL(spi_phytium_set_cmd32); + +void spi_phytium_write_pre(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, u8 tmode, + u8 flags, u8 spi_write_flag) +{ + u32 len; + void __iomem *smem_tx; + u8 first = 1; + u64 tx_addr; + void __iomem *msg_io; + struct msg msg_shadow; + + len = min((u32)(fts->tx_end - fts->tx), (u32)SPI_TRANS_DATA_SIZE); + msg_io = fts->msg + (sizeof(struct msg) + FLASH_PAGE_SIZE) * spi_write_flag; + memset(&msg_shadow, 0, sizeof(struct msg)); + + msg_shadow.cmd_id = PHYTSPI_MSG_CMD_DATA; + if (spi_write_flag) { + if (len > 16 && fts->dma_get_ddrdata) + msg_shadow.cmd_subid = PHYTSPI_MSG_CMD_DATA_FLASH_DMA_TX; + else + msg_shadow.cmd_subid = PHYTSPI_MSG_CMD_DATA_FLASH_TX; + } else { + if (len > 16 && fts->dma_get_ddrdata) + msg_shadow.cmd_subid = PHYTSPI_MSG_CMD_DATA_DMA_TX; + else + msg_shadow.cmd_subid = PHYTSPI_MSG_CMD_DATA_TX; + } + + if (len > 16 && fts->dma_get_ddrdata) { + tx_addr = dma_map_single(&fts->master->dev, fts->tx, len, + DMA_TO_DEVICE); + if (dma_mapping_error(&fts->master->dev, tx_addr)) { + dev_err(&fts->master->dev, "tx address translation failed\n"); + return; + } + *(u64 *)&msg_shadow.data[0] = tx_addr; + } else { + smem_tx = msg_io + sizeof(struct msg); + memcpy_toio(smem_tx, fts->tx, len); + *(u64 *)&msg_shadow.data[0] = sizeof(struct msg); + } + + *(u32 *)&msg_shadow.data[8] = len; + fts->tx += len; + msg_shadow.data[12] = cs; + msg_shadow.data[13] = dfs; + msg_shadow.data[14] = mode; + msg_shadow.data[15] = tmode; + msg_shadow.data[16] = flags; + msg_shadow.data[17] = first; + memcpy_toio(msg_io, &msg_shadow, sizeof(struct msg)); + first = 0; +} +EXPORT_SYMBOL_GPL(spi_phytium_write_pre); + +int spi_phytium_flash_erase(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, + u8 tmode, u8 flags, u8 cmd) +{ + u32 len; + u32 copy_len; + void __iomem *smem_tx; + u8 first = 1; + u8 cmd_addr[8]; + int ret; + + len = (u32)(fts->tx_end - fts->tx); + /* cmd_addr packs the opcode plus at most a 7-byte address phase; + * longer transfers would overflow the stack buffer. + */ + copy_len = min_t(u32, len, sizeof(cmd_addr) - 1); + + memset(&fts->msg_buf, 0, sizeof(struct msg)); + + fts->msg_buf.cmd_id = PHYTSPI_MSG_CMD_DATA; + + if (cmd == SPINOR_OP_BE_4K || cmd == SPINOR_OP_CHIP_ERASE) + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_FLASH_ERASE; + else if (cmd == SPINOR_OP_READ || cmd == SPINOR_OP_READ_FAST || + cmd == SPINOR_OP_READ_4B || cmd == SPINOR_OP_READ_FAST_4B) + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_DATA_TX; + + smem_tx = fts->msg + sizeof(struct msg); + + cmd_addr[0] = cmd; + if (cmd == SPINOR_OP_BE_4K || cmd == SPINOR_OP_READ || + cmd == SPINOR_OP_READ_FAST || cmd == SPINOR_OP_READ_4B || + cmd == SPINOR_OP_READ_FAST_4B) { + memcpy_byte((void *)&cmd_addr[1], fts->tx, copy_len); + memcpy_toio(smem_tx, (void *)&cmd_addr[0], copy_len + 1); + *(u32 *)&fts->msg_buf.data[8] = copy_len + 1; + } else if (cmd == SPINOR_OP_CHIP_ERASE) { + memcpy_toio(smem_tx, (void *)&cmd_addr[0], 1); + *(u32 *)&fts->msg_buf.data[8] = len; + } + + *(u32 *)&fts->msg_buf.data[0] = sizeof(struct msg); + fts->tx += len; + fts->msg_buf.data[12] = cs; + fts->msg_buf.data[13] = dfs; + fts->msg_buf.data[14] = mode; + fts->msg_buf.data[15] = tmode; + fts->msg_buf.data[16] = flags; + fts->msg_buf.data[17] = first; + + ret = spi_phytium_set(fts); + if (ret) { + dev_err(&fts->master->dev, "AP <-> RV interaction failed\n"); + return ret; + } + return ret; +} +EXPORT_SYMBOL_GPL(spi_phytium_flash_erase); + +int spi_phytium_flash_write(struct phytium_spi *fts, u8 cmd) +{ + u8 cmd_addr[8] = {0}; + size_t copy_len = min_t(size_t, fts->len, sizeof(cmd_addr) - 2); + + /* cmd_addr packs the length byte and opcode, leaving 6 bytes for + * the payload; fts->len is a size_t and must not be truncated + * implicitly either. + */ + cmd_addr[0] = (u8)(copy_len + 1); + cmd_addr[1] = cmd; + memcpy_byte((void *)&cmd_addr[2], fts->tx, copy_len); + + fts->msg_buf.data[18] = cmd_addr[0]; + fts->msg_buf.data[19] = cmd_addr[1]; + fts->msg_buf.data[20] = cmd_addr[2]; + fts->msg_buf.data[21] = cmd_addr[3]; + fts->msg_buf.data[22] = cmd_addr[4]; + fts->msg_buf.data[23] = cmd_addr[5]; + + return 0; +} +EXPORT_SYMBOL_GPL(spi_phytium_flash_write); + +int spi_phytium_write(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, + u8 tmode, u8 flags, u8 spi_write_flag) +{ + int ret = 0; + u32 len; + void __iomem *smem_tx; + u8 first = 1; + u64 tx_addr; + + if (spi_write_flag == 1) { + ret = spi_phytium_set(fts); + if (ret) { + dev_err(&fts->master->dev, "AP <-> RV interaction failed\n"); + return ret; + } + } + + do { + len = min_t(u32, (u32)(fts->tx_end - fts->tx), + (u32)SPI_TRANS_DATA_SIZE); + + fts->msg_buf.cmd_id = PHYTSPI_MSG_CMD_DATA; + + if (spi_write_flag == 2) { + if (len > 16 && fts->dma_get_ddrdata) + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_DATA_FLASH_DMA_TX; + else + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_DATA_FLASH_TX; + } else { + if (len > 16 && fts->dma_get_ddrdata) + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_DATA_DMA_TX; + else + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_DATA_TX; + } + + if (len > 16 && fts->dma_get_ddrdata) { + tx_addr = dma_map_single(&fts->master->dev, fts->tx, len, + DMA_TO_DEVICE); + if (dma_mapping_error(&fts->master->dev, tx_addr)) { + dev_err(&fts->master->dev, "tx address translation failed\n"); + return -1; + } + *(u64 *)&fts->msg_buf.data[0] = tx_addr; + } else { + smem_tx = fts->msg + sizeof(struct msg); + memcpy_toio(smem_tx, fts->tx, len); + *(u64 *)&fts->msg_buf.data[0] = sizeof(struct msg); + } + + *(u32 *)&fts->msg_buf.data[8] = len; + fts->tx += len; + fts->msg_buf.data[12] = cs; + fts->msg_buf.data[13] = dfs; + fts->msg_buf.data[14] = mode; + fts->msg_buf.data[15] = tmode; + fts->msg_buf.data[16] = flags; + fts->msg_buf.data[17] = first; + ret = spi_phytium_set(fts); + if (len > 16 && fts->dma_get_ddrdata) + dma_unmap_single(&fts->master->dev, tx_addr, len, + DMA_TO_DEVICE); + if (ret) { + dev_err(&fts->master->dev, "AP <-> RV interaction failed\n"); + return ret; + } + + first = 0; + } while (fts->tx_end > fts->tx); + + return ret; +} +EXPORT_SYMBOL_GPL(spi_phytium_write); + +int spi_phytium_read(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, + u8 tmode, u8 flags) +{ + int ret; + u32 len; + void __iomem *smem_rx; + u8 first = 1; + u64 rx_addr; + + do { + if (fts->dma_get_ddrdata) + len = min_t(u32, (u32)(fts->rx_end - fts->rx), + (u32)SPI_TRANS_DATA_SIZE); + else + len = min_t(u32, (u32)(fts->rx_end - fts->rx), 128); + + fts->msg_buf.cmd_id = PHYTSPI_MSG_CMD_DATA; + + smem_rx = fts->msg + sizeof(struct msg); + + if (len > 16 && fts->dma_get_ddrdata) { + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_DATA_DMA_RX; + rx_addr = dma_map_single(&fts->master->dev, fts->rx, len, + DMA_FROM_DEVICE); + if (dma_mapping_error(&fts->master->dev, rx_addr)) { + dev_err(&fts->master->dev, "rx address translation failed\n"); + return -1; + } + *(u64 *)&fts->msg_buf.data[0] = rx_addr; + } else { + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_DATA_RX; + *(u64 *)&fts->msg_buf.data[0] = sizeof(struct msg); + } + + *(u32 *)&fts->msg_buf.data[8] = len; + fts->msg_buf.data[12] = cs; + fts->msg_buf.data[13] = dfs; + fts->msg_buf.data[14] = mode; + fts->msg_buf.data[15] = tmode; + if (fts->rx_end <= fts->rx + len) + fts->msg_buf.data[16] = flags; + else if (first == 1) + fts->msg_buf.data[16] = 1; + else + fts->msg_buf.data[16] = 0; + fts->msg_buf.data[17] = first; + ret = spi_phytium_set(fts); + if (len > 16 && fts->dma_get_ddrdata) + dma_unmap_single(&fts->master->dev, rx_addr, len, + DMA_FROM_DEVICE); + if (ret) { + dev_err(&fts->master->dev, "AP <-> RV interaction failed\n"); + return ret; + } + if (len <= 16 || !fts->dma_get_ddrdata) + memcpy_fromio(fts->rx, smem_rx, len); + + fts->rx += len; + first = 0; + } while (fts->rx_end > fts->rx); + + return ret; +} +EXPORT_SYMBOL_GPL(spi_phytium_read); + +int spi_phytium_xfer(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, + u8 tmode, u8 flags) +{ + int ret; + u32 len; + void __iomem *smem_tx, *smem_rx; + u8 first = 1; + u64 tx_addr, rx_addr; + + do { + if (fts->dma_get_ddrdata) + len = min_t(u32, (u32)(fts->rx_end - fts->rx), + (u32)SPI_TRANS_DATA_SIZE); + else + len = min_t(u32, (u32)(fts->rx_end - fts->rx), 128); + + fts->msg_buf.cmd_id = PHYTSPI_MSG_CMD_DATA; + + smem_tx = fts->msg + sizeof(struct msg); + smem_rx = fts->msg + sizeof(struct msg) + 128; + + if (len > 16 && fts->dma_get_ddrdata) { + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_DATA_DMA_XFER; + tx_addr = dma_map_single(&fts->master->dev, fts->tx, len, + DMA_TO_DEVICE); + if (dma_mapping_error(&fts->master->dev, tx_addr)) { + dev_err(&fts->master->dev, "tx address translation failed\n"); + return -1; + } + rx_addr = dma_map_single(&fts->master->dev, fts->rx, len, + DMA_FROM_DEVICE); + if (dma_mapping_error(&fts->master->dev, rx_addr)) { + dma_unmap_single(&fts->master->dev, tx_addr, len, + DMA_TO_DEVICE); + dev_err(&fts->master->dev, "rx address translation failed\n"); + return -1; + } + + *(u64 *)&fts->msg_buf.data[0] = tx_addr; + *(u64 *)&fts->msg_buf.data[8] = rx_addr; + } else { + fts->msg_buf.cmd_subid = PHYTSPI_MSG_CMD_DATA_XFER; + memcpy_toio(smem_tx, fts->tx, len); + *(u64 *)&fts->msg_buf.data[0] = sizeof(struct msg); + *(u64 *)&fts->msg_buf.data[8] = sizeof(struct msg) + 128; + } + + *(u32 *)&fts->msg_buf.data[16] = len; + fts->msg_buf.data[20] = cs; + fts->msg_buf.data[21] = dfs; + fts->msg_buf.data[22] = mode; + fts->msg_buf.data[23] = tmode; + if (first == 1) + fts->msg_buf.data[24] = 1; + else + fts->msg_buf.data[24] = flags; + ret = spi_phytium_set(fts); + if (len > 16 && fts->dma_get_ddrdata) { + dma_unmap_single(&fts->master->dev, tx_addr, len, + DMA_TO_DEVICE); + dma_unmap_single(&fts->master->dev, rx_addr, len, + DMA_FROM_DEVICE); + } + if (ret) { + dev_err(&fts->master->dev, "AP <-> RV interaction failed\n"); + return ret; + } + if (len <= 16 || !fts->dma_get_ddrdata) + memcpy_fromio(fts->rx, smem_rx, len); + + fts->tx += len; + fts->rx += len; + first = 0; + } while (fts->rx_end > fts->rx); + + return ret; +} +EXPORT_SYMBOL_GPL(spi_phytium_xfer); + +MODULE_AUTHOR("Peng Min "); +MODULE_DESCRIPTION("Phytium SPI adapter core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-phytium-dma.c b/drivers/spi/spi-phytium-dma.c new file mode 100644 index 0000000000000..a1647f9c8fc01 --- /dev/null +++ b/drivers/spi/spi-phytium-dma.c @@ -0,0 +1,588 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Special handling for phytium DMA core + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd.. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "spi-phytium.h" +#include +#include + +#define RX_BUSY 0 +#define RX_BURST_LEVEL 16 +#define TX_BUSY 1 +#define TX_BURST_LEVEL 16 + +#define DMA_MAX_BUF_SIZE 4096 + +static void phytium_spi_dma_maxburst_init(struct phytium_spi *fts) +{ + struct dma_slave_caps caps; + u32 max_burst, def_burst; + int ret; + + def_burst = fts->fifo_len / 2; + + ret = dma_get_slave_caps(fts->rxchan, &caps); + if (!ret && caps.max_burst) + max_burst = caps.max_burst; + else + max_burst = RX_BURST_LEVEL; + + fts->rxburst = min(max_burst, def_burst); + phytium_writel(fts, DMARDLR, 0x0); + + ret = dma_get_slave_caps(fts->txchan, &caps); + if (!ret && caps.max_burst) + max_burst = caps.max_burst; + else + max_burst = TX_BURST_LEVEL; + + /* + * Having a Rx DMA channel serviced with higher priority than a Tx DMA + * channel might not be enough to provide a well balanced DMA-based + * SPI transfer interface. There might still be moments when the Tx DMA + * channel is occasionally handled faster than the Rx DMA channel. + * That in its turn will eventually cause the SPI Rx FIFO overflow if + * SPI bus speed is high enough to fill the SPI Rx FIFO in before it's + * cleared by the Rx DMA channel. In order to fix the problem the Tx + * DMA activity is intentionally slowed down by limiting the SPI Tx + * FIFO depth with a value twice bigger than the Tx burst length. + */ + fts->txburst = min(max_burst, def_burst); + /* set dmatdlr to 0 + 1 */ + phytium_writel(fts, DMATDLR, 0); +} + +static int phytium_spi_dma_init(struct device *dev, + struct phytium_spi *fts) +{ + /* Support ACPI (FixedDMA) and DT (dmas/dma-names) */ + if (has_acpi_companion(dev)) + fts->rxchan = acpi_dma_request_slave_chan_by_index(dev, 0); + else + fts->rxchan = dma_request_chan(dev, "rx"); + if (IS_ERR_OR_NULL(fts->rxchan)) { + fts->rxchan = NULL; + return -ENODEV; + } + + if (has_acpi_companion(dev)) + fts->txchan = acpi_dma_request_slave_chan_by_index(dev, 1); + else + fts->txchan = dma_request_chan(dev, "tx"); + if (IS_ERR_OR_NULL(fts->txchan)) { + dev_err(dev, "can't request chan\n"); + dma_release_channel(fts->rxchan); + fts->rxchan = NULL; + fts->txchan = NULL; + return -ENODEV; + } + + fts->master->dma_rx = fts->rxchan; + fts->master->dma_tx = fts->txchan; + init_completion(&fts->dma_completion); + + phytium_spi_dma_maxburst_init(fts); + fts->dma_sg_burst = 0; + + return 0; +} + +static void phytium_spi_dma_exit(struct phytium_spi *fts) +{ + if (fts->txchan) { + dmaengine_terminate_sync(fts->txchan); + dma_release_channel(fts->txchan); + } + + if (fts->rxchan) { + dmaengine_terminate_sync(fts->rxchan); + dma_release_channel(fts->rxchan); + } +} + +static irqreturn_t phytium_spi_dma_transfer_handler +(struct phytium_spi *fts) +{ + phytium_spi_check_status(fts, false); + + complete(&fts->dma_completion); + + return IRQ_HANDLED; +} + +static bool phytium_spi_can_dma(struct spi_controller *master, + struct spi_device *spi, struct spi_transfer *xfer) +{ + struct phytium_spi *fts = spi_controller_get_devdata(master); + + return xfer->len > fts->fifo_len; +} + +static enum dma_slave_buswidth phytium_spi_dma_convert_width(u8 n_bytes) +{ + if (n_bytes == 1) + return DMA_SLAVE_BUSWIDTH_1_BYTE; + else if (n_bytes == 2) + return DMA_SLAVE_BUSWIDTH_2_BYTES; + + return DMA_SLAVE_BUSWIDTH_UNDEFINED; +} + +static int phytium_spi_dma_wait(struct phytium_spi *fts, unsigned int len, + u32 speed) +{ + unsigned long long ms; + + ms = len * MSEC_PER_SEC * BITS_PER_BYTE; + do_div(ms, speed); + ms += ms + 200; + + if (ms > UINT_MAX) + ms = UINT_MAX; + + ms = wait_for_completion_timeout(&fts->dma_completion, + msecs_to_jiffies(ms)); + + if (ms == 0) { + dev_err(&fts->master->cur_msg->spi->dev, + "DMA transaction timed out\n"); + return -ETIMEDOUT; + } + + /* The same completion is signaled by both normal DMA callbacks + * and the controller error IRQ. On an error IRQ, + * phytium_spi_check_status() sets cur_msg->status to -EIO; + * propagate it so the chunked path stops submitting and + * handle_err terminates outstanding DMA. + */ + if (fts->master->cur_msg->status != -EINPROGRESS) + return fts->master->cur_msg->status; + + return 0; +} + +static inline bool phytium_spi_dma_tx_busy(struct phytium_spi *fts) +{ + return !(phytium_readl(fts, SR) & SR_TF_EMPT); +} + +static void spi_transfer_delay_ns(u32 ns) +{ + if (!ns) + return; + if (ns <= 1000) { + ndelay(ns); + } else { + u32 us = DIV_ROUND_UP(ns, 1000); + + if (us <= 10) + udelay(us); + else + usleep_range(us, us + DIV_ROUND_UP(us, 10)); + } +} + +static int phytium_spi_dma_wait_tx_done(struct phytium_spi *fts, + struct spi_transfer *xfer) +{ + int retry = SPI_WAIT_RETRIES; + u32 ns = 0; + u32 nents = 0; + + nents = phytium_readl(fts, TXFLR); + ns = nents * fts->n_bytes * BITS_PER_BYTE; + ns *= DIV_ROUND_UP(1000000000, max_t(u32, xfer->speed_hz / 2, 1)); + + while (phytium_spi_dma_tx_busy(fts) && retry--) + spi_transfer_delay_ns(ns); + + if (retry < 0) { + dev_err(&fts->master->dev, "Tx hanged up\n"); + return -EIO; + } + + return 0; +} + +/* + * fts->dma_chan_busy is set before the dma transfer starts, + * callback for tx + * channel will clear a corresponding bit. + */ +static void phytium_spi_dma_tx_done(void *arg) +{ + struct phytium_spi *fts = arg; + + clear_bit(TX_BUSY, &fts->dma_chan_busy); + if (test_bit(RX_BUSY, &fts->dma_chan_busy)) + return; + + complete(&fts->dma_completion); +} + +static int phytium_spi_dma_config_tx(struct phytium_spi *fts) +{ + struct dma_slave_config txconf; + + memset(&txconf, 0, sizeof(txconf)); + txconf.direction = DMA_MEM_TO_DEV; + txconf.dst_addr = fts->dma_addr; + txconf.dst_maxburst = fts->txburst; + txconf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + txconf.dst_addr_width = phytium_spi_dma_convert_width(fts->n_bytes); + txconf.device_fc = false; + + return dmaengine_slave_config(fts->txchan, &txconf); +} + +static int phytium_spi_dma_submit_tx(struct phytium_spi *fts, + struct scatterlist *sgl, unsigned int nents) +{ + struct dma_async_tx_descriptor *txdesc; + dma_cookie_t cookie; + int ret; + + txdesc = dmaengine_prep_slave_sg(fts->txchan, sgl, nents, + DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!txdesc) + return -ENOMEM; + + txdesc->callback = phytium_spi_dma_tx_done; + txdesc->callback_param = fts; + + cookie = dmaengine_submit(txdesc); + ret = dma_submit_error(cookie); + if (ret) { + dmaengine_terminate_sync(fts->txchan); + return ret; + } + + set_bit(TX_BUSY, &fts->dma_chan_busy); + + return 0; +} + +static inline bool phytium_spi_dma_rx_busy(struct phytium_spi *fts) +{ + return !!(phytium_readl(fts, SR) & SR_RF_NOT_EMPT); +} + +static int phytium_spi_dma_wait_rx_done(struct phytium_spi *fts) +{ + int retry = SPI_WAIT_RETRIES; + unsigned long ns = 0; + u32 nents = 0; + + /* + * It's unlikely that DMA engine is still doing the data fetching, but + * if it's let's give it some reasonable time. The timeout calculation + * is based on the synchronous APB/SSI reference clock rate, on a + * number of data entries left in the Rx FIFO, times a number of clock + * periods normally needed for a single APB read/write transaction + * without PREADY signal utilized (which is true for the phytium APB SSI + * controller). + */ + nents = phytium_readl(fts, RXFLR); + /* fts->max_freq can be 0 when the clock is unconfigured or the + * ACPI property is absent; guard the division like the TX path. + */ + ns = 4U * NSEC_PER_SEC / max_t(u32, fts->max_freq, 1) * nents; + + while (phytium_spi_dma_rx_busy(fts) && retry--) + spi_transfer_delay_ns(ns); + + if (retry < 0) { + dev_err(&fts->master->dev, "Rx hanged up, nents = %d\n", nents); + return -EIO; + } + + return 0; +} + +/* + * fts->dma_chan_busy is set before the dma transfer starts, + * callback for rx + * channel will clear a corresponding bit. + */ +static void phytium_spi_dma_rx_done(void *arg) +{ + struct phytium_spi *fts = arg; + + clear_bit(RX_BUSY, &fts->dma_chan_busy); + if (test_bit(TX_BUSY, &fts->dma_chan_busy)) + return; + + complete(&fts->dma_completion); +} + +static int phytium_spi_dma_config_rx(struct phytium_spi *fts) +{ + struct dma_slave_config rxconf; + + memset(&rxconf, 0, sizeof(rxconf)); + rxconf.direction = DMA_DEV_TO_MEM; + rxconf.src_addr = fts->dma_addr; + rxconf.src_maxburst = fts->rxburst; + rxconf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + rxconf.src_addr_width = phytium_spi_dma_convert_width(fts->n_bytes); + rxconf.device_fc = false; + + return dmaengine_slave_config(fts->rxchan, &rxconf); +} + +static int phytium_spi_dma_submit_rx(struct phytium_spi *fts, + struct scatterlist *sgl, unsigned int nents) +{ + struct dma_async_tx_descriptor *rxdesc; + dma_cookie_t cookie; + int ret; + + rxdesc = dmaengine_prep_slave_sg(fts->rxchan, sgl, nents, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!rxdesc) + return -ENOMEM; + + rxdesc->callback = phytium_spi_dma_rx_done; + rxdesc->callback_param = fts; + + cookie = dmaengine_submit(rxdesc); + ret = dma_submit_error(cookie); + if (ret) { + dmaengine_terminate_sync(fts->rxchan); + return ret; + } + + set_bit(RX_BUSY, &fts->dma_chan_busy); + + return 0; +} + +static int phytium_spi_dma_setup(struct phytium_spi *fts, + struct spi_transfer *xfer) +{ + u16 imr, dma_ctrl; + int ret; + + if (!xfer->tx_buf) + return -EINVAL; + + /* Setup DMA channels */ + ret = phytium_spi_dma_config_tx(fts); + if (ret) + return ret; + + if (xfer->rx_buf) { + ret = phytium_spi_dma_config_rx(fts); + if (ret) + return ret; + } + + /* Set the DMA handshaking interface */ + dma_ctrl = SPI_DMA_TDMAE; + if (xfer->rx_buf) + dma_ctrl |= SPI_DMA_RDMAE; + phytium_writel(fts, DMACR, dma_ctrl); + + /* Set the interrupt mask */ + imr = INT_TXOI; + if (xfer->rx_buf) + imr |= INT_RXUI | INT_RXOI; + + spi_umask_intr(fts, imr); + + reinit_completion(&fts->dma_completion); + + fts->transfer_handler = phytium_spi_dma_transfer_handler; + + return 0; +} + +static int phytium_spi_dma_transfer_all(struct phytium_spi *fts, + struct spi_transfer *xfer) +{ + int ret; + + /* Submit the DMA Tx transfer */ + ret = phytium_spi_dma_submit_tx(fts, xfer->tx_sg.sgl, + xfer->tx_sg.nents); + if (ret) + goto err_clear_dmac; + + /* Submit the DMA Rx transfer if required */ + if (xfer->rx_buf) { + ret = phytium_spi_dma_submit_rx(fts, xfer->rx_sg.sgl, + xfer->rx_sg.nents); + if (ret) + goto err_clear_dmac; + + /* rx must be started before tx due to spi instinct */ + dma_async_issue_pending(fts->rxchan); + } + + dma_async_issue_pending(fts->txchan); + + ret = phytium_spi_dma_wait(fts, xfer->len, xfer->speed_hz); + +err_clear_dmac: + phytium_writel(fts, DMACR, 0); + + return ret; +} + +static int phytium_spi_dma_transfer_one(struct phytium_spi *fts, + struct spi_transfer *xfer) +{ + struct scatterlist *tx_sg = NULL, *rx_sg = NULL, tx_tmp, rx_tmp; + unsigned int tx_len = 0, rx_len = 0; + unsigned int base, len; + int ret; + + sg_init_table(&tx_tmp, 1); + sg_init_table(&rx_tmp, 1); + + for (base = 0, len = 0; base < xfer->len; base += len) { + /* Fetch next Tx DMA data chunk */ + if (!tx_len) { + tx_sg = !tx_sg ? &xfer->tx_sg.sgl[0] : sg_next(tx_sg); + sg_dma_address(&tx_tmp) = sg_dma_address(tx_sg); + tx_len = sg_dma_len(tx_sg); + } + + /* Fetch next Rx DMA data chunk */ + if (!rx_len) { + rx_sg = !rx_sg ? &xfer->rx_sg.sgl[0] : sg_next(rx_sg); + sg_dma_address(&rx_tmp) = sg_dma_address(rx_sg); + rx_len = sg_dma_len(rx_sg); + } + + if ((base + DMA_MAX_BUF_SIZE) > xfer->len) + len = xfer->len - base; + else + len = DMA_MAX_BUF_SIZE; + + len = min3(len, tx_len, rx_len); + + sg_dma_len(&tx_tmp) = len; + sg_dma_len(&rx_tmp) = len; + + /* Submit DMA Tx transfer */ + ret = phytium_spi_dma_submit_tx(fts, &tx_tmp, 1); + if (ret) + break; + + /* Submit DMA Rx transfer */ + ret = phytium_spi_dma_submit_rx(fts, &rx_tmp, 1); + if (ret) + break; + + /* Rx must be started before Tx due to SPI instinct */ + dma_async_issue_pending(fts->rxchan); + + dma_async_issue_pending(fts->txchan); + + /* + * Here we only need to wait for the DMA transfer to be + * finished since SPI controller is kept enabled during the + * procedure this loop implements and there is no risk to lose + * data left in the Tx/Rx FIFOs. + */ + ret = phytium_spi_dma_wait(fts, len, xfer->speed_hz); + if (ret) + break; + + reinit_completion(&fts->dma_completion); + + sg_dma_address(&tx_tmp) += len; + sg_dma_address(&rx_tmp) += len; + tx_len -= len; + rx_len -= len; + } + + phytium_writel(fts, DMACR, 0); + + return ret; +} + +static int phytium_spi_dma_transfer(struct phytium_spi *fts, + struct spi_transfer *xfer) +{ + unsigned int nents; + int ret; + + /* + * The SPI core substitutes a zero speed with spi->max_speed_hz, + * which can still be zero on a controller with an unconfigured + * clock. Refuse such transfers instead of dividing by zero in + * the timeout estimation below. + */ + if (!xfer->speed_hz) { + dev_err(&fts->master->dev, "DMA transfer speed is zero\n"); + return -EINVAL; + } + + nents = max(xfer->tx_sg.nents, xfer->rx_sg.nents); + + /* + * large transfer length caused spi RX FIFO full event + * transfer 4096 bytes each time + */ + if (xfer->len <= DMA_MAX_BUF_SIZE || !xfer->rx_buf) + ret = phytium_spi_dma_transfer_all(fts, xfer); + else + ret = phytium_spi_dma_transfer_one(fts, xfer); + if (ret) + return ret; + + if (fts->master->cur_msg->status == -EINPROGRESS) { + ret = phytium_spi_dma_wait_tx_done(fts, xfer); + if (ret) + return ret; + } + + if (xfer->rx_buf && fts->master->cur_msg->status == -EINPROGRESS) + ret = phytium_spi_dma_wait_rx_done(fts); + + return ret; +} + +static void phytium_spi_dma_stop(struct phytium_spi *fts) +{ + if (test_bit(TX_BUSY, &fts->dma_chan_busy)) { + dmaengine_terminate_sync(fts->txchan); + clear_bit(TX_BUSY, &fts->dma_chan_busy); + } + if (test_bit(RX_BUSY, &fts->dma_chan_busy)) { + dmaengine_terminate_sync(fts->rxchan); + clear_bit(RX_BUSY, &fts->dma_chan_busy); + } +} + +static const struct phytium_spi_dma_ops phytium_spi_dma_generic_ops = { + .dma_init = phytium_spi_dma_init, + .dma_exit = phytium_spi_dma_exit, + .dma_setup = phytium_spi_dma_setup, + .can_dma = phytium_spi_can_dma, + .dma_transfer = phytium_spi_dma_transfer, + .dma_stop = phytium_spi_dma_stop, +}; + +void phytium_spi_dmaops_set(struct phytium_spi *fts) +{ + fts->dma_ops = &phytium_spi_dma_generic_ops; +} +EXPORT_SYMBOL_GPL(phytium_spi_dmaops_set); + +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-phytium-pci.c b/drivers/spi/spi-phytium-pci.c new file mode 100644 index 0000000000000..3d5391edea4ce --- /dev/null +++ b/drivers/spi/spi-phytium-pci.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SPI core controller PCI driver. + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd. + * + * Derived from drivers/spi/spi-dw-pci.c + * Copyright (c) 2009, 2014 Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "spi-phytium.h" + +#define DRIVER_NAME "phytium_spi_pci" + +static int phytium_spi_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct phytium_spi *fts; + int pci_bar = 0; + int ret; + + fts = devm_kzalloc(&pdev->dev, sizeof(struct phytium_spi), + GFP_KERNEL); + if (!fts) + return -ENOMEM; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + ret = pcim_iomap_regions(pdev, 1 << pci_bar, pci_name(pdev)); + if (ret) { + dev_err(&pdev->dev, "pci iomap failed?\n"); + return ret; + } + + fts->regs = pcim_iomap_table(pdev)[pci_bar]; + if (IS_ERR(fts->regs)) { + dev_err(&pdev->dev, "SPI region map failed\n"); + return PTR_ERR(fts->regs); + } + + fts->irq = pdev->irq; + if (fts->irq < 0) { + dev_err(&pdev->dev, "no irq resource?\n"); + return fts->irq; /* -ENXIO */ + } + + fts->bus_num = -1; + + fts->max_freq = 48000000; + + fts->num_cs = 4; + + fts->global_cs = 1; + + ret = phytium_spi_add_host(&pdev->dev, fts); + if (ret) + return ret; + + pci_set_drvdata(pdev, fts); + return 0; +} + +static void phytium_spi_pci_remove(struct pci_dev *pdev) +{ + struct phytium_spi *fts = pci_get_drvdata(pdev); + + phytium_spi_remove_host(fts); +} + + +#ifdef CONFIG_PM_SLEEP +static int spi_suspend(struct device *dev) +{ + struct phytium_spi *fts = dev_get_drvdata(dev); + + return phytium_spi_suspend_host(fts); +} + +static int spi_resume(struct device *dev) +{ + struct phytium_spi *fts = dev_get_drvdata(dev); + + return phytium_spi_resume_host(fts); +} +#endif + +static SIMPLE_DEV_PM_OPS(phytium_spi_pm_ops, spi_suspend, spi_resume); + +static const struct pci_device_id phytium_device_pci_tbl[] = { + { PCI_VDEVICE(PHYTIUM, 0xdc2c) }, + {}, +}; + +static struct pci_driver phytium_spi_pci_driver = { + .name = DRIVER_NAME, + .id_table = phytium_device_pci_tbl, + .probe = phytium_spi_pci_probe, + .remove = phytium_spi_pci_remove, + .driver = { + .pm = &phytium_spi_pm_ops, + } +}; + +module_pci_driver(phytium_spi_pci_driver); + +MODULE_AUTHOR("Yiqun Zhang "); +MODULE_DESCRIPTION("PCI Driver for Phytium SPI controller core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-phytium-plat-v2.c b/drivers/spi/spi-phytium-plat-v2.c new file mode 100644 index 0000000000000..f25a171a61481 --- /dev/null +++ b/drivers/spi/spi-phytium-plat-v2.c @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SPI core controller platform driver. + * + * Copyright (c) 2023-2024, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "spi-phytium.h" + +#define DRIVER_NAME_PHYT "phytium_spi_2.0" +#define DRIVER_VERSION "1.0.14" + + + +static ssize_t debug_show(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct phytium_spi *fts = dev_get_drvdata(dev); + ssize_t ret; + u32 reg; + + reg = phytium_read_regfile(fts, SPI_REGFILE_DEBUG); + ret = sprintf(buf, "%x\n", reg); + + return ret; +} + +static ssize_t debug_store(struct device *dev, + struct device_attribute *da, const char *buf, + size_t size) +{ + u8 loc, dis_en, status = 0; + char *p, *orig; + char *token; + int ret = 0; + long value; + u32 reg; + struct phytium_spi *fts = dev_get_drvdata(dev); + + dev_info(dev, "echo alive(1)/debug(0) enable(1)/disable(0) > debug\n"); + dev_info(dev, "Example:echo 0 1 > debug; Enable Debug Function\n"); + + p = kmalloc(size, GFP_KERNEL); + if (!p) + return -ENOMEM; + strscpy(p, buf, size); + orig = p; + + token = strsep(&p, " "); + if (!token) { + ret = -EINVAL; + goto out_free; + } + + status = kstrtol(token, 0, &value); + if (status) { + ret = status; + goto out_free; + } + loc = (u8)value; + + token = strsep(&p, " "); + if (!token) { + ret = -EINVAL; + goto out_free; + } + + status = kstrtol(token, 0, &value); + if (status) { + ret = status; + goto out_free; + } + dis_en = value; + + reg = phytium_read_regfile(fts, SPI_REGFILE_DEBUG); + + if (loc == 1) { + if (dis_en == 1) { + fts->alive_enabled = true; + reg |= BIT(loc); + /* start the watchdog timer when alive monitoring + * transitions to enabled */ + mod_timer(&fts->timer, + jiffies + msecs_to_jiffies(10)); + } else if (dis_en == 0) { + fts->alive_enabled = false; + reg &= ~BIT(loc); + } + fts->runtimes = 0; + } else if (loc == 0) { + if (dis_en == 1) { + fts->debug_enabled = true; + reg |= BIT(loc); + } else if (dis_en == 0) { + fts->debug_enabled = false; + reg &= ~BIT(loc); + } + } + + phytium_write_regfile(fts, SPI_REGFILE_DEBUG, reg); + +out_free: + kfree(orig); + + return ret ? ret : size; +} + +static DEVICE_ATTR_RW(debug); + +static struct attribute *spi_phyt_device_attrs[] = { + &dev_attr_debug.attr, + NULL, +}; + +static const struct attribute_group spi_phyt_device_group = { + .attrs = spi_phyt_device_attrs, +}; + +static int spi_phyt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phytium_spi *fts; + struct resource *regfile_mem, *share_mem; + int ret; + int num_cs; + int global_cs = 0; + u32 clk_rate = SPI_DEFAULT_CLK; + + fts = devm_kzalloc(&pdev->dev, sizeof(struct phytium_spi), + GFP_KERNEL); + if (!fts) + return -ENOMEM; + + regfile_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regfile_mem) { + dev_err(&pdev->dev, "no regfile_mem resource?\n"); + return -EINVAL; + } + + fts->regfile = devm_ioremap_resource(&pdev->dev, regfile_mem); + if (IS_ERR(fts->regfile)) { + dev_err(&pdev->dev, "fts->regfile map failed\n"); + return PTR_ERR(fts->regfile); + } + + share_mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!share_mem) { + dev_err(&pdev->dev, "no share_mem resource?\n"); + return -EINVAL; + } + + fts->mem_tx_physic = (u32)share_mem->start + sizeof(struct msg); + fts->mem_rx_physic = (u32)share_mem->start + sizeof(struct msg); + + fts->tx_shmem_addr = devm_ioremap_resource(&pdev->dev, share_mem); + if (IS_ERR(fts->tx_shmem_addr)) { + dev_err(&pdev->dev, "fts->tx_shmem_addr map failed\n"); + return PTR_ERR(fts->tx_shmem_addr); + } + + fts->msg = fts->tx_shmem_addr; + + fts->mem_tx = (u64)fts->msg + sizeof(struct msg); + fts->mem_rx = (u64)fts->msg + sizeof(struct msg); + + fts->irq = platform_get_irq(pdev, 0); + if (fts->irq < 0) { + dev_err(&pdev->dev, "no irq resource?\n"); + return fts->irq; /* -ENXIO */ + } + + if (pdev->dev.of_node) { + fts->clk = devm_clk_get(&pdev->dev, NULL); + + if (IS_ERR(fts->clk)) + return PTR_ERR(fts->clk); + ret = clk_prepare_enable(fts->clk); + if (ret) + return ret; + + fts->max_freq = clk_get_rate(fts->clk); + } else if (has_acpi_companion(&pdev->dev)) { + fwnode_property_read_u32(dev->fwnode, "spi-clock", &clk_rate); + fts->max_freq = clk_rate; + } + + fts->bus_num = pdev->id; + device_property_read_u32(&pdev->dev, "reg-io-width", &fts->reg_io_width); + + num_cs = 4; + + device_property_read_u32(&pdev->dev, "num-cs", &num_cs); + + fts->num_cs = num_cs; + + /* The SPI core acquires and manages the cs-gpios when + * use_gpio_descriptors is set in spi_phyt_add_host; nothing + * to do here. + */ + + device_property_read_u32(&pdev->dev, "global-cs", &global_cs); + fts->global_cs = global_cs; + + fts->regfile_version = phytium_read_regfile(fts, SPI_REGFILE_VERSION_REG); + if (fts->regfile_version & SPI_REGFILE_VERSION_DMA) + fts->dma_get_ddrdata = true; + else + fts->dma_get_ddrdata = false; + + ret = spi_phyt_add_host(&pdev->dev, fts); + if (ret) + goto out; + + platform_set_drvdata(pdev, fts); + + if (sysfs_create_group(&pdev->dev.kobj, &spi_phyt_device_group)) + dev_warn(&pdev->dev, "failed create sysfs\n"); + + return 0; + +out: + clk_disable_unprepare(fts->clk); + return ret; +} + +static void spi_phyt_remove(struct platform_device *pdev) +{ + struct phytium_spi *fts = platform_get_drvdata(pdev); + + spi_phyt_remove_host(fts); + sysfs_remove_group(&pdev->dev.kobj, &spi_phyt_device_group); + clk_disable_unprepare(fts->clk); +} + +#ifdef CONFIG_PM_SLEEP +static int spi_phyt_suspend(struct device *dev) +{ + struct phytium_spi *fts = dev_get_drvdata(dev); + + return spi_phyt_suspend_host(fts); +} + +static int spi_phyt_resume(struct device *dev) +{ + struct phytium_spi *fts = dev_get_drvdata(dev); + + return spi_phyt_resume_host(fts); +} +#endif + +static SIMPLE_DEV_PM_OPS(spi_phyt_pm_ops, spi_phyt_suspend, spi_phyt_resume); + +static const struct of_device_id spi_phyt_of_match[] = { + { .compatible = "phytium,spi-2.0", .data = (void *)0 }, + { /* end of table */} +}; +MODULE_DEVICE_TABLE(of, spi_phyt_of_match); + +static const struct acpi_device_id spi_phyt_acpi_match[] = { + {"PHYT0060", 0}, + {} +}; +MODULE_DEVICE_TABLE(acpi, spi_phyt_acpi_match); + +static struct platform_driver spi_phyt_driver = { + .probe = spi_phyt_probe, + .remove = spi_phyt_remove, + .driver = { + .name = DRIVER_NAME_PHYT, + .of_match_table = of_match_ptr(spi_phyt_of_match), + .acpi_match_table = ACPI_PTR(spi_phyt_acpi_match), + .pm = &spi_phyt_pm_ops, + }, +}; +module_platform_driver(spi_phyt_driver); + +MODULE_AUTHOR("Peng Min "); +MODULE_DESCRIPTION("Platform Driver for Phytium SPI controller core"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); diff --git a/drivers/spi/spi-phytium-plat.c b/drivers/spi/spi-phytium-plat.c new file mode 100644 index 0000000000000..cb8427fb452e4 --- /dev/null +++ b/drivers/spi/spi-phytium-plat.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SPI core controller platform driver. + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd. + * + * Derived from drivers/spi/spi-dw-mmio.c + * Copyright (c) 2010, Octasic semiconductor. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "spi-phytium.h" + +#define DRIVER_NAME "phytium_spi" +#define DRIVER_VERSION "1.0.2" + +#define SPI_PHYTIUM_DEFAULT_CLK_RATE 50000000 + +struct phytium_spi_clk { + struct phytium_spi fts; + struct clk *clk; +}; + +static bool phytium_acpi_has_FixedDMA(struct device *dev) +{ + struct acpi_device *adev; + struct acpi_resource *res; + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; + bool found = false; + acpi_status status; + + if (!dev) + return false; + + adev = ACPI_COMPANION(dev); + if (!adev || !adev->handle) + return false; + + /* Get _CRS resource block (kernel allocates buffer) */ + status = acpi_get_current_resources(adev->handle, &buf); + if (ACPI_FAILURE(status) || !buf.pointer) + return false; + + /* Traverse resource list, exit and return true if FixedDMA is found */ + for (res = buf.pointer; res && res->type != ACPI_RESOURCE_TYPE_END_TAG; + res = ACPI_NEXT_RESOURCE(res)) { + if (res->type == ACPI_RESOURCE_TYPE_FIXED_DMA) { + found = true; + break; + } + } + + kfree(buf.pointer); + return found; +} + +static int phytium_spi_probe(struct platform_device *pdev) +{ + struct phytium_spi_clk *ftsc; + struct phytium_spi *fts; + struct resource *mem; + int ret; + int num_cs; + int global_cs = 1; + u32 clk_rate = SPI_PHYTIUM_DEFAULT_CLK_RATE; + + ftsc = devm_kzalloc(&pdev->dev, sizeof(struct phytium_spi_clk), + GFP_KERNEL); + if (!ftsc) + return -ENOMEM; + + fts = &ftsc->fts; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "no mem resource?\n"); + return -EINVAL; + } + + fts->paddr = mem->start; + fts->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(fts->regs)) { + dev_err(&pdev->dev, "SPI region map failed\n"); + return PTR_ERR(fts->regs); + } + + fts->irq = platform_get_irq(pdev, 0); + if (fts->irq < 0) { + dev_err(&pdev->dev, "no irq resource?\n"); + return fts->irq; /* -ENXIO */ + } + + if (pdev->dev.of_node) { + ftsc->clk = devm_clk_get(&pdev->dev, NULL); + + if (IS_ERR(ftsc->clk)) + return PTR_ERR(ftsc->clk); + ret = clk_prepare_enable(ftsc->clk); + if (ret) + return ret; + + fts->max_freq = clk_get_rate(ftsc->clk); + } else if (has_acpi_companion(&pdev->dev)) { + fwnode_property_read_u32(pdev->dev.fwnode, "spi-clock", &clk_rate); + fts->max_freq = clk_rate; + } + + fts->bus_num = pdev->id; + device_property_read_u32(&pdev->dev, "reg-io-width", &fts->reg_io_width); + + num_cs = 4; + device_property_read_u32(&pdev->dev, "num-cs", &num_cs); + fts->num_cs = num_cs; + + device_property_read_u32(&pdev->dev, "global-cs", &global_cs); + fts->global_cs = global_cs; + + /* check is use dma transfer */ + if ((device_property_read_string_array(&pdev->dev, "dma-names", + NULL, 0) > 0 && + device_property_present(&pdev->dev, "dmas")) || + (has_acpi_companion(&pdev->dev) && + phytium_acpi_has_FixedDMA(&pdev->dev))) { + fts->dma_en = true; + phytium_spi_dmaops_set(fts); + } + + ret = phytium_spi_add_host(&pdev->dev, fts); + if (ret) + goto out; + + platform_set_drvdata(pdev, ftsc); + return 0; + +out: + clk_disable_unprepare(ftsc->clk); + return ret; +} + +static void phytium_spi_remove(struct platform_device *pdev) +{ + struct phytium_spi_clk *ftsc = platform_get_drvdata(pdev); + + phytium_spi_remove_host(&ftsc->fts); + clk_disable_unprepare(ftsc->clk); +} + +#ifdef CONFIG_PM_SLEEP +static int spi_suspend(struct device *dev) +{ + struct phytium_spi_clk *ftsc = dev_get_drvdata(dev); + + return phytium_spi_suspend_host(&ftsc->fts); +} + +static int spi_resume(struct device *dev) +{ + struct phytium_spi_clk *ftsc = dev_get_drvdata(dev); + + return phytium_spi_resume_host(&ftsc->fts); +} +#endif + +static SIMPLE_DEV_PM_OPS(phytium_spi_pm_ops, spi_suspend, spi_resume); + +static const struct of_device_id phytium_spi_of_match[] = { + { .compatible = "phytium,spi", .data = (void *)0 }, + { /* end of table */} +}; +MODULE_DEVICE_TABLE(of, phytium_spi_of_match); + +static const struct acpi_device_id phytium_spi_acpi_match[] = { + {"PHYT000E", 0}, + {} +}; +MODULE_DEVICE_TABLE(acpi, phytium_spi_acpi_match); + +static struct platform_driver phytium_spi_driver = { + .probe = phytium_spi_probe, + .remove = phytium_spi_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(phytium_spi_of_match), + .acpi_match_table = ACPI_PTR(phytium_spi_acpi_match), + .pm = &phytium_spi_pm_ops, + }, +}; +module_platform_driver(phytium_spi_driver); + +MODULE_AUTHOR("Yiqun Zhang "); +MODULE_DESCRIPTION("Platform Driver for Phytium SPI controller core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-phytium-qspi.c b/drivers/spi/spi-phytium-qspi.c new file mode 100644 index 0000000000000..acbf1900f6d59 --- /dev/null +++ b/drivers/spi/spi-phytium-qspi.c @@ -0,0 +1,1007 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium Quad SPI controller driver. + * + * Copyright (c) 2022-2023, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define DRIVER_VERSION "1.0.3" + +#define PHYTIUM_CPU_PART_FTC862 0x862 + +#define MIDR_PHYTIUM_FTC862 MIDR_CPU_MODEL(ARM_CPU_IMP_PHYTIUM, PHYTIUM_CPU_PART_FTC862) + +#define QSPI_FLASH_CAP_REG 0x00 +#define QSPI_FLASH_CAP_NUM_SHIFT 3 +#define QSPI_FLASH_CAP_NUM_MASK (0x3 << QSPI_FLASH_CAP_NUM_SHIFT) +#define QSPI_FLASH_CAP_CAP_SHIFT 0 +#define QSPI_FLASH_CAP_CAP_MASK (0x7 << QSPI_FLASH_CAP_CAP_SHIFT) +#define QSPI_FLASH_CAP_NUM_SHIFT_NEW 16 + +#define QSPI_RD_CFG_REG 0x04 +#define QSPI_RD_CFG_RD_CMD_SHIFT 24 +#define QSPI_RD_CFG_RD_CMD_MASK (0xff << QSPI_RD_CFG_RD_CMD_SHIFT) +#define QSPI_RD_CFG_RD_THROUGH_SHIFT 23 +#define QSPI_RD_CFG_RD_THROUGH_MASK (0x1 << QSPI_RD_CFG_RD_THROUGH_SHIFT) +#define QSPI_RD_CFG_RD_TRANSFER_SHIFT 20 +#define QSPI_RD_CFG_RD_TRANSFER_MASK (0x7 << QSPI_RD_CFG_RD_TRANSFER_SHIFT) +#define QSPI_RD_CFG_RD_ADDR_SEL_SHIFT 19 +#define QSPI_RD_CFG_RD_ADDR_SEL_MASK (0x1 << QSPI_RD_CFG_RD_ADDR_SEL_SHIFT) +#define QSPI_RD_CFG_RD_LATENCY_SHIFT 18 +#define QSPI_RD_CFG_RD_LATENCY_MASK (0x1 << QSPI_RD_CFG_RD_LATENCY_SHIFT) +#define QSPI_RD_CFG_MODE_BYTE_SHIFT 17 +#define QSPI_RD_CFG_MODE_BYTE_MASK (0x1 << QSPI_RD_CFG_MODE_BYTE_SHIFT) +#define QSPI_RD_CFG_CMD_SIGN_SHIFT 9 +#define QSPI_RD_CFG_CMD_SIGN_MASK (0xff << QSPI_RD_CFG_CMD_SIGN_SHIFT) +#define QSPI_RD_CFG_DUMMY_SHIFT 4 +#define QSPI_RD_CFG_DUMMY_MASK (0x1f << QSPI_RD_CFG_DUMMY_SHIFT) +#define QSPI_RD_CFG_D_BUFFER_SHIFT 3 +#define QSPI_RD_CFG_D_BUFFER_MASK (0x1 << QSPI_RD_CFG_D_BUFFER_SHIFT) +#define QSPI_RD_CFG_RD_SCK_SEL_SHIFT 0 +#define QSPI_RD_CFG_RD_SCK_SEL_MASK (0x7 << QSPI_RD_CFG_RD_SCK_SEL_SHIFT) + +#define QSPI_WR_CFG_REG 0x08 +#define QSPI_WR_CFG_WR_CMD_SHIFT 24 +#define QSPI_WR_CFG_WR_CMD_MASK (0xff << QSPI_WR_CFG_WR_CMD_SHIFT) +#define QSPI_WR_CFG_WR_WAIT_SHIFT 9 +#define QSPI_WR_CFG_WR_WAIT_MASK (0x01 << QSPI_WR_CFG_WR_WAIT_SHIFT) +#define QSPI_WR_CFG_WR_THROUGH_SHIFT 8 +#define QSPI_WR_CFG_WR_THROUGH_MASK (0x01 << QSPI_WR_CFG_WR_THROUGH_SHIFT) +#define QSPI_WR_CFG_WR_TRANSFER_SHIFT 5 +#define QSPI_WR_CFG_WR_TRANSFER_MASK (0X7 << QSPI_WR_CFG_WR_TRANSFER_SHIFT) +#define QSPI_WR_CFG_WR_ADDR_SEL_SHIFT 4 +#define QSPI_WR_CFG_WR_ADDR_SEL_MASK (0x1 << QSPI_WR_CFG_WR_ADDR_SEL_SHIFT) +#define QSPI_WR_CFG_WR_MODE_SHIFT 3 +#define QSPI_WR_CFG_WR_MODE_MASK (0x1 << QSPI_WR_CFG_WR_MODE_SHIFT) +#define QSPI_WR_CFG_WR_SCK_SEL_SHIFT 0 +#define QSPI_WR_CFG_WR_SCK_SEL_MASK (0x7 << QSPI_WR_CFG_WR_SCK_SEL_SHIFT) +#define WR_CFG_NODIR_VALUE 0x5000000 + +#define QSPI_FLUSH_REG 0x0c +#define QSPI_FLUSH_EN (0x1 << 0) + +#define QSPI_CMD_PORT_REG 0x10 +#define QSPI_CMD_PORT_CMD_SHIFT 24 +#define QSPI_CMD_PORT_CMD_MASK (0xff << QSPI_CMD_PORT_CMD_SHIFT) +#define QSPI_CMD_PORT_WAIT_SHIFT 22 +#define QSPI_CMD_PORT_WAIT_MASK (0x1 << QSPI_CMD_PORT_WAIT_SHIFT) +#define QSPI_CMD_PORT_THROUGH_SHIFT 21 +#define QSPI_CMD_PORT_THROUGH_MASK (0x1 << QSPI_CMD_PORT_THROUGH_SHIFT) +#define QSPI_CMD_PORT_CS_SHIFT 19 +#define QSPI_CMD_PORT_CS_MASK (0x3 << QSPI_CMD_PORT_CS_SHIFT) +#define QSPI_CMD_PORT_TRANSFER_SHIFT 16 +#define QSPI_CMD_PORT_TRANSFER_MASK (0x7 << QSPI_CMD_PORT_TRANSFER_SHIFT) +#define QSPI_CMD_PORT_CMD_ADDR_SHIFT 15 +#define QSPI_CMD_PORT_CMD_ADDR_MASK (0x1 << QSPI_CMD_PORT_CMD_ADDR_SHIFT) +#define QSPI_CMD_PORT_LATENCY_SHIFT 14 +#define QSPI_CMD_PORT_LATENCY_MASK (0x1 << QSPI_CMD_PORT_LATENCY_SHIFT) +#define QSPI_CMD_PORT_DATA_XFER_SHIFT 13 +#define QSPI_CMD_PORT_DATA_XFER_MASK (0x1 << QSPI_CMD_PORT_DATA_XFER_SHIFT) +#define QSPI_CMD_PORT_ADDR_SEL_SHIFT 12 +#define QSPI_CMD_PORT_ADDR_SEL_MASK (0x1 << QSPI_CMD_PORT_ADDR_SEL_SHIFT) +#define QSPI_CMD_PORT_DUMMY_SHIFT 7 +#define QSPI_CMD_PORT_DUMMY_MASK (0x1f << QSPI_CMD_PORT_DUMMY_SHIFT) +#define QSPI_CMD_PORT_P_BUFFER_SHIFT 6 +#define QSPI_CMD_PORT_P_BUFFER_MASK (0x1 << QSPI_CMD_PORT_P_BUFFER_SHIFT) +#define QSPI_CMD_PORT_RW_NUM_SHIFT 3 +#define QSPI_CMD_PORT_RW_NUM_MASK (0x7 << QSPI_CMD_PORT_RW_NUM_SHIFT) +#define QSPI_CMD_PORT_SCK_SEL_SHIFT 0 +#define QSPI_CMD_PORT_SCK_SEL_MASK (0x7 << QSPI_CMD_PORT_SCK_SEL_SHIFT) + +#define QSPI_ADDR_PORT_REG 0x14 +#define QSPI_HD_PORT_REG 0x18 +#define QSPI_LD_PORT_REG 0x1c + +#define QSPI_FUN_SET_REG 0x20 +#define QSPI_FUN_SET_HOLD_SHIFT 24 +#define QSPI_FUN_SET_HOLD_MASK (0xff << QSPI_FUN_SET_HOLD_SHIFT) +#define QSPI_FUN_SET_SETUP_SHIFT 16 +#define QSPI_FUN_SET_SETUP_MASK (0xff << QSPI_FUN_SET_SETUP_SHIFT) +#define QSPI_FUN_SET_DELAY_SHIFT 0 +#define QSPI_FUN_SET_DELAY_MASK (0xffff << QSPI_FUN_SET_DELAY_SHIFT) + +#define QSPI_WIP_REG 0x24 +#define QSPI_WIP_W_CMD_SHIFT 24 +#define QSPI_WIP_W_CMD_MASK (0xff << QSPI_WIP_W_CMD_SHIFT) +#define QSPI_WIP_W_TRANSFER_SHIFT 3 +#define QSPI_WIP_W_TRANSFER_MASK (0x3 << QSPI_WIP_W_TRANSFER_SHIFT) +#define QSPI_WIP_W_SCK_SEL_SHIFT 0 +#define QSPI_WIP_W_SCK_SEL_MASK (0x7 << QSPI_WIP_W_SCK_SEL_SHIFT) + +#define QSPI_WP_REG 0x28 +#define QSPI_WP_EN_SHIFT 17 +#define QSPI_WP_EN_MASK (0x1 << QSPI_WP_EN_SHIFT) +#define QSPI_WP_IO2_SHIFT 16 +#define QSPI_WP_IO2_MASK (0x1 << QSPI_WP_IO2_SHIFT) +#define QSPI_WP_HOLD_SHIFT 8 +#define QSPI_WP_HOLD_MASK (0xff << QSPI_WP_HOLD_SHIFT) +#define QSPI_WP_SETUP_SHIFT 0 +#define QSPI_WP_SETUP_MASK (0xff << QSPI_WP_SETUP_SHIFT) + +#define QSPI_MODE_REG 0x2c +#define QSPI_MODE_VALID_SHIFT 8 +#define QSPI_MODE_VALID_MASK (0xff << QSPI_MODE_VALID_SHIFT) +#define QSPI_MODE_SHIFT 0 +#define QSPI_MODE_MASK (0xff << QSPI_MODE_SHIFT) + +#define PHYTIUM_QSPI_MAX_NORCHIP 4 +#define PHYTIUM_QSPI_MAX_MMAP_SZ (SZ_256M * PHYTIUM_QSPI_MAX_NORCHIP) +#define PHYTIUM_QSPI_MAX_XFER_SZ 8 +#define PHYTIUM_QSPI_DEFAULT_SCK_SEL 5 + +#define XFER_PROTO_1_1_1 0x0 +#define XFER_PROTO_1_1_2 0x1 +#define XFER_PROTO_1_1_4 0x2 +#define XFER_PROTO_1_2_2 0x3 +#define XFER_PROTO_1_4_4 0x4 +#define XFER_PROTO_2_2_2 0x5 +#define XFER_PROTO_4_4_4 0x6 + +struct phytium_qspi_flash { + u32 cs; + u32 clk_div; + + void __iomem *base; + resource_size_t size; + struct spi_device *spi; +}; + +struct phytium_qspi { + struct device *dev; + struct spi_controller *ctrl; + + void __iomem *io_base; + void __iomem *mm_base; + resource_size_t mm_size; + resource_size_t used_size; + + struct clk *clk; + u32 clk_rate; + + struct phytium_qspi_flash flash[PHYTIUM_QSPI_MAX_NORCHIP]; + u8 fnum; + bool nodirmap; + + u32 wr_cfg_reg[PHYTIUM_QSPI_MAX_NORCHIP]; + u32 rd_cfg_reg[PHYTIUM_QSPI_MAX_NORCHIP]; + u32 flash_cap; + struct notifier_block nb; +}; + +static uint phytium_spi_nor_clac_clk_div(int div) +{ + uint clk_div = 0; + + if (div <= 2) + clk_div = 1; + else if (div <= 4) + clk_div = 2; + else if (div <= 8) + clk_div = 3; + else if (div <= 16) + clk_div = 4; + else if (div <= 32) + clk_div = 5; + else if (div <= 64) + clk_div = 6; + else if (div <= 128) + clk_div = 7; + else + clk_div = 65535; + + return clk_div; +} + +static int phytium_spi_nor_protocol_encode(const struct spi_mem_op *op, u32 *code) +{ + /* + * Phases absent from the operation (e.g. the address and data + * phases of SPI_NOR_WREN_OP) carry a buswidth of zero; treat them + * as the active single-bit width so such operations encode as + * 1-1-1 instead of being rejected. + */ + u8 cmd_width = op->cmd.buswidth ?: 1; + u8 addr_width = op->addr.buswidth ?: 1; + u8 data_width = op->data.buswidth ?: 1; + + if (cmd_width == 1 && addr_width == 1 && data_width == 1) + *code = XFER_PROTO_1_1_1; + else if (cmd_width == 1 && addr_width == 1 && data_width == 2) + *code = XFER_PROTO_1_1_2; + else if (cmd_width == 1 && addr_width == 1 && data_width == 4) + *code = XFER_PROTO_1_1_4; + else if (cmd_width == 1 && addr_width == 2 && data_width == 2) + *code = XFER_PROTO_1_2_2; + else if (cmd_width == 1 && addr_width == 4 && data_width == 4) + *code = XFER_PROTO_1_4_4; + else if (cmd_width == 2 && addr_width == 2 && data_width == 2) + *code = XFER_PROTO_2_2_2; + else if (cmd_width == 4 && addr_width == 4 && data_width == 4) + *code = XFER_PROTO_4_4_4; + else + return -EOPNOTSUPP; + + return 0; +} + +static int phytium_qspi_flash_capacity_encode_new(u32 size, + u32 *cap, int i) +{ + int ret = 0; + + switch (size) { + case SZ_4M: + *cap |= (0x0 << (4 * i)); + break; + case SZ_8M: + *cap |= (0x1 << (4 * i)); + break; + case SZ_16M: + *cap |= (0x2 << (4 * i)); + break; + case SZ_32M: + *cap |= (0x3 << (4 * i)); + break; + case SZ_64M: + *cap |= (0x4 << (4 * i)); + break; + case SZ_128M: + *cap |= (0x5 << (4 * i)); + break; + case SZ_256M: + *cap |= (0x6 << (4 * i)); + break; + case SZ_512M: + *cap |= (0x7 << (4 * i)); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int phytium_qspi_flash_capacity_encode(u32 size, u32 *cap) +{ + int ret = 0; + + switch (size) { + case SZ_4M: + *cap = 0x0; + break; + case SZ_8M: + *cap = 0x1; + break; + case SZ_16M: + *cap = 0x2; + break; + case SZ_32M: + *cap = 0x3; + break; + case SZ_64M: + *cap = 0x4; + break; + case SZ_128M: + *cap = 0x5; + break; + case SZ_256M: + *cap = 0x6; + break; + case SZ_512M: + *cap = 0x7; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int phytium_qspi_write_port(struct phytium_qspi *qspi, + const u8 *buf, const size_t len) +{ + u32 bouncebuf[2] = { 0 }; + + if (len > PHYTIUM_QSPI_MAX_XFER_SZ) { + dev_err(qspi->dev, "WRITE data exceeds 8 bytes.\n"); + return -EINVAL; + } + + memcpy(bouncebuf, buf, len); + + if (len > 4) + writel_relaxed(bouncebuf[1], qspi->io_base + QSPI_HD_PORT_REG); + writel_relaxed(bouncebuf[0], qspi->io_base + QSPI_LD_PORT_REG); + + return 0; +} + +static int phytium_qspi_read_port(struct phytium_qspi *qspi, + u8 *buf, size_t len) +{ + u32 bouncebuf[2] = { 0 }; + + if (len > PHYTIUM_QSPI_MAX_XFER_SZ) { + dev_err(qspi->dev, "READ data exceeds 8 bytes.\n"); + return -EINVAL; + } + + /* Dummy write to LD_PORT register and issue READ ops*/ + writel_relaxed(0, qspi->io_base + QSPI_LD_PORT_REG); + + /* Read data */ + bouncebuf[0] = readl_relaxed(qspi->io_base + QSPI_LD_PORT_REG); + if (len > 4) + bouncebuf[1] = readl_relaxed(qspi->io_base + QSPI_HD_PORT_REG); + + memcpy(buf, bouncebuf, len); + + return 0; +} + +static int phytium_qspi_adjust_op_size(struct spi_mem *mem, + struct spi_mem_op *op) +{ + if (op->data.nbytes > PHYTIUM_QSPI_MAX_XFER_SZ) + op->data.nbytes = PHYTIUM_QSPI_MAX_XFER_SZ; + + return 0; +} + +static bool phytium_qspi_supports_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + u32 code; + + /* + * Apply the same protocol encoding check exec_op() uses, so an + * operation accepted here can never fail later with + * -EOPNOTSUPP. Phase-less operations (e.g. WREN) are normalized + * to 1-bit by the encoder and accepted as 1-1-1. + */ + if (phytium_spi_nor_protocol_encode(op, &code)) + return false; + + /* Max 32 dummy clock cycles supported */ + if (op->dummy.nbytes && + (op->dummy.nbytes * 8 / op->dummy.buswidth > 32)) + return false; + + return spi_mem_default_supports_op(mem, op); +} + +static int phytium_qspi_exec_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + struct phytium_qspi *qspi = spi_controller_get_devdata(mem->spi->controller); + struct phytium_qspi_flash *flash = &qspi->flash[spi_get_chipselect(mem->spi, 0)]; + u32 cmd, transfer; + int ret = 0; + + dev_dbg(qspi->dev, "cmd:%#x mode: %d.%d.%d.%d addr:%#llx len:%#x\n", + op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth, + op->dummy.buswidth, op->data.buswidth, op->addr.val, + op->data.nbytes); + + cmd = op->cmd.opcode << QSPI_CMD_PORT_CMD_SHIFT; + cmd |= flash->cs << QSPI_CMD_PORT_CS_SHIFT; + + ret = phytium_spi_nor_protocol_encode(op, &transfer); + if (ret) { + dev_err(qspi->dev, "Unsupported SPI NOR protocol.\n"); + goto out; + } + cmd |= transfer << QSPI_CMD_PORT_TRANSFER_SHIFT; + + if (op->addr.nbytes) { + cmd |= QSPI_CMD_PORT_CMD_ADDR_MASK; + if (op->addr.nbytes == 4) + cmd |= QSPI_CMD_PORT_ADDR_SEL_MASK; + + /* Write target address to ADDR_PORT register */ + writel_relaxed(op->addr.val, qspi->io_base + QSPI_ADDR_PORT_REG); + } + + if (op->dummy.nbytes) { + cmd |= QSPI_CMD_PORT_LATENCY_MASK; + cmd |= (((op->dummy.nbytes * 8 - 1) / op->dummy.buswidth) << + QSPI_CMD_PORT_DUMMY_SHIFT) & QSPI_CMD_PORT_DUMMY_MASK; + } + + if (op->data.nbytes) { + cmd |= QSPI_CMD_PORT_DATA_XFER_MASK; + cmd &= ~QSPI_CMD_PORT_P_BUFFER_MASK; + cmd |= (op->data.nbytes-1) << QSPI_CMD_PORT_RW_NUM_SHIFT; + } + + cmd |= flash->clk_div; + writel_relaxed(cmd, qspi->io_base + QSPI_CMD_PORT_REG); + + if (op->data.dir == SPI_MEM_DATA_IN) { + ret = phytium_qspi_read_port(qspi, op->data.buf.in, op->data.nbytes); + if (ret) { + dev_err(qspi->dev, "Failed to read data from the port.\n"); + goto out; + } + } else if (op->data.dir == SPI_MEM_DATA_OUT) { + ret = phytium_qspi_write_port(qspi, op->data.buf.out, op->data.nbytes); + if (ret) { + dev_err(qspi->dev, "Failed to write data to the port.\n"); + goto out; + } + } else { + /* Dummy write to LD_PORT register and issue the command */ + writel_relaxed(0, qspi->io_base + QSPI_LD_PORT_REG); + } + +out: + return ret; +} + +static int phytium_qspi_dirmap_create(struct spi_mem_dirmap_desc *desc) +{ + struct spi_device *spi = desc->mem->spi; + struct phytium_qspi *qspi = spi_controller_get_devdata(spi->controller); + struct phytium_qspi_flash *flash = &qspi->flash[spi_get_chipselect(spi, 0)]; + struct spi_nor *nor = spi_mem_get_drvdata(desc->mem); + u32 cmd, transfer; + int ret = 0; + + if (!qspi->mm_base || !qspi->mm_size) { + ret = -EOPNOTSUPP; + goto out; + } + + if (!flash->base) { + if (qspi->used_size + nor->mtd.size > qspi->mm_size) { + dev_err(qspi->dev, + "direct mapping window too small for the flashes\n"); + ret = -EOPNOTSUPP; + goto out; + } + flash->base = qspi->mm_base + qspi->used_size; + qspi->used_size += nor->mtd.size; + } + + /* Setup RD/WR_CFG register */ + if (desc->info.op_tmpl->data.dir == SPI_MEM_DATA_IN) { + cmd = desc->info.op_tmpl->cmd.opcode << QSPI_RD_CFG_RD_CMD_SHIFT; + ret = phytium_spi_nor_protocol_encode(desc->info.op_tmpl, &transfer); + if (ret) { + dev_err(qspi->dev, "Unsupported SPI NOR protocol.\n"); + goto out; + } + cmd |= transfer << QSPI_RD_CFG_RD_TRANSFER_SHIFT; + + if (desc->info.op_tmpl->addr.nbytes == 4) + cmd |= QSPI_RD_CFG_RD_ADDR_SEL_MASK; + + if (nor->read_dummy) { + cmd |= QSPI_RD_CFG_RD_LATENCY_MASK; + cmd |= (nor->read_dummy - 1) << QSPI_RD_CFG_DUMMY_SHIFT; + } + + cmd |= QSPI_RD_CFG_D_BUFFER_MASK; + cmd |= flash->clk_div & QSPI_RD_CFG_RD_SCK_SEL_MASK; + + writel_relaxed(cmd, qspi->io_base + QSPI_RD_CFG_REG); + qspi->rd_cfg_reg[spi_get_chipselect(spi, 0)] = cmd; + + dev_dbg(qspi->dev, "Create read dirmap and setup RD_CFG_REG [%#x].\n", cmd); + } else if (desc->info.op_tmpl->data.dir == SPI_MEM_DATA_OUT) { + cmd = desc->info.op_tmpl->cmd.opcode << QSPI_WR_CFG_WR_CMD_SHIFT; + ret = phytium_spi_nor_protocol_encode(desc->info.op_tmpl, &transfer); + if (ret) { + dev_err(qspi->dev, "Unsupported SPI NOR protocol.\n"); + goto out; + } + cmd |= transfer << QSPI_WR_CFG_WR_TRANSFER_SHIFT; + + if (desc->info.op_tmpl->addr.nbytes == 4) + cmd |= QSPI_WR_CFG_WR_ADDR_SEL_MASK; + + cmd |= QSPI_WR_CFG_WR_MODE_MASK; + cmd |= flash->clk_div & QSPI_WR_CFG_WR_SCK_SEL_MASK; + qspi->wr_cfg_reg[spi_get_chipselect(spi, 0)] = cmd; + } else { + ret = -EINVAL; + } + +out: + return ret; +} + +static ssize_t phytium_qspi_dirmap_read(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, void *buf) +{ + struct spi_device *spi = desc->mem->spi; + struct phytium_qspi *qspi = spi_controller_get_devdata(spi->controller); + struct phytium_qspi_flash *flash = &qspi->flash[spi_get_chipselect(spi, 0)]; + + void __iomem *src = flash->base + offs; + u8 *buf_rx = buf; + + writel_relaxed(qspi->rd_cfg_reg[spi_get_chipselect(spi, 0)], qspi->io_base + QSPI_RD_CFG_REG); + memcpy_fromio(buf_rx, src, len); + + return len; +} + +static ssize_t phytium_qspi_dirmap_write(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, const void *buf) +{ + struct spi_device *spi = desc->mem->spi; + struct phytium_qspi *qspi = spi_controller_get_devdata(spi->controller); + struct phytium_qspi_flash *flash = &qspi->flash[spi_get_chipselect(spi, 0)]; + + void __iomem *dst = flash->base + offs; + void __iomem *addr; + int i; + size_t mask = 0x03; + size_t orig_len = len; + + /* set wr_cfg for dirmap write */ + writel_relaxed(qspi->wr_cfg_reg[spi_get_chipselect(spi, 0)], qspi->io_base + QSPI_WR_CFG_REG); + + /* Handle an unaligned leading fragment so byte-granular MTD writes work. + * Align the MMIO address down to a 4-byte boundary and place the + * leading source bytes at the matching lane offset, so the write + * targets the correct byte lanes and all subsequent writes stay + * aligned. + */ + if (offs & 0x03) { + size_t lane = offs & 0x03; + size_t head = min(4 - lane, len); + u_char tmp[4] = { 0xff, 0xff, 0xff, 0xff }; + + memcpy(tmp + lane, buf, head); + writel_relaxed(*(u32 *)tmp, dst - lane); + buf += head; + dst += head; + len -= head; + } + + for (i = 0; i < len / 4; i++) + writel_relaxed(*(u32 *)(buf + 4 * i), dst + 4 * i); + + if (len & mask) { + /* unused byte lanes must stay 0xff so adjacent flash + * content is not programmed to zero */ + u_char tmp[4] = { 0xff, 0xff, 0xff, 0xff }; + + addr = dst + (len & ~mask); + memcpy(tmp, buf + (len & ~mask), len & mask); + writel_relaxed(*(u32 *)(tmp), addr); + } + + //write cache data to flash + writel_relaxed(QSPI_FLUSH_EN, qspi->io_base + QSPI_FLUSH_REG); + + return orig_len; +} + +static int phytium_qspi_program_capacity(struct phytium_qspi *qspi) +{ + struct device *dev = qspi->dev; + struct spi_mem *mem; + struct spi_nor *nor; + u32 flash_cap = 0; + int first_flash = -1; + int i, ret; + + if (qspi->fnum == 0) + return 0; + + /* Discover the size of every bound flash */ + for (i = 0; i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { + if (!qspi->flash[i].spi) + continue; + if (!qspi->flash[i].spi->dev.driver || + strcmp(qspi->flash[i].spi->dev.driver->name, "spi-nor")) + return -EPROBE_DEFER; + if (first_flash < 0) + first_flash = i; + mem = spi_get_drvdata(qspi->flash[i].spi); + if (mem) { + nor = spi_mem_get_drvdata(mem); + if (nor) + qspi->flash[i].size = nor->mtd.size; + } + if (!qspi->flash[i].size) + return -EPROBE_DEFER; + } + + if (!qspi->nodirmap) { + for (i = 0; i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { + if (!qspi->flash[i].spi || i == first_flash) + continue; + if (qspi->flash[i].size != qspi->flash[first_flash].size) { + dev_err(dev, "Flashes are of different sizes.\n"); + return -EINVAL; + } + } + + ret = phytium_qspi_flash_capacity_encode(qspi->flash[first_flash].size, + &flash_cap); + if (ret) { + dev_err(dev, "Flash size is invalid.\n"); + return ret; + } + + if (qspi->fnum > (QSPI_FLASH_CAP_NUM_MASK >> QSPI_FLASH_CAP_NUM_SHIFT)) + dev_warn(dev, "%u flashes exceed the CAP NUM field width\n", + qspi->fnum); + flash_cap |= (qspi->fnum << QSPI_FLASH_CAP_NUM_SHIFT) & + QSPI_FLASH_CAP_NUM_MASK; + qspi->flash_cap = flash_cap; + writel_relaxed(qspi->flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); + } else { + for (i = 0; i < PHYTIUM_QSPI_MAX_NORCHIP; i++) { + if (!qspi->flash[i].spi) + continue; + ret = phytium_qspi_flash_capacity_encode_new(qspi->flash[i].size, + &qspi->flash_cap, i); + if (ret) { + dev_err(dev, "Flash size is invalid.\n"); + return ret; + } + } + qspi->flash_cap |= (qspi->fnum - 1) << QSPI_FLASH_CAP_NUM_SHIFT_NEW; + writel_relaxed(qspi->flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); + } + + return 0; +} + +static int phytium_qspi_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct phytium_qspi *qspi = + container_of(nb, struct phytium_qspi, nb); + struct device *dev = data; + int ret; + + if (action != BUS_NOTIFY_BOUND_DRIVER) + return NOTIFY_DONE; + if (!dev->driver || strcmp(dev->driver->name, "spi-nor")) + return NOTIFY_DONE; + /* SPI children are parented by the controller device, not the + * platform device that owns the controller. */ + if (dev->parent != &qspi->ctrl->dev) + return NOTIFY_DONE; + + ret = phytium_qspi_program_capacity(qspi); + if (ret && ret != -EPROBE_DEFER) + dev_err(qspi->dev, "capacity programming failed: %d\n", ret); + + return NOTIFY_OK; +} + +static int phytium_qspi_setup(struct spi_device *spi) +{ struct spi_controller *ctrl = spi->controller; + struct phytium_qspi *qspi = spi_controller_get_devdata(ctrl); + struct phytium_qspi_flash *flash; + uint clk_div; + + if (ctrl->busy) + return -EBUSY; + + if (spi_get_chipselect(spi, 0) >= PHYTIUM_QSPI_MAX_NORCHIP) { + dev_err(&spi->dev, "Flash CS is out of range.\n"); + return -EINVAL; + } + + flash = &qspi->flash[spi_get_chipselect(spi, 0)]; + + /* validate the clock divider before committing any state */ + if (spi->max_speed_hz) { + clk_div = DIV_ROUND_UP(qspi->clk_rate, spi->max_speed_hz); + clk_div = phytium_spi_nor_clac_clk_div(clk_div); + if (clk_div == 65535) { + dev_err(qspi->dev, "qspi maximum frequency setting is error.\n"); + return -EINVAL; + } + flash->clk_div = clk_div; + } else { + flash->clk_div = PHYTIUM_QSPI_DEFAULT_SCK_SEL; + } + + /* install the slot only once; setup may be called repeatedly */ + if (!flash->spi) { + flash->cs = spi_get_chipselect(spi, 0); + flash->spi = spi; + qspi->fnum++; + } + + return 0; +} + +static struct spi_controller_mem_ops phytium_qspi_mem_ops = { + .adjust_op_size = phytium_qspi_adjust_op_size, + .supports_op = phytium_qspi_supports_op, + .exec_op = phytium_qspi_exec_op, + .dirmap_create = phytium_qspi_dirmap_create, + .dirmap_read = phytium_qspi_dirmap_read, + .dirmap_write = phytium_qspi_dirmap_write, +}; + +/** + * Direct mapping is supported only when all flashes under the controller + * are of the same size and the mapping address is continuous. For those + * cases which flashes are of different sizes, the driver offered a non-dirmap + * mem_ops with which read/write ops is executed through command port. + */ +static struct spi_controller_mem_ops phytium_qspi_mem_ops_nodirmap = { + .adjust_op_size = phytium_qspi_adjust_op_size, + .supports_op = phytium_qspi_supports_op, + .exec_op = phytium_qspi_exec_op, +}; + +/** + * phytium_qspi_probe - Probe method for the QSPI driver + * @pdev: Pointer to the platform_device structure + * + * This function initializes the driver data structures and the hardware. + * + * Return: 0 on success and error value on failure + */ +static int phytium_qspi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct spi_controller *ctrl; + struct resource *res = NULL; + struct phytium_qspi *qspi; + int ret; + const char **reg_name_array; + + ctrl = devm_spi_alloc_host(dev, sizeof(*qspi)); + if (!ctrl) + return -ENOMEM; + + ctrl->mode_bits = SPI_CPOL | SPI_CPHA | + SPI_RX_DUAL | SPI_RX_QUAD | + SPI_TX_DUAL | SPI_TX_QUAD; + ctrl->setup = phytium_qspi_setup; + ctrl->num_chipselect = PHYTIUM_QSPI_MAX_NORCHIP; + if (IS_ENABLED(CONFIG_OF)) + ctrl->dev.of_node = dev->of_node; + else if (IS_ENABLED(CONFIG_ACPI) && has_acpi_companion(dev)) + ctrl->dev.fwnode = dev->fwnode; + + qspi = spi_controller_get_devdata(ctrl); + qspi->ctrl = ctrl; + + reg_name_array = devm_kcalloc(dev, 4, sizeof(*reg_name_array), + GFP_KERNEL); + if (!reg_name_array) { + ret = -ENOMEM; + return ret; + } + if (dev->of_node) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi"); + } else if (has_acpi_companion(dev)) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "missing QSPI register resource\n"); + ret = -ENODEV; + return ret; + } + fwnode_property_read_string_array(dev->fwnode, + "reg-names", reg_name_array, 2); + if (reg_name_array[0]) + res->name = reg_name_array[0]; + } + qspi->io_base = devm_ioremap_resource(dev, res); + if (IS_ERR(qspi->io_base)) { + ret = PTR_ERR(qspi->io_base); + return ret; + } + + if (dev->of_node) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi_mm"); + } else if (has_acpi_companion(dev)) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) { + dev_err(dev, "missing QSPI memory resource\n"); + ret = -ENODEV; + return ret; + } + if (reg_name_array[1]) + res->name = reg_name_array[1]; + } + + qspi->mm_base = devm_ioremap_resource(dev, res); + if (IS_ERR(qspi->mm_base)) { + ret = PTR_ERR(qspi->mm_base); + return ret; + } + + qspi->mm_size = resource_size(res); + if (qspi->mm_size > PHYTIUM_QSPI_MAX_MMAP_SZ) { + ret = -EINVAL; + return ret; + } + qspi->used_size = 0; + + if (dev->of_node) { + qspi->clk = devm_clk_get(dev, NULL); + if (IS_ERR(qspi->clk)) { + ret = PTR_ERR(qspi->clk); + return ret; + } + + qspi->clk_rate = clk_get_rate(qspi->clk); + if (!qspi->clk_rate) { + ret = -EINVAL; + return ret; + } + + pm_runtime_enable(dev); + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + pm_runtime_put_noidle(dev); + return ret; + } + + ret = clk_prepare_enable(qspi->clk); + if (ret) { + dev_err(dev, "Failed to enable PCLK of the controller.\n"); + goto probe_clk_failed; + } + } else if (has_acpi_companion(dev)) { + qspi->clk_rate = 50000000; + } + qspi->nodirmap = device_property_present(dev, "no-direct-mapping"); + ctrl->mem_ops = qspi->nodirmap ? + &phytium_qspi_mem_ops_nodirmap : + &phytium_qspi_mem_ops; + + qspi->dev = dev; + platform_set_drvdata(pdev, qspi); + + ret = spi_register_controller(ctrl); + if (ret) { + dev_err(dev, "failed to register SPI controller: %d\n", ret); + goto probe_clk_failed; + } + + /* + * Program flash capacity registers. The flash sizes come from the + * spi-nor driver bound to each child. When MTD_SPI_NOR is a module + * that has not been loaded yet, the children exist but have no + * driver — return success now and register a bus notifier so the + * capacity registers are programmed when spi-nor binds. + */ + ret = phytium_qspi_program_capacity(qspi); + if (ret == -EPROBE_DEFER) { + qspi->nb.notifier_call = phytium_qspi_notifier; + ret = bus_register_notifier(&spi_bus_type, &qspi->nb); + if (ret) { + dev_err(dev, "failed to register bus notifier: %d\n", ret); + goto probe_setup_failed; + } + /* retry now in case spi-nor bound between the first attempt + * and the notifier registration */ + ret = phytium_qspi_program_capacity(qspi); + if (ret && ret != -EPROBE_DEFER) { + bus_unregister_notifier(&spi_bus_type, &qspi->nb); + dev_err(dev, "capacity programming failed: %d\n", ret); + goto probe_setup_failed; + } + } else if (ret) { + goto probe_setup_failed; + } + + return 0; + +probe_setup_failed: + /* the controller was registered: tear it down before killing + * the clock it depends on */ + spi_unregister_controller(ctrl); +probe_clk_failed: + clk_disable_unprepare(qspi->clk); + if (dev->of_node) { + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + } + return ret; +} + +/** + * phytium_qspi_remove - Remove method for the QSPI driver + * @pdev: Pointer to the platform_device structure + * + * This function is called if a device is physically removed from the system + * or if the driver module is being unloaded. It free all resources allocated + * to the device. + * + * Return: 0 on success and error value on failure + */ +static void phytium_qspi_remove(struct platform_device *pdev) +{ + struct phytium_qspi *qspi = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + bus_unregister_notifier(&spi_bus_type, &qspi->nb); + /* unregister children and the transfer queue before the hardware + * (clock, runtime PM) is turned off */ + spi_unregister_controller(qspi->ctrl); + + clk_disable_unprepare(qspi->clk); + + if (dev->of_node) { + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + } +} + +static int __maybe_unused phytium_qspi_suspend(struct device *dev) +{ + return pm_runtime_force_suspend(dev); +} + +static int __maybe_unused phytium_qspi_resume(struct device *dev) +{ + struct phytium_qspi *qspi = dev_get_drvdata(dev); + + if (!qspi->nodirmap) { + /* set rd_cfg reg and flash_capacity reg after resume */ + writel_relaxed(qspi->flash_cap, qspi->io_base + QSPI_FLASH_CAP_REG); + } else { + writel_relaxed(WR_CFG_NODIR_VALUE, qspi->io_base + QSPI_WR_CFG_REG); + } + + return pm_runtime_force_resume(dev); +} + +static const struct dev_pm_ops phytium_qspi_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(phytium_qspi_suspend, + phytium_qspi_resume) +}; + +static const struct of_device_id phytium_qspi_of_match[] = { + { .compatible = "phytium,qspi-nor" }, + { } +}; +static const struct acpi_device_id phytium_qspi_acpi_match[] = { + { "PHYT0011", 0 }, + { } +}; +MODULE_DEVICE_TABLE(of, phytium_qspi_of_match); +MODULE_DEVICE_TABLE(acpi, phytium_qspi_acpi_match); + +static struct platform_driver phytium_qspi_driver = { + .probe = phytium_qspi_probe, + .remove = phytium_qspi_remove, + .driver = { + .name = "phytium-qspi", + .of_match_table = of_match_ptr(phytium_qspi_of_match), + .acpi_match_table = ACPI_PTR(phytium_qspi_acpi_match), + .pm = &phytium_qspi_pm_ops, + }, +}; +module_platform_driver(phytium_qspi_driver); + +MODULE_AUTHOR("Chen Baozi "); +MODULE_DESCRIPTION("Phytium Quad SPI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-phytium-v2.c b/drivers/spi/spi-phytium-v2.c new file mode 100644 index 0000000000000..4487620c91dd3 --- /dev/null +++ b/drivers/spi/spi-phytium-v2.c @@ -0,0 +1,708 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SPI core controller driver. + * + * Copyright (c) 2023-2024, Phytium Technology Co., Ltd.. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "spi-phytium.h" + +#define MCP251x_READ 0x03 +#define MCP251x_READ_RXB0 0x90 +#define MCP251x_READ_RXB1 0x94 + +static inline int spi_phyt_enable_chip(struct phytium_spi *fts, u8 enable) +{ + u8 val = enable ? 1 : 2; + + return spi_phytium_set_cmd8(fts, PHYTSPI_MSG_CMD_SET_MODULE_EN, val); +} + +static inline int spi_phyt_set_clk(struct phytium_spi *fts, u16 div) +{ + u32 new_div = div; + + return spi_phytium_set_cmd32(fts, PHYTSPI_MSG_CMD_SET_BAUDR, new_div); +} + +static inline int spi_phyt_dma_reset(struct phytium_spi *fts, u8 enable) +{ + return spi_phytium_set_cmd8(fts, PHYTSPI_MSG_CMD_SET_DMA_RESET, enable); +} + +static inline int spi_phyt_global_cs(struct phytium_spi *fts) +{ + u32 global_cs_en; + u16 cs; + + global_cs_en = GENMASK(fts->num_cs-1, 0) << fts->num_cs; + + cs = (u16)((0x1 << 8) | global_cs_en); + return spi_phytium_set_cmd16(fts, PHYTSPI_MSG_CMD_SET_CS, cs); +} + +static inline void spi_phyt_reset_chip(struct phytium_spi *fts) +{ + spi_phyt_dma_reset(fts, 1); + spi_phyt_enable_chip(fts, 0); + if (fts->global_cs) + spi_phyt_global_cs(fts); + spi_phyt_enable_chip(fts, 1); +} + +static inline int spi_phyt_shutdown_chip(struct phytium_spi *fts) +{ + int ret; + + ret = spi_phyt_enable_chip(fts, 0); + if (ret) + return ret; + + return spi_phyt_set_clk(fts, 0); +} + +struct phytium_spi_chip { + u8 poll_mode; + u8 type; + void (*cs_control)(u32 command); +}; + +struct chip_data { + u8 cs; + u8 tmode; + u8 type; + + u8 poll_mode; + + u16 clk_div; + u32 speed_hz; + void (*cs_control)(u32 command); +}; + +static void spi_phyt_set_cs(struct spi_device *spi, bool enable) +{ + struct phytium_spi *fts = spi_controller_get_devdata(spi->controller); + struct chip_data *chip = spi_get_ctldata(spi); + u32 origin; + u16 cs; + + if (fts->tx || fts->rx) + return; + + if (fts->msg_buf.cmd_id == PHYTSPI_MSG_CMD_DATA && + fts->msg_buf.cmd_subid == PHYTSPI_MSG_CMD_DATA_TX) + return; + + if (chip && chip->cs_control) + chip->cs_control(!enable); + + if (!enable) { + cs = BIT(spi_get_chipselect(spi, 0)); + spi_phytium_set_cmd16(fts, PHYTSPI_MSG_CMD_SET_CS, cs); + if (fts->global_cs) { + origin = (GENMASK(fts->num_cs-1, 0) << fts->num_cs) + | (1 << spi_get_chipselect(spi, 0)); + cs = (0x1 << 8) | origin; + spi_phytium_set_cmd16(fts, PHYTSPI_MSG_CMD_SET_CS, cs); + } + } else { + if (fts->global_cs) { + origin = (GENMASK(fts->num_cs-1, 0) << fts->num_cs) + & ~(1 << spi_get_chipselect(spi, 0)); + cs = (0x1 << 8) | origin; + spi_phytium_set_cmd16(fts, PHYTSPI_MSG_CMD_SET_CS, cs); + } + } +} + +static irqreturn_t spi_phyt_irq(int irq, void *dev_id) +{ + struct spi_controller *master = dev_id; + struct phytium_spi *fts = spi_controller_get_devdata(master); + u32 state; + + /* Check whether this controller actually raised the interrupt; + * the handler is registered with IRQF_SHARED. + */ + state = readl_relaxed(fts->regfile + SPI_REGFILE_RV2AP_INTR_STATE); + if (!state) + return IRQ_NONE; + + /* Acknowledge before completing so a waiter on another CPU cannot + * submit a new command before the old status is cleared. + */ + writel_relaxed(0, fts->regfile + SPI_REGFILE_RV2AP_INTR_STATE); + writel_relaxed(0x10, fts->regfile + SPI_REGFILE_RV2AP_INT_CLEAN); + complete(&fts->cmd_completion); + + return IRQ_HANDLED; +} + +static int spi_phyt_transfer_one(struct spi_controller *master, + struct spi_device *spi, struct spi_transfer *transfer) +{ + struct phytium_spi *fts = spi_controller_get_devdata(master); + struct chip_data *chip = spi_get_ctldata(spi); + struct spi_mem *mem = NULL; + struct spi_nor *nor = NULL; + int ret; + + /* + * spi_get_drvdata() returns whatever the client driver stored: + * spi_mem for spi-mem drivers, but e.g. mcp251x_priv for the + * MCP251x CAN controller also supported here. Only interpret it + * as a struct spi_mem when the client is actually the spi-nor + * driver, otherwise the dereference below crashes on ordinary + * SPI clients. + */ + if (spi->dev.driver && !strcmp(spi->dev.driver->name, "spi-nor")) { + mem = spi_get_drvdata(spi); + if (mem) + nor = spi_mem_get_drvdata(mem); + } + + if (!transfer->tx_buf && !transfer->rx_buf) { + /* clock-only transfers without buffers are not supported */ + dev_err(&master->dev, "transfer with no tx/rx buffer\n"); + return -EOPNOTSUPP; + } + + fts->tx = (void *)transfer->tx_buf; + fts->tx_end = fts->tx + transfer->len; + fts->rx = transfer->rx_buf; + fts->rx_end = fts->rx + transfer->len; + fts->len = transfer->len; + + if (chip->cs_control) { + if (fts->rx && fts->tx) + chip->tmode = TMOD_TR; + else if (fts->rx) + chip->tmode = TMOD_RO; + else + chip->tmode = TMOD_TO; + } + + if (fts->tx && fts->rx) { + if (fts->half_duplex) { + dev_err(&master->dev, "SPI-V2 not support full duplex\n"); + return -EPERM; + } + ret = spi_phytium_xfer(fts, spi_get_chipselect(spi, 0), transfer->bits_per_word, + spi->mode, chip->tmode, 0); + return ret; + } + + if (mem != NULL && nor != NULL && mem == nor->spimem && fts->tx && fts->len == 1) { + if ((*(u8 *)fts->tx == SPINOR_OP_WREN) && fts->spi_write_flag == 0) { + spi_phytium_write_pre(fts, spi_get_chipselect(spi, 0), + transfer->bits_per_word, spi->mode, + chip->tmode, 3, fts->spi_write_flag); + fts->spi_write_flag++; + return 0; + } + + if ((*(u8 *)fts->tx == SPINOR_OP_BE_4K) && (fts->spi_write_flag == 1) && + fts->flash_read == 0 && fts->flash_erase != 1) { + fts->spi_write_flag++; + fts->flash_erase = 1; + return 0; + } + + if ((*(u8 *)fts->tx == SPINOR_OP_CHIP_ERASE) && (fts->spi_write_flag == 1) && + fts->flash_read == 0 && fts->flash_erase == 0) { + ret = spi_phytium_flash_erase(fts, spi_get_chipselect(spi, 0), + transfer->bits_per_word, + spi->mode, chip->tmode, 3, SPINOR_OP_CHIP_ERASE); + if (ret) { + dev_err(&spi->dev, "chip erase failed: %d\n", ret); + return ret; + } + fts->spi_write_flag = 0; + fts->flash_erase = 2; + /* the erase was issued to the firmware; do not fall + * through to the generic TX path below, which would + * send the opcode a second time */ + return ret; + } + + if ((*(u8 *)fts->tx == SPINOR_OP_READ || *(u8 *)fts->tx == SPINOR_OP_READ_FAST || + *(u8 *)fts->tx == SPINOR_OP_READ_4B || + *(u8 *)fts->tx == SPINOR_OP_READ_FAST_4B) && + fts->spi_write_flag == 0 && fts->flash_read == 0 && + fts->flash_erase == 0) { + fts->flash_cmd = *(u8 *)fts->tx; + fts->spi_write_flag++; + fts->flash_read = 1; + return 0; + } + + if ((fts->spi_write_flag == 1) && fts->flash_read == 0 && + fts->flash_write == 0 && ((*(u8 *)fts->tx == SPINOR_OP_PP) || + (*(u8 *)fts->tx == SPINOR_OP_PP_4B))) { + fts->flash_cmd = *(u8 *)fts->tx; + fts->spi_write_flag++; + fts->flash_write = 1; + return 0; + } + + if ((*(u8 *)fts->tx == SPINOR_OP_RDSR) && fts->flash_write == 3) { + fts->read_sr = 1; + fts->flash_write = 0; + return 0; + } + + if ((*(u8 *)fts->tx == SPINOR_OP_RDSR) && fts->flash_erase == 2) { + fts->read_sr = 1; + return 0; + } + } + + if (fts->read_sr) { + *(u8 *)(fts->rx) = 0; + fts->read_sr = 0; + return 0; + } + + if (fts->tx) { + if (fts->flash_erase == 1) { + ret = spi_phytium_flash_erase(fts, spi_get_chipselect(spi, 0), + transfer->bits_per_word, + spi->mode, chip->tmode, 3, SPINOR_OP_BE_4K); + if (ret) { + dev_err(&master->dev, "flash erase failed\n"); + return ret; + } + fts->spi_write_flag = 0; + fts->flash_erase++; + } else if (fts->flash_read) { + ret = spi_phytium_flash_erase(fts, spi_get_chipselect(spi, 0), + transfer->bits_per_word, + spi->mode, chip->tmode, 1, fts->flash_cmd); + if (ret) { + dev_err(&master->dev, "transfer read-command failed\n"); + return ret; + } + fts->spi_write_flag = 0; + fts->flash_read = 0; + } else if (fts->flash_write == 1) { + fts->flash_write++; + ret = spi_phytium_flash_write(fts, fts->flash_cmd); + if (ret) { + dev_err(&master->dev, "flash write failed\n"); + return ret; + } + } else if (fts->flash_erase == 2 && (*(u8 *)fts->tx == SPINOR_OP_WRDI)) { + ret = spi_phytium_write(fts, spi_get_chipselect(spi, 0), transfer->bits_per_word, + spi->mode, chip->tmode, 3, fts->spi_write_flag); + if (ret) { + dev_err(&master->dev, "transfer disable-command failed\n"); + return ret; + } + fts->flash_erase = 0; + } else { + fts->flags = 1; + if (fts->spi_write_flag == 0 && *(u8 *)(fts->tx) != MCP251x_READ + && *(u8 *)(fts->tx) != MCP251x_READ_RXB0 + && *(u8 *)(fts->tx) != MCP251x_READ_RXB1) + fts->flags = 3; + + ret = spi_phytium_write(fts, spi_get_chipselect(spi, 0), transfer->bits_per_word, + spi->mode, chip->tmode, fts->flags, fts->spi_write_flag); + if (ret) { + dev_err(&master->dev, "write command failed\n"); + return ret; + } + if (fts->flash_write == 2) + fts->flash_write++; + fts->spi_write_flag = 0; + } + } + + if (fts->rx) { + ret = spi_phytium_read(fts, spi_get_chipselect(spi, 0), transfer->bits_per_word, + spi->mode, chip->tmode, 2); + if (ret) { + dev_err(&master->dev, "read data failed\n"); + return ret; + } + } + + return ret; +} + +static void spi_phyt_handle_err(struct spi_controller *master, + struct spi_message *msg) +{ + struct phytium_spi *fts = spi_controller_get_devdata(master); + + spi_phyt_reset_chip(fts); +} + +static int spi_phyt_setup(struct spi_device *spi) +{ + struct phytium_spi_chip *chip_info = NULL; + struct chip_data *chip; + struct spi_controller *master = spi->controller; + struct phytium_spi *fts = spi_controller_get_devdata(master); + u8 data_width, scph, scpol, tmode; + u16 mode; + u16 clk_div; + int ret; + + ret = spi_phyt_enable_chip(fts, 0); + if (ret) + return ret; + + if (!spi->max_speed_hz) { + dev_err(&spi->dev, "max_speed_hz is zero\n"); + return -EINVAL; + } + + clk_div = (fts->max_freq / spi->max_speed_hz + 1) & 0xfffe; + ret = spi_phyt_set_clk(fts, clk_div); + if (ret) + return ret; + fts->clk_div = clk_div; + + chip = spi_get_ctldata(spi); + if (!chip) { + chip = kzalloc(sizeof(struct chip_data), GFP_KERNEL); + if (!chip) + return -ENOMEM; + spi_set_ctldata(spi, chip); + } + + chip_info = spi->controller_data; + + if (chip_info) { + if (chip_info->cs_control) + chip->cs_control = chip_info->cs_control; + + chip->poll_mode = chip_info->poll_mode; + chip->type = chip_info->type; + } + + chip->tmode = 0; + + data_width = spi->bits_per_word; + ret = spi_phytium_set_cmd8(fts, PHYTSPI_MSG_CMD_SET_DATA_WIDTH, data_width); + if (ret) + return ret; + + scph = spi->mode & (0x1); + scpol = spi->mode >> 1; + mode = (scph << 8) | scpol; + ret = spi_phytium_set_cmd16(fts, PHYTSPI_MSG_CMD_SET_MODE, mode); + if (ret) + return ret; + + tmode = chip->tmode; + ret = spi_phytium_set_cmd8(fts, PHYTSPI_MSG_CMD_SET_TMOD, tmode); + if (ret) + return ret; + + ret = spi_phyt_enable_chip(fts, 1); + if (ret) + return ret; + + return 0; +} + +static void spi_phyt_cleanup(struct spi_device *spi) +{ + struct chip_data *chip = spi_get_ctldata(spi); + + kfree(chip); + spi_set_ctldata(spi, NULL); +} + +static void spi_phyt_disable_debug(struct phytium_spi *fts) +{ + u32 reg; + + reg = phytium_read_regfile(fts, SPI_REGFILE_DEBUG); + reg &= ~SPI_REGFILE_DEBUG_VAL; + + phytium_write_regfile(fts, SPI_REGFILE_DEBUG, reg); +} + +static void spi_phyt_disable_alive(struct phytium_spi *fts) +{ + u32 reg; + + reg = phytium_read_regfile(fts, SPI_REGFILE_DEBUG); + reg &= ~SPI_REGFILE_ALIVE_VAL; + + phytium_write_regfile(fts, SPI_REGFILE_DEBUG, reg); +} + +static void spi_watchdog(struct phytium_spi *fts) +{ + u32 reg; + + reg = phytium_read_regfile(fts, SPI_REGFILE_DEBUG); + phytium_write_regfile(fts, SPI_REGFILE_DEBUG, + reg | SPI_REGFILE_HEARTBIT_VAL); +} + +static void spi_phyt_timer_handle(struct timer_list *t) +{ + struct phytium_spi *fts = timer_container_of(fts, t, timer); + + if (fts->alive_enabled && fts->watchdog) { + if (fts->runtimes < 20) + fts->watchdog(fts); + + fts->runtimes++; + + /* rearm only while alive monitoring is enabled */ + mod_timer(&fts->timer, jiffies + msecs_to_jiffies(10)); + } +} + +static void spi_handle_debug_err(struct phytium_spi *fts) +{ + struct device *dev = &fts->master->dev; + u32 reg, len, i; + + reg = phytium_read_regfile(fts, SPI_REGFILE_DEBUG); + + if (reg & SPI_REGFILE_HAVE_LOG) { + len = strnlen(fts->log, fts->log_size); + dev_info(dev, "log len :%d,addr: 0x%llx,size:%d\n", + len, (u64)fts->log, fts->log_size); + if (len > SPI_LOG_LINE_MAX_LEN) { + for (i = 0; i + SPI_LOG_LINE_MAX_LEN < len; i += SPI_LOG_LINE_MAX_LEN) + dev_info(dev, "(log)%.*s\n", SPI_LOG_LINE_MAX_LEN, &fts->log[i]); + } else { + dev_info(dev, "(log)%.*s\n", SPI_LOG_LINE_MAX_LEN, &fts->log[0]); + } + + for (i = 0; i < fts->log_size; i++) + fts->log[i] = 0; + } + + reg &= ~SPI_REGFILE_HAVE_LOG; + phytium_write_regfile(fts, SPI_REGFILE_DEBUG, reg); +} + +static int spi_phyt_hw_init(struct device *dev, struct phytium_spi *fts) +{ + u32 reg, i, reg_ddr_high; + int ret; + + ret = spi_phytium_default(fts); + if (ret) { + dev_err(dev, "firmware is not responsive: %d\n", ret); + return ret; + } + + reg = phytium_read_regfile(fts, SPI_REGFILE_DEBUG); + + if (fts->regfile_version & SPI_REGFILE_VERSION_DDR) { + fts->ddr_paddr = ((reg & SPI_REGFILE_ADDR_MASK) >> 8); + reg_ddr_high = phytium_read_regfile(fts, SPI_REGFILE_DDR_HIGH_REG); + fts->ddr_paddr |= ((u64)reg_ddr_high << 20); + } else { + fts->ddr_paddr = ((reg & SPI_REGFILE_ADDR_MASK) >> 8) << SPI_DDR_ADDR_HIGH; + } + + fts->log_size = ((reg & SPI_REGFILE_SIZE_MASK) >> 4) * SPI_DEBUG_LOG_SIZE; + + /* + * Map the firmware log buffer only once: hw_init also runs from + * the resume path and a devm_ioremap() there would leak a mapping + * and a devres entry on every suspend/resume cycle. + */ + if (!fts->log) { + fts->log = devm_ioremap(dev, fts->ddr_paddr, fts->log_size); + if (!fts->log) { + dev_err(dev, "log_addr is err\n"); + return -ENOMEM; + } + + for (i = 0; i < fts->log_size; i++) + fts->log[i] = 0; + } + + return 0; +} + +int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts) +{ + struct spi_controller *master; + int ret; + + WARN_ON(fts == NULL); + + master = devm_spi_alloc_host(dev, 0); + if (!master) + return -ENOMEM; + + fts->master = master; + snprintf(fts->name, sizeof(fts->name), "phytium_spi%d", fts->bus_num); + + init_completion(&fts->cmd_completion); + ret = devm_request_irq(dev, fts->irq, spi_phyt_irq, IRQF_SHARED, fts->name, master); + if (ret < 0) { + dev_err(dev, "can not get IRQ\n"); + return ret; + } + + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP; + master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16); + master->bus_num = fts->bus_num; + master->num_chipselect = fts->num_cs; + master->use_gpio_descriptors = true; + master->setup = spi_phyt_setup; + master->cleanup = spi_phyt_cleanup; + master->set_cs = spi_phyt_set_cs; + master->transfer_one = spi_phyt_transfer_one; + master->handle_err = spi_phyt_handle_err; + master->max_speed_hz = fts->max_freq; + master->dev.of_node = dev->of_node; + master->dev.fwnode = dev->fwnode; + master->flags = SPI_CONTROLLER_GPIO_SS; + + fts->half_duplex = false; + if (!(phytium_read_regfile(fts, SPI_REGFILE_SOFTWARE2) + & SPI_REGFILE_FULL_DUPLEX)) { + dev_warn(dev, "SPI-V2 only support half duplex\n"); + fts->half_duplex = true; + master->flags |= SPI_CONTROLLER_HALF_DUPLEX; + } + + spi_controller_set_devdata(master, fts); + + spi_phyt_disable_debug(fts); + spi_phyt_disable_alive(fts); + fts->runtimes = 0; + fts->debug_enabled = false; + fts->alive_enabled = false; + + fts->watchdog = spi_watchdog; + fts->handle_debug_err = spi_handle_debug_err; + + timer_setup(&fts->timer, spi_phyt_timer_handle, 0); + + ret = spi_phyt_hw_init(dev, fts); + if (ret) { + dev_err(dev, "hardware init failed: %d\n", ret); + goto err_exit; + } + + /* + * Register explicitly rather than devm: the devres unregister + * action would only run after the platform .remove callback has + * already shut the chip down, leaving child devices and the + * transfer queue registered against disabled hardware. With an + * explicit registration, spi_phyt_remove_host() unregisters the + * controller first and only then stops the hardware. + */ + ret = spi_register_controller(master); + if (ret) { + dev_err(&master->dev, "problem registering spi master\n"); + goto err_exit; + } + + return 0; + +err_exit: + timer_delete_sync(&fts->timer); + spi_phyt_enable_chip(fts, 0); + return ret; +} +EXPORT_SYMBOL_GPL(spi_phyt_add_host); + +void spi_phyt_remove_host(struct phytium_spi *fts) +{ + /* unregister children and the transfer queue before the + * hardware is turned off */ + spi_unregister_controller(fts->master); + + timer_delete_sync(&fts->timer); + spi_phyt_shutdown_chip(fts); +} +EXPORT_SYMBOL_GPL(spi_phyt_remove_host); + +int spi_phyt_suspend_host(struct phytium_spi *fts) +{ + int ret; + + ret = spi_controller_suspend(fts->master); + if (ret) + return ret; + + /* stop the watchdog timer before shutting down the chip */ + timer_delete_sync(&fts->timer); + ret = spi_phyt_shutdown_chip(fts); + if (ret) { + dev_err(&fts->master->dev, "firmware shutdown failed: %d\n", ret); + /* + * Let system suspend abort: the controller may still be + * enabled or keep its active clock divider. The PM core + * resumes the device during rollback, which restarts the + * transfer queue via spi_phyt_resume_host(). + */ + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(spi_phyt_suspend_host); + +int spi_phyt_resume_host(struct phytium_spi *fts) +{ + int ret; + + ret = spi_phyt_hw_init(&fts->master->dev, fts); + if (ret) { + dev_err(&fts->master->dev, "hardware init failed on resume: %d\n", ret); + return ret; + } + + ret = spi_phyt_enable_chip(fts, 0); + if (ret) + return ret; + ret = spi_phyt_set_clk(fts, fts->clk_div); + if (ret) + return ret; + ret = spi_phyt_enable_chip(fts, 1); + if (ret) + return ret; + + ret = spi_controller_resume(fts->master); + if (ret) { + dev_err(&fts->master->dev, "fail to start queue (%d)\n", ret); + return ret; + } + + /* restart the watchdog timer after a successful resume, + * but only while alive monitoring is enabled */ + if (fts->alive_enabled) + mod_timer(&fts->timer, jiffies + msecs_to_jiffies(10)); + return 0; +} +EXPORT_SYMBOL_GPL(spi_phyt_resume_host); + +MODULE_AUTHOR("Peng Min "); +MODULE_DESCRIPTION("Driver for Phytium SPI controller core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-phytium.c b/drivers/spi/spi-phytium.c new file mode 100644 index 0000000000000..d0e25ac23d9c5 --- /dev/null +++ b/drivers/spi/spi-phytium.c @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Phytium SPI core controller driver. + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd.. + * + * Derived from drivers/spi/spi-dw.c + * Copyright (c) 2009, Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "spi-phytium.h" + +struct phytium_spi_chip { + u8 poll_mode; + u8 type; + void (*cs_control)(u32 command); +}; + +struct chip_data { + u8 cs; + u8 tmode; + u8 type; + + u8 poll_mode; + + u16 clk_div; + u32 speed_hz; + void (*cs_control)(u32 command); +}; + +static void phytium_spi_set_cs(struct spi_device *spi, bool enable) +{ + struct phytium_spi *fts = spi_controller_get_devdata(spi->controller); + struct chip_data *chip = spi_get_ctldata(spi); + u32 origin; + + if (chip && chip->cs_control) + chip->cs_control(!enable); + + if (!enable) { + phytium_writel(fts, SER, BIT(spi_get_chipselect(spi, 0))); + if (fts->global_cs) { + origin = phytium_readl(fts, GCSR); + phytium_writel(fts, GCSR, origin | (1 << spi_get_chipselect(spi, 0))); + } + } else { + if (fts->global_cs) { + origin = phytium_readl(fts, GCSR); + phytium_writel(fts, GCSR, origin & ~(1 << spi_get_chipselect(spi, 0))); + } + } +} + +static inline u32 tx_max(struct phytium_spi *fts) +{ + u32 tx_left, tx_room, rxtx_gap; + + tx_left = (fts->tx_end - fts->tx) / fts->n_bytes; + tx_room = fts->fifo_len - phytium_readl(fts, TXFLR); + + rxtx_gap = ((fts->rx_end - fts->rx) - (fts->tx_end - fts->tx)) + / fts->n_bytes; + + return min3(tx_left, tx_room, (u32) (fts->fifo_len - rxtx_gap)); +} + +static inline u32 rx_max(struct phytium_spi *fts) +{ + u32 rx_left = (fts->rx_end - fts->rx) / fts->n_bytes; + + return min_t(u32, rx_left, phytium_readl(fts, RXFLR)); +} + +static void phytium_writer(struct phytium_spi *fts) +{ + u32 max = tx_max(fts); + u16 txw = 0; + + while (max--) { + if (fts->tx_end - fts->len) { + if (fts->n_bytes == 1) + txw = *(u8 *)(fts->tx); + else + txw = *(u16 *)(fts->tx); + } + phytium_write_io_reg(fts, DR, txw); + fts->tx += fts->n_bytes; + } +} + +static void phytium_reader(struct phytium_spi *fts) +{ + u32 max = rx_max(fts); + u16 rxw; + + while (max--) { + rxw = phytium_read_io_reg(fts, DR); + if (fts->rx_end - fts->len) { + if (fts->n_bytes == 1) + *(u8 *)(fts->rx) = rxw; + else + *(u16 *)(fts->rx) = rxw; + } + fts->rx += fts->n_bytes; + } +} +int phytium_spi_check_status(struct phytium_spi *fts, bool raw) +{ + u32 irq_status; + int ret = 0; + + if (raw) + irq_status = phytium_readl(fts, RISR); + else + irq_status = phytium_readl(fts, ISR); + + if (irq_status & INT_RXOI) { + dev_err(&fts->master->dev, "RX FIFO overflow detected\n"); + ret = -EIO; + } + + if (irq_status & INT_RXUI) { + dev_err(&fts->master->dev, "RX FIFO underflow detected\n"); + ret = -EIO; + } + + if (irq_status & INT_TXOI) { + dev_err(&fts->master->dev, "TX FIFO overflow detected\n"); + ret = -EIO; + } + + /* Generically handle the erroneous situation */ + if (ret) { + spi_reset_chip(fts); + if (fts->master->cur_msg) + fts->master->cur_msg->status = ret; + } + return ret; +} +EXPORT_SYMBOL_GPL(phytium_spi_check_status); + +static void int_error_stop(struct phytium_spi *fts, const char *msg) +{ + spi_reset_chip(fts); + + dev_err(&fts->master->dev, "%s\n", msg); + fts->master->cur_msg->status = -EIO; + spi_finalize_current_transfer(fts->master); +} + +static irqreturn_t interrupt_transfer(struct phytium_spi *fts) +{ + u16 irq_status = phytium_readl(fts, ISR); + + if (irq_status & (INT_TXOI | INT_RXOI | INT_RXUI)) { + phytium_readl(fts, ICR); + int_error_stop(fts, "irq transfer: fifo overrun/underrun"); + return IRQ_HANDLED; + } + + phytium_reader(fts); + if (fts->rx_end == fts->rx) { + spi_mask_intr(fts, INT_TXEI); + spi_finalize_current_transfer(fts->master); + return IRQ_HANDLED; + } + if (irq_status & INT_TXEI) { + spi_mask_intr(fts, INT_TXEI); + phytium_writer(fts); + spi_umask_intr(fts, INT_TXEI); + } + + return IRQ_HANDLED; +} + +static irqreturn_t phytium_spi_irq(int irq, void *dev_id) +{ + struct spi_controller *master = dev_id; + struct phytium_spi *fts = spi_controller_get_devdata(master); + u16 irq_status = phytium_readl(fts, ISR) & 0x3f; + + if (!irq_status) + return IRQ_NONE; + + if (!master->cur_msg) { + spi_mask_intr(fts, INT_TXEI); + return IRQ_HANDLED; + } + + if (fts->transfer_handler) + return fts->transfer_handler(fts); + else + return IRQ_HANDLED; +} + +static int poll_transfer(struct phytium_spi *fts) +{ + /* generous cap so a hung controller cannot spin forever */ + unsigned long deadline = jiffies + msecs_to_jiffies(100 + fts->len); + + do { + phytium_writer(fts); + phytium_reader(fts); + cpu_relax(); + } while (fts->rx_end > fts->rx && !time_after(jiffies, deadline)); + + if (fts->rx_end > fts->rx) { + dev_err(&fts->master->dev, "transfer timed out\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int phytium_spi_transfer_one(struct spi_controller *master, + struct spi_device *spi, struct spi_transfer *transfer) +{ + struct phytium_spi *fts = spi_controller_get_devdata(master); + struct chip_data *chip = spi_get_ctldata(spi); + u8 imask = 0; + u16 txlevel = 0; + u16 clk_div; + u32 cr0; + int ret = 0; + + /* + * The SPI core substitutes a zero speed with spi->max_speed_hz, + * which can still be zero when the controller clock was never + * configured. Refuse instead of dividing by zero below. + */ + if (!transfer->speed_hz) { + dev_err(&master->dev, "transfer speed is zero\n"); + return -EINVAL; + } + + fts->dma_mapped = 0; + fts->tx = (void *)transfer->tx_buf; + fts->tx_end = fts->tx + transfer->len; + fts->rx = transfer->rx_buf; + fts->rx_end = fts->rx + transfer->len; + fts->len = transfer->len; + + spi_enable_chip(fts, 0); + + if (transfer->speed_hz != fts->current_freq) { + if (transfer->speed_hz != chip->speed_hz) { + clk_div = (fts->max_freq / transfer->speed_hz + 1) & + 0xfffe; + + chip->speed_hz = transfer->speed_hz; + chip->clk_div = clk_div; + } + fts->current_freq = transfer->speed_hz; + spi_set_clk(fts, chip->clk_div); + } + + if (transfer->bits_per_word == 8) + fts->n_bytes = 1; + else if (transfer->bits_per_word == 16) + fts->n_bytes = 2; + else + return -EINVAL; + + cr0 = (transfer->bits_per_word - 1) + | (chip->type << FRF_OFFSET) + | (spi->mode << MODE_OFFSET) + | (chip->tmode << TMOD_OFFSET); + + if (chip->cs_control) { + if (fts->rx && fts->tx) + chip->tmode = TMOD_TR; + else if (fts->rx) + chip->tmode = TMOD_RO; + else + chip->tmode = TMOD_TO; + + cr0 &= ~TMOD_MASK; + cr0 |= (chip->tmode << TMOD_OFFSET); + } + + phytium_writel(fts, CTRLR0, cr0); + + /* check if current transfer is a DMA transcation */ + if (master->can_dma && master->can_dma(master, spi, transfer)) + fts->dma_mapped = (transfer->tx_sg_mapped || transfer->rx_sg_mapped); + + spi_mask_intr(fts, 0xff); + + /* DMA setup */ + if (fts->dma_mapped) { + ret = fts->dma_ops->dma_setup(fts, transfer); + if (ret) + return ret; + } + /* interrupt transfer mode setup */ + if (!chip->poll_mode && !fts->dma_mapped) { + txlevel = min_t(u16, fts->fifo_len / 2, fts->len / fts->n_bytes); + phytium_writel(fts, TXFLTR, txlevel); + + imask |= INT_TXEI | INT_TXOI | + INT_RXUI | INT_RXOI; + spi_umask_intr(fts, imask); + + fts->transfer_handler = interrupt_transfer; + } + + spi_enable_chip(fts, 1); + + if (fts->dma_mapped) + return fts->dma_ops->dma_transfer(fts, transfer); + + if (chip->poll_mode) + return poll_transfer(fts); + + return 1; +} + +static void phytium_spi_handle_err(struct spi_controller *master, + struct spi_message *msg) +{ + struct phytium_spi *fts = spi_controller_get_devdata(master); + + if (fts->dma_mapped) + fts->dma_ops->dma_stop(fts); + + spi_reset_chip(fts); +} + +static int phytium_spi_setup(struct spi_device *spi) +{ + struct phytium_spi_chip *chip_info = NULL; + struct chip_data *chip; + struct spi_controller *master = spi->controller; + struct phytium_spi *fts = spi_controller_get_devdata(master); + u32 cr0; + + spi_enable_chip(fts, 0); + + chip = spi_get_ctldata(spi); + if (!chip) { + chip = kzalloc(sizeof(struct chip_data), GFP_KERNEL); + if (!chip) + return -ENOMEM; + spi_set_ctldata(spi, chip); + } + + chip_info = spi->controller_data; + + if (chip_info) { + if (chip_info->cs_control) + chip->cs_control = chip_info->cs_control; + + chip->poll_mode = chip_info->poll_mode; + chip->type = chip_info->type; + } + + chip->tmode = 0; + + cr0 = (spi->bits_per_word - 1) | (chip->type << FRF_OFFSET) | + (spi->mode << MODE_OFFSET) | (chip->tmode << TMOD_OFFSET); + + phytium_writel(fts, CTRLR0, cr0); + + spi_enable_chip(fts, 1); + + return 0; +} + +static void phytium_spi_cleanup(struct spi_device *spi) +{ + struct chip_data *chip = spi_get_ctldata(spi); + + kfree(chip); + spi_set_ctldata(spi, NULL); +} + +static void spi_hw_init(struct device *dev, struct phytium_spi *fts) +{ + spi_reset_chip(fts); + + if (!fts->fifo_len) { + u32 fifo; + + for (fifo = 1; fifo < 256; fifo++) { + phytium_writel(fts, TXFLTR, fifo); + if (fifo != phytium_readl(fts, TXFLTR)) + break; + } + phytium_writel(fts, TXFLTR, 0); + + fts->fifo_len = (fifo == 1) ? 0 : fifo; + dev_dbg(dev, "Detected FIFO size: %u bytes\n", fts->fifo_len); + } +} + +int phytium_spi_add_host(struct device *dev, struct phytium_spi *fts) +{ + struct spi_controller *master; + int ret; + + WARN_ON(fts == NULL); + + master = spi_alloc_host(dev, 0); + if (!master) + return -ENOMEM; + + fts->master = master; + fts->dma_addr = (dma_addr_t)(fts->paddr + DR); + snprintf(fts->name, sizeof(fts->name), "phytium_spi%d", fts->bus_num); + + master->use_gpio_descriptors = true; + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP; + master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16); + master->bus_num = fts->bus_num; + master->num_chipselect = fts->num_cs; + master->setup = phytium_spi_setup; + master->cleanup = phytium_spi_cleanup; + master->set_cs = phytium_spi_set_cs; + master->transfer_one = phytium_spi_transfer_one; + master->handle_err = phytium_spi_handle_err; + master->max_speed_hz = fts->max_freq; + master->dev.of_node = dev->of_node; + master->dev.fwnode = dev->fwnode; + master->flags = SPI_CONTROLLER_GPIO_SS; + + spi_hw_init(dev, fts); + + if (fts->dma_ops && fts->dma_ops->dma_init) { + ret = fts->dma_ops->dma_init(dev, fts); + if (ret) { + dev_warn(dev, "DMA init failed\n"); + } else { + master->can_dma = fts->dma_ops->can_dma; + master->flags |= SPI_CONTROLLER_MUST_TX; + } + } + + spi_controller_set_devdata(master, fts); + + ret = request_irq(fts->irq, phytium_spi_irq, IRQF_SHARED, + fts->name, master); + if (ret < 0) { + dev_err(dev, "can not get IRQ\n"); + if (fts->dma_ops && fts->dma_ops->dma_exit) + fts->dma_ops->dma_exit(fts); + spi_enable_chip(fts, 0); + goto err_free_master; + } + + ret = spi_register_controller(master); + if (ret) { + dev_err(&master->dev, "problem registering spi master\n"); + goto err_exit; + } + + return 0; + +err_exit: + if (fts->dma_ops && fts->dma_ops->dma_exit) + fts->dma_ops->dma_exit(fts); + spi_enable_chip(fts, 0); + free_irq(fts->irq, master); +err_free_master: + spi_controller_put(master); + return ret; +} +EXPORT_SYMBOL_GPL(phytium_spi_add_host); + +void phytium_spi_remove_host(struct phytium_spi *fts) +{ + spi_unregister_controller(fts->master); + + if (fts->dma_ops && fts->dma_ops->dma_exit) + fts->dma_ops->dma_exit(fts); + spi_shutdown_chip(fts); + + free_irq(fts->irq, fts->master); + + /* spi_unregister_controller() does not drop the initial reference + * taken by spi_alloc_host(); release it here to avoid leaking the + * controller allocation on every unbind. + */ + spi_controller_put(fts->master); +} +EXPORT_SYMBOL_GPL(phytium_spi_remove_host); + +int phytium_spi_suspend_host(struct phytium_spi *fts) +{ + int ret; + + ret = spi_controller_suspend(fts->master); + if (ret) + return ret; + + spi_shutdown_chip(fts); + return 0; +} +EXPORT_SYMBOL_GPL(phytium_spi_suspend_host); + +int phytium_spi_resume_host(struct phytium_spi *fts) +{ + int ret; + + spi_hw_init(&fts->master->dev, fts); + ret = spi_controller_resume(fts->master); + if (ret) + dev_err(&fts->master->dev, "fail to start queue (%d)\n", ret); + return ret; +} +EXPORT_SYMBOL_GPL(phytium_spi_resume_host); + +MODULE_AUTHOR("Zhu Mingshuai "); +MODULE_AUTHOR("Chen Baozi "); +MODULE_DESCRIPTION("Driver for Phytium SPI controller core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-phytium.h b/drivers/spi/spi-phytium.h new file mode 100644 index 0000000000000..74269ec9f36c9 --- /dev/null +++ b/drivers/spi/spi-phytium.h @@ -0,0 +1,370 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Phytium SPI controller driver. + * + * Copyright (c) 2019-2023, Phytium Technology Co., Ltd. + */ +#ifndef PHYTIUM_SPI_HEADER_H +#define PHYTIUM_SPI_HEADER_H + +#include +#include +#include + +#define CTRLR0 0x00 +#define SSIENR 0x08 +#define SER 0x10 +#define BAUDR 0x14 +#define TXFLTR 0x18 +#define TXFLR 0x20 +#define RXFLR 0x24 +#define SR 0x28 +#define IMR 0x2c +#define ISR 0x30 +#define RISR 0x34 +#define ICR 0x48 +#define DMACR 0x4C +#define DMATDLR 0x50 +#define DMARDLR 0x54 +#define DR 0x60 +#define GCSR 0x100 + +#define FRF_OFFSET 4 +#define MODE_OFFSET 6 +#define TMOD_OFFSET 8 + +#define TMOD_MASK (0x3 << TMOD_OFFSET) +#define TMOD_TR 0x0 +#define TMOD_TO 0x1 +#define TMOD_RO 0x2 + +#define INT_TXEI (1 << 0) +#define INT_TXOI (1 << 1) +#define INT_RXUI (1 << 2) +#define INT_RXOI (1 << 3) +/* Bit fields in SR, 7 bits */ +#define SR_MASK 0x7f /* cover 7 bits */ +#define SR_BUSY (1 << 0) +#define SR_TF_NOT_FULL (1 << 1) +#define SR_TF_EMPT (1 << 2) +#define SR_RF_NOT_EMPT (1 << 3) +#define SR_RF_FULL (1 << 4) +#define SR_TX_ERR (1 << 5) +#define SR_DCOL (1 << 6) +/* Bit fields in DMACR */ +#define SPI_DMA_RDMAE (1 << 0) +#define SPI_DMA_TDMAE (1 << 1) +#define SPI_WAIT_RETRIES 5 + +#define SPI_REGFILE_AP2RV_INTR_STATE (0x24) +#define SPI_REGFILE_RV2AP_INTR_STATE (0x2c) +#define SPI_REGFILE_SIZE (0x48) +#define SPI_REGFILE_DDR_HIGH_REG (0x4c) + +#define SPI_REGFILE_SOFTWARE2 (0x54) +#define SPI_REGFILE_FULL_DUPLEX BIT(9) + +#define SPI_REGFILE_DEBUG (0x58) +#define SPI_REGFILE_DEBUG_VAL BIT(0) +#define SPI_REGFILE_ALIVE_VAL BIT(1) +#define SPI_REGFILE_HEARTBIT_VAL BIT(2) +#define SPI_REGFILE_HAVE_LOG BIT(3) +#define SPI_REGFILE_SIZE_MASK GENMASK(7, 4) +#define SPI_REGFILE_ADDR_MASK GENMASK(27, 8) + +#define SPI_REGFILE_DDR_HIGH_REG (0x4c) +#define SPI_REGFILE_VERSION_REG (0x700) +#define SPI_REGFILE_VERSION_SUB GENMASK(7, 0) +#define SPI_REGFILE_VERSION GENMASK(15, 8) +#define SPI_REGFILE_VERSION_DMA BIT(16) +#define SPI_REGFILE_VERSION_DDR BIT(17) + +#define SPI_REGFILE_RV2AP_INT_CLEAN (0x74) + +#define SPI_DDR_ADDR_HIGH 12 +#define SPI_DEBUG_LOG_SIZE 4096 + +#define SPI_LOG_LINE_MAX_LEN 400 + +#define SPI_MODULE_OPT_CMD 0x20 + +#define SPI_TRANS_DATA_SIZE 1024 +#define FLASH_PAGE_SIZE 256 + +#define SPI_MSG_COMPLETE_OK 1 +#define SPI_MSG_COMPLETE_KO 0 + +#define SPI_RESULT_IGNORE_LVL (0) +#define SPI_RESULT_WRITE_ISR_LVL (1) +#define SPI_RESULT_READ_ISR_LVL (2) + +#define SPI_SHMEM_TX_MSG_MAX_CNT 1 + +#define SPI_MASTER_TIMEOUT 8 + +#define SPI_DEFAULT_CLK 50000000 + +enum phytspi_msg_cmd_id { + PHYTSPI_MSG_CMD_DEFAULT = 0, + PHYTSPI_MSG_CMD_SET, + PHYTSPI_MSG_CMD_GET, + PHYTSPI_MSG_CMD_DATA, + PHYTSPI_MSG_CMD_REPORT, +}; + +enum phytspi_set_subid { + PHYTSPI_MSG_CMD_SET_MODULE_EN = 0, + PHYTSPI_MSG_CMD_SET_DATA_WIDTH, + PHYTSPI_MSG_CMD_SET_MODE, + PHYTSPI_MSG_CMD_SET_TMOD, + PHYTSPI_MSG_CMD_SET_BAUDR, + PHYTSPI_MSG_CMD_SET_INT_TI, + PHYTSPI_MSG_CMD_SET_NDF, + PHYTSPI_MSG_CMD_SET_CS, + PHYTSPI_MSG_CMD_SET_DMA_RESET, +}; + +enum phytspi_data_subid { + PHYTSPI_MSG_CMD_DATA_TX = 0, + PHYTSPI_MSG_CMD_DATA_RX, + PHYTSPI_MSG_CMD_DATA_FLASH_TX, + PHYTSPI_MSG_CMD_FLASH_ERASE, + PHYTSPI_MSG_CMD_DATA_DMA_TX, + PHYTSPI_MSG_CMD_DATA_DMA_RX, + PHYTSPI_MSG_CMD_DATA_FLASH_DMA_TX, + PHYTSPI_MSG_CMD_DATA_XFER, + PHYTSPI_MSG_CMD_DATA_DMA_XFER, +}; + +struct msg { + u8 reserved; + u8 seq; + u8 cmd_id; + u8 cmd_subid; + u16 len; + u8 status1; + u8 status0; + u8 data[56]; +}; + +struct spi_trans_msg_info { + u32 msg_total_num; + u32 shmem_data_addr; + int result; +}; + +struct phytium_spi; +struct phytium_spi_dma_ops { + int (*dma_init)(struct device *dev, struct phytium_spi *fts); + void (*dma_exit)(struct phytium_spi *fts); + int (*dma_setup)(struct phytium_spi *fts, struct spi_transfer *xfer); + bool (*can_dma)(struct spi_controller *master, struct spi_device *spi, + struct spi_transfer *xfer); + int (*dma_transfer)(struct phytium_spi *fts, struct spi_transfer *xfer); + void (*dma_stop)(struct phytium_spi *fts); +}; + +struct phytium_spi { + struct spi_controller *master; + char name[16]; + + void __iomem *regs; + void __iomem *regfile; + void __iomem *tx_shmem_addr; + void *rx_shmem_addr; + + struct msg msg_buf; + void __iomem *msg; + u32 mem_tx_physic; + u32 mem_rx_physic; + u64 mem_tx; + u64 mem_rx; + + u16 clk_div; + int module; + + bool global_cs; + bool dma_en; + bool half_duplex; + unsigned long paddr; + int irq; + u32 fifo_len; + u32 max_freq; + + u32 reg_io_width; + int bus_num; + u16 num_cs; + int *cs; + + size_t len; + void *tx; + void *tx_end; + void *rx; + void *rx_end; + u8 n_bytes; + int dma_mapped; + struct clk *clk; + irqreturn_t (*transfer_handler)(struct phytium_spi *fts); + + int cmd_err; + u32 cur_tx_tail; + struct completion cmd_completion; + + u8 flags; + u8 spi_write_flag; + u8 flash_erase; + u8 flash_read; + u8 flash_write; + u8 read_sr; + u8 flash_cmd; + + bool debug_enabled; + bool alive_enabled; + struct timer_list timer; + u32 runtimes; // for debug + u64 ddr_paddr; + char *log; + u32 log_size; + void (*watchdog)(struct phytium_spi *fts); + void (*handle_debug_err)(struct phytium_spi *fts); + u32 regfile_version; + + /* DMA info */ + u32 current_freq; /* frequency in hz */ + struct dma_chan *txchan; + u32 txburst; + struct dma_chan *rxchan; + u32 rxburst; + u32 dma_sg_burst; + unsigned long dma_chan_busy; + dma_addr_t dma_addr; /* phy address of the Data register */ + const struct phytium_spi_dma_ops *dma_ops; + struct completion dma_completion; + + bool dma_get_ddrdata; +}; + +static inline u32 phytium_readl(struct phytium_spi *fts, u32 offset) +{ + return __raw_readl(fts->regs + offset); +} +static inline u16 phytium_readw(struct phytium_spi *fts, u32 offset) +{ + return __raw_readw(fts->regs + offset); +} +static inline void phytium_writel(struct phytium_spi *fts, u32 offset, u32 val) +{ + __raw_writel(val, fts->regs + offset); +} +static inline void phytium_writew(struct phytium_spi *fts, u32 offset, u16 val) +{ + __raw_writew(val, fts->regs + offset); +} +static inline u32 phytium_read_io_reg(struct phytium_spi *fts, u32 offset) +{ + switch (fts->reg_io_width) { + case 2: + return phytium_readw(fts, offset); + case 4: + default: + return phytium_readl(fts, offset); + } +} +static inline void phytium_write_io_reg(struct phytium_spi *fts, + u32 offset, u32 val) +{ + switch (fts->reg_io_width) { + case 2: + phytium_writew(fts, offset, val); + break; + case 4: + default: + phytium_writel(fts, offset, val); + break; + } +} +static inline void spi_enable_chip(struct phytium_spi *fts, int enable) +{ + phytium_writel(fts, SSIENR, (enable ? 1 : 0)); +} +static inline void spi_set_clk(struct phytium_spi *fts, u16 div) +{ + phytium_writel(fts, BAUDR, div); +} +static inline void spi_mask_intr(struct phytium_spi *fts, u32 mask) +{ + u32 new_mask; + + new_mask = phytium_readl(fts, IMR) & ~mask; + phytium_writel(fts, IMR, new_mask); +} +static inline void spi_umask_intr(struct phytium_spi *fts, u32 mask) +{ + u32 new_mask; + + new_mask = phytium_readl(fts, IMR) | mask; + phytium_writel(fts, IMR, new_mask); +} +static inline void spi_global_cs(struct phytium_spi *fts) +{ + u32 global_cs_en, mask, setmask; + + mask = GENMASK(fts->num_cs-1, 0) << fts->num_cs; + setmask = ~GENMASK(fts->num_cs-1, 0); + global_cs_en = (phytium_readl(fts, GCSR) | mask) & setmask; + phytium_writel(fts, GCSR, global_cs_en); +} +static inline void spi_reset_chip(struct phytium_spi *fts) +{ + spi_enable_chip(fts, 0); + if (fts->global_cs) + spi_global_cs(fts); + spi_mask_intr(fts, 0xff); + spi_enable_chip(fts, 1); +} +static inline void spi_shutdown_chip(struct phytium_spi *fts) +{ + spi_enable_chip(fts, 0); + spi_set_clk(fts, 0); + fts->current_freq = 0; +} + +static inline u32 phytium_read_regfile(struct phytium_spi *fts, u32 reg_off) +{ + return readl_relaxed(fts->regfile + reg_off); +} + +static inline void phytium_write_regfile(struct phytium_spi *fts, u32 reg_off, u32 val) +{ + writel_relaxed(val, fts->regfile + reg_off); +} + +extern int spi_phytium_default(struct phytium_spi *fts); +extern int spi_phytium_set_cmd8(struct phytium_spi *fts, u16 sub_cmd, u8 data); +extern int spi_phytium_set_cmd16(struct phytium_spi *fts, u16 sub_cmd, u16 data); +extern int spi_phytium_set_cmd32(struct phytium_spi *fts, u16 sub_cmd, u32 data); +extern void spi_phytium_data_cmd_write(struct phytium_spi *fts, u16 sub_cmd); +extern void spi_phytium_data_cmd_read(struct phytium_spi *fts, u16 sub_cmd); +extern void spi_phytium_write_pre(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, u8 tmode, + u8 flags, u8 spi_write_flag); +extern int spi_phytium_flash_erase(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, + u8 tmode, u8 flags, u8 cmd); +extern int spi_phytium_flash_write(struct phytium_spi *fts, u8 cmd); +extern int spi_phytium_write(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, + u8 tmode, u8 flags, u8 spi_write_flag); +extern int spi_phytium_read(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, + u8 tmode, u8 flags); +extern int spi_phytium_xfer(struct phytium_spi *fts, u8 cs, u8 dfs, u8 mode, + u8 tmode, u8 flags); +extern int spi_phyt_add_host(struct device *dev, struct phytium_spi *fts); +extern void spi_phyt_remove_host(struct phytium_spi *fts); +extern int spi_phyt_suspend_host(struct phytium_spi *fts); +extern int spi_phyt_resume_host(struct phytium_spi *fts); + +extern int phytium_spi_add_host(struct device *dev, struct phytium_spi *fts); +extern void phytium_spi_remove_host(struct phytium_spi *fts); +extern int phytium_spi_suspend_host(struct phytium_spi *fts); +extern int phytium_spi_resume_host(struct phytium_spi *fts); +extern void phytium_spi_dmaops_set(struct phytium_spi *fts); +extern int phytium_spi_check_status(struct phytium_spi *fts, bool raw); +#endif /* PHYTIUM_SPI_HEADER_H */ diff --git a/include/linux/mtd/partitions.h b/include/linux/mtd/partitions.h index b74a539ec5819..d504ac36c46f5 100644 --- a/include/linux/mtd/partitions.h +++ b/include/linux/mtd/partitions.h @@ -51,6 +51,7 @@ struct mtd_partition { uint32_t mask_flags; /* master MTD flags to mask out for this partition */ uint32_t add_flags; /* flags to add to the partition */ struct device_node *of_node; + struct fwnode_handle *fwnode; }; #define MTDPART_OFS_RETAIN (-3) @@ -61,6 +62,8 @@ struct mtd_partition { struct mtd_info; struct device_node; +struct acpi_device; +struct fwnode_handle; /** * struct mtd_part_parser_data - used to pass data to MTD partition parsers. @@ -80,6 +83,7 @@ struct mtd_part_parser { struct module *owner; const char *name; const struct of_device_id *of_match_table; + const struct acpi_device_id *acpi_match_table; int (*parse_fn)(struct mtd_info *, const struct mtd_partition **, struct mtd_part_parser_data *); void (*cleanup)(const struct mtd_partition *pparts, int nr_parts); diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 8ee94377155b2..861588eb1002d 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -353,6 +353,7 @@ struct hdac_bus { bool not_use_interrupts:1; /* prohibiting the RIRB IRQ */ bool access_sdnctl_in_dword:1; /* accessing the sdnctl register by dword */ bool use_pio_for_commands:1; /* Use PIO instead of CORB for commands */ + bool cmd_resend:1; /* command resend */ bool hygon_dword_access:1; int poll_count; diff --git a/sound/hda/codecs/hdmi/hdmi.c b/sound/hda/codecs/hdmi/hdmi.c index a4a2f2f0540c9..a65348a3856bd 100644 --- a/sound/hda/codecs/hdmi/hdmi.c +++ b/sound/hda/codecs/hdmi/hdmi.c @@ -2346,6 +2346,8 @@ static const struct hda_device_id snd_hda_id_generichdmi[] = { HDA_CODEC_ID_MODEL(0x1d179f8e, "KX-7000 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x1d179f8f, "KX-7000 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x1d179f90, "KX-7000 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x67663d80, "Arise 80 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x67663d81, "Arise 81 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x67663d82, "Arise 82 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x67663d83, "Arise 83 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x67663d84, "Arise 84 HDMI/DP", MODEL_GF), diff --git a/sound/hda/common/controller.c b/sound/hda/common/controller.c index afec5c5546ec7..0d923b2353c4d 100644 --- a/sound/hda/common/controller.c +++ b/sound/hda/common/controller.c @@ -17,6 +17,9 @@ #include #include +#include "../controllers/intel.h" +#include + #ifdef CONFIG_X86 /* for art-tsc conversion */ #include @@ -84,6 +87,123 @@ static u64 azx_adjust_codec_delay(struct snd_pcm_substream *substream, return (nsec > codec_nsecs) ? nsec - codec_nsecs : 0; } +static int gf_setup_bdle(struct snd_pcm_substream *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct azx *chip = apcm->chip; + struct azx_dev *azx_dev = get_azx_dev(substream); + __le32 *bdl; + unsigned int i, stream_idx; + struct gf_private *gf_chip = NULL; + + // setup BDL and BDLE + if ((chip->pci != NULL) && (chip->pci->vendor == 0x6766) && (chip->pci->device == 0x3d40)) { + gf_chip = container_of(chip, struct gf_private, hda.chip); + if (gf_chip == NULL) { + return -1; + } + stream_idx = apcm->codec->addr - 1; + if ((stream_idx <= 1) && (gf_chip->diu_fb_bdl_vaddr[stream_idx])) { + /* + * Rewrite the BDL address entries in a normal-memory copy + * first, then flush it to the WC mapping with a single + * memcpy_toio(). Reading back from a WC ioremap mapping is + * not a portable MMIO pattern and can return stale data on + * some architectures. Allocate the copy before writing to + * the mapping, so an allocation failure leaves the mapping + * untouched instead of a stale uncorrected BDL. + */ + { + __le32 *tmp; + u32 copy_bytes = min_t(u32, azx_dev->core.bdl.bytes, BDL_SIZE); + + tmp = kmemdup(azx_dev->core.bdl.area, copy_bytes, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + bdl = tmp; + for (i = 0; i < azx_dev->core.frags; i++) { + if (i > 0) { + bdl[i*4] = cpu_to_le32((u32)(le32_to_cpu(bdl[(i-1)*4]) + le32_to_cpu(bdl[(i-1)*4+2]))); + bdl[i*4+1] = cpu_to_le32(upper_32_bits(gf_chip->diu_fb_stream_ofs[stream_idx])); + } + else { + bdl[i*4] = cpu_to_le32((u32)gf_chip->diu_fb_stream_ofs[stream_idx]); + bdl[i*4+1] = cpu_to_le32(upper_32_bits(gf_chip->diu_fb_stream_ofs[stream_idx])); + } + } + memcpy_toio(gf_chip->diu_fb_bdl_vaddr[stream_idx], tmp, copy_bytes); + kfree(tmp); + } + snd_hdac_stream_writel((azx_stream(azx_dev)), SD_BDLPL, (u32)gf_chip->diu_fb_bdl_ofs[stream_idx]); + snd_hdac_stream_writel((azx_stream(azx_dev)), SD_BDLPU, upper_32_bits(gf_chip->diu_fb_bdl_ofs[stream_idx])); + } + } + return 0; +} + +static int gf_pre_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct azx *chip = apcm->chip; + unsigned int stream_idx; + struct gf_private *gf_chip = NULL; + + if ((substream->runtime) && (chip->pci != NULL) && (chip->pci->vendor == 0x6766) && (chip->pci->device == 0x3d40)) { + gf_chip = container_of(chip, struct gf_private, hda.chip); + if (gf_chip == NULL) { + return -1; + } + stream_idx = apcm->codec->addr - 1; + if ((cmd == SNDRV_PCM_TRIGGER_START) && + (stream_idx <= 1) && (gf_chip->diu_fb_stream_vaddr[stream_idx]) && + (substream->runtime->dma_area) && + (substream->runtime->dma_bytes <= GF_HDA_FB_STREAM_SIZE)) { + memcpy_toio(gf_chip->diu_fb_stream_vaddr[stream_idx], substream->runtime->dma_area, substream->runtime->dma_bytes); + gf_chip->diu_fb_stream_pos[stream_idx] = 0; + } + } + return 0; +} + +static int gf_update_stream(struct snd_pcm_substream *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct azx *chip = apcm->chip; + unsigned int stream_idx, appl_pos; + struct gf_private *gf_chip = NULL; + + if ((substream->runtime) && (chip->pci != NULL) && (chip->pci->vendor == 0x6766) && (chip->pci->device == 0x3d40)) { + gf_chip = container_of(chip, struct gf_private, hda.chip); + if (gf_chip == NULL) { + return -1; + } + stream_idx = apcm->codec->addr - 1; + if ((stream_idx <= 1) && (gf_chip->diu_fb_stream_vaddr[stream_idx]) && (substream->runtime->dma_area) && + (substream->runtime->dma_bytes <= GF_HDA_FB_STREAM_SIZE) && snd_pcm_running(substream)) { + appl_pos = frames_to_bytes(substream->runtime, substream->runtime->control->appl_ptr % substream->runtime->buffer_size); + + /* + * Sync only the newly committed range. This runs + * from the PCM pointer callback, which can be + * called in interrupt context, so never copy the + * whole buffer here; a full refresh is done by + * gf_pre_trigger() when the stream starts. + */ + if (appl_pos > gf_chip->diu_fb_stream_pos[stream_idx]) { + memcpy_toio(gf_chip->diu_fb_stream_vaddr[stream_idx] + gf_chip->diu_fb_stream_pos[stream_idx], substream->runtime->dma_area + gf_chip->diu_fb_stream_pos[stream_idx], (appl_pos - gf_chip->diu_fb_stream_pos[stream_idx])); + } + else if (appl_pos < gf_chip->diu_fb_stream_pos[stream_idx]) { + if(substream->runtime->dma_bytes > gf_chip->diu_fb_stream_pos[stream_idx]) { + memcpy_toio(gf_chip->diu_fb_stream_vaddr[stream_idx] + gf_chip->diu_fb_stream_pos[stream_idx], substream->runtime->dma_area + gf_chip->diu_fb_stream_pos[stream_idx], (substream->runtime->dma_bytes - gf_chip->diu_fb_stream_pos[stream_idx])); + } + memcpy_toio(gf_chip->diu_fb_stream_vaddr[stream_idx], substream->runtime->dma_area, appl_pos); + } + gf_chip->diu_fb_stream_pos[stream_idx] = appl_pos; + } + } + return 0; +} + /* * PCM ops */ @@ -186,6 +306,10 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream) snd_hdac_stream_setup(azx_stream(azx_dev), false); + err = gf_setup_bdle(substream); + if (err < 0) + return err; + stream_tag = azx_dev->core.stream_tag; /* CA-IBG chips need the playback stream starting from 1 */ if ((chip->driver_caps & AZX_DCAPS_CTX_WORKAROUND) && @@ -239,6 +363,8 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) return -EINVAL; } + gf_pre_trigger(substream, cmd); + snd_pcm_group_for_each_entry(s, substream) { if (s->pcm->card != substream->pcm->card) continue; @@ -323,6 +449,9 @@ static snd_pcm_uframes_t azx_pcm_pointer(struct snd_pcm_substream *substream) struct azx_pcm *apcm = snd_pcm_substream_chip(substream); struct azx *chip = apcm->chip; struct azx_dev *azx_dev = get_azx_dev(substream); + + gf_update_stream(substream); + return bytes_to_frames(substream->runtime, azx_get_position(chip, azx_dev)); } diff --git a/sound/hda/common/hda_controller.h b/sound/hda/common/hda_controller.h index 38227f82e7040..12f93b43c066b 100644 --- a/sound/hda/common/hda_controller.h +++ b/sound/hda/common/hda_controller.h @@ -17,6 +17,9 @@ #define AZX_MAX_CODECS HDA_MAX_CODECS #define AZX_DEFAULT_CODECS 4 +#define GF_HDA_PATCH_VERSION 1 +#define GF_HDA_FB_STREAM_SIZE 7*1024*1024 + /* driver quirks (capabilities) */ /* bits 0-7 are used for indicating driver type */ #define AZX_DCAPS_NO_TCSEL (1 << 8) /* No Intel TCSEL bit */ @@ -63,6 +66,13 @@ struct azx_dev { * when link position is not greater than FIFO size */ bool insufficient; + + /* + * Delayed IRQ handling flag. + * Upstream moved this into the snd-hda-intel private stream + * (e36a88b33cbe3); keep it here for the Phytium HDA controller. + */ + bool irq_pending; }; #define azx_stream(dev) (&(dev)->core) diff --git a/sound/hda/controllers/Kconfig b/sound/hda/controllers/Kconfig index 5d6a77e68588e..e689fa829fbde 100644 --- a/sound/hda/controllers/Kconfig +++ b/sound/hda/controllers/Kconfig @@ -14,6 +14,22 @@ config SND_HDA_INTEL To compile this driver as a module, choose M here: the module will be called snd-hda-intel. +config SND_HDA_PHYTIUM + tristate "PHYTIUM HD Audio" + depends on SOUND + select SND_HDA + select SND_HDA_ALIGNED_MMIO + help + Say Y here to support the HDA controller present in PHYTIUM + SoCs + + This options enables support for the HD Audio controller + present in some PHYTIUM SoCs, used to communicate audio + to the "High Definition Audio" codec. + + To compile this driver as a module, choose M here: the module + will be called snd-hda-phytium. + config SND_HDA_TEGRA tristate "NVIDIA Tegra HD Audio" depends on ARCH_TEGRA diff --git a/sound/hda/controllers/Makefile b/sound/hda/controllers/Makefile index 8967b6771d904..b6491155e8e53 100644 --- a/sound/hda/controllers/Makefile +++ b/sound/hda/controllers/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 snd-hda-intel-y := intel.o +snd-hda-phytium-y := phytium.o snd-hda-tegra-y := tegra.o snd-hda-cix-ipbloq-y := cix-ipbloq.o snd-hda-acpi-y := acpi.o @@ -10,6 +11,7 @@ subdir-ccflags-y += -I$(src)/../common CFLAGS_intel.o := -I$(src) obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-intel.o +obj-$(CONFIG_SND_HDA_PHYTIUM) += snd-hda-phytium.o obj-$(CONFIG_SND_HDA_TEGRA) += snd-hda-tegra.o obj-$(CONFIG_SND_HDA_CIX_IPBLOQ) += snd-hda-cix-ipbloq.o obj-$(CONFIG_SND_HDA_ACPI) += snd-hda-acpi.o diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c index 2095cf84bd5b6..6aa2c8ae1008c 100644 --- a/sound/hda/controllers/intel.c +++ b/sound/hda/controllers/intel.c @@ -383,6 +383,116 @@ static void update_pci_byte(struct pci_dev *pci, unsigned int reg, pci_write_config_byte(pci, reg, data); } + +static int gf_init_pci(struct azx *chip) +{ + struct pci_dev *diu_pci = NULL; + unsigned long fb_size; + phys_addr_t diu_fb_base; + phys_addr_t diu_fb_stream[2]; + phys_addr_t diu_fb_bdl[2]; + struct gf_private *gf_chip = NULL; + + dev_info(chip->card->dev, "gf_hda patch version %03d \n", GF_HDA_PATCH_VERSION); + + if ((chip->pci != NULL) && (chip->pci->vendor == 0x6766) && (chip->pci->device == 0x3d40)) { + gf_chip = container_of(chip, struct gf_private, hda.chip); + if (gf_chip == NULL) { + return -1; + } + gf_chip->diu_fb_stream_vaddr[0] = NULL; + gf_chip->diu_fb_stream_vaddr[1] = NULL; + gf_chip->diu_fb_bdl_vaddr[0] = NULL; + gf_chip->diu_fb_bdl_vaddr[1] = NULL; + + if (chip->pci->bus != NULL) + diu_pci = pci_get_slot(chip->pci->bus, PCI_DEVFN(PCI_SLOT(chip->pci->devfn), 0)); + + if (!diu_pci) { + dev_info(chip->card->dev, "gf_hda can't get display device\n"); + } + else { + diu_fb_base = pci_resource_start(diu_pci, 1); + fb_size = pci_resource_len(diu_pci, 1); + + /* + * The audio stream and BDL windows are carved out of + * the last 20 MB of the display frame buffer; make + * sure the BAR is at least that large. + */ + if (fb_size < (4 + 16) * 1024 * 1024) { + dev_err(chip->card->dev, + "gf_hda display frame buffer too small\n"); + pci_dev_put(diu_pci); + return -ENOMEM; + } + + diu_fb_stream[0] = diu_fb_base + fb_size - (4+16)*1024*1024; + gf_chip->diu_fb_stream_ofs[0] = diu_fb_stream[0] - diu_fb_base; // stream offset = fb_size -4M-16M + gf_chip->diu_fb_stream_vaddr[0] = ioremap_wc(diu_fb_stream[0], GF_HDA_FB_STREAM_SIZE); // size = 7M + + diu_fb_stream[1] = diu_fb_stream[0] + GF_HDA_FB_STREAM_SIZE; + gf_chip->diu_fb_stream_ofs[1] = diu_fb_stream[1] - diu_fb_base; // stream offset = fb_size -4M-16M+7M + gf_chip->diu_fb_stream_vaddr[1] = ioremap_wc(diu_fb_stream[1], GF_HDA_FB_STREAM_SIZE); // size = 7M + + diu_fb_bdl[0] = diu_fb_stream[1] + GF_HDA_FB_STREAM_SIZE; + gf_chip->diu_fb_bdl_ofs[0] = diu_fb_bdl[0] - diu_fb_base; // stream offset = fb_size -4M-16M+7M*2 + gf_chip->diu_fb_bdl_vaddr[0] = ioremap_wc(diu_fb_bdl[0], BDL_SIZE); // size = 4K + + diu_fb_bdl[1] = diu_fb_bdl[0] + BDL_SIZE; + gf_chip->diu_fb_bdl_ofs[1] = diu_fb_bdl[1] - diu_fb_base; // stream offset = fb_size -4M-16M+7M*2+4K + gf_chip->diu_fb_bdl_vaddr[1] = ioremap_wc(diu_fb_bdl[1], BDL_SIZE); // size = 4K + + if (!gf_chip->diu_fb_stream_vaddr[0] || !gf_chip->diu_fb_stream_vaddr[1] || + !gf_chip->diu_fb_bdl_vaddr[0] || !gf_chip->diu_fb_bdl_vaddr[1]) { + dev_err(chip->card->dev, "gf_hda can't map display frame buffer\n"); + if (gf_chip->diu_fb_stream_vaddr[0]) + iounmap(gf_chip->diu_fb_stream_vaddr[0]); + if (gf_chip->diu_fb_stream_vaddr[1]) + iounmap(gf_chip->diu_fb_stream_vaddr[1]); + if (gf_chip->diu_fb_bdl_vaddr[0]) + iounmap(gf_chip->diu_fb_bdl_vaddr[0]); + if (gf_chip->diu_fb_bdl_vaddr[1]) + iounmap(gf_chip->diu_fb_bdl_vaddr[1]); + gf_chip->diu_fb_stream_vaddr[0] = NULL; + gf_chip->diu_fb_stream_vaddr[1] = NULL; + gf_chip->diu_fb_bdl_vaddr[0] = NULL; + gf_chip->diu_fb_bdl_vaddr[1] = NULL; + pci_dev_put(diu_pci); + return -ENOMEM; + } + + dev_info(chip->card->dev, "gf_hda diu fb base=0x%llx, size=%dM.\n", diu_fb_base, (unsigned int)(fb_size >> 20)); + pci_dev_put(diu_pci); + } + } + return 0; +} + +static void gf_free_pci(struct azx *chip) +{ + struct gf_private *gf_chip = NULL; + + if ((chip->pci != NULL) && (chip->pci->vendor == 0x6766) && (chip->pci->device == 0x3d40)) { + gf_chip = container_of(chip, struct gf_private, hda.chip); + if (gf_chip == NULL) { + return; + } + + if(gf_chip->diu_fb_stream_vaddr[0]) + iounmap(gf_chip->diu_fb_stream_vaddr[0]); + + if(gf_chip->diu_fb_stream_vaddr[1]) + iounmap(gf_chip->diu_fb_stream_vaddr[1]); + + if(gf_chip->diu_fb_bdl_vaddr[0]) + iounmap(gf_chip->diu_fb_bdl_vaddr[0]); + + if(gf_chip->diu_fb_bdl_vaddr[1]) + iounmap(gf_chip->diu_fb_bdl_vaddr[1]); + } +} + static void azx_init_pci(struct azx *chip) { int snoop_type = azx_get_snoop_type(chip); @@ -1394,6 +1504,10 @@ static void azx_free(struct azx *chip) if (bus->irq >= 0) free_irq(bus->irq, (void*)chip); + if (chip->driver_type == AZX_DRIVER_GFHDMI) { + gf_free_pci(chip); + } + azx_free_stream_pages(chip); azx_free_streams(chip); snd_hdac_bus_exit(bus); @@ -1795,7 +1909,12 @@ static int azx_create(struct snd_card *card, struct pci_dev *pci, if (err < 0) return err; - hda = devm_kzalloc(&pci->dev, sizeof(*hda), GFP_KERNEL); + if ((pci != NULL) && (pci->vendor == 0x6766) && (pci->device == 0x3d40)) { + hda = devm_kzalloc(&pci->dev, sizeof(struct gf_private), GFP_KERNEL); + } + else { + hda = devm_kzalloc(&pci->dev, sizeof(*hda), GFP_KERNEL); + } if (!hda) return -ENOMEM; @@ -1915,8 +2034,14 @@ static int azx_first_init(struct azx *chip) * Fix response write request not synced to memory when handle * hdac interrupt on Glenfly Gpus */ - if (chip->driver_type == AZX_DRIVER_GFHDMI) + if (chip->driver_type == AZX_DRIVER_GFHDMI) { bus->polling_mode = 1; + if (gf_init_pci(chip)) + /* the controller still works, only the DIU frame + * buffer path stays disabled */ + dev_err(chip->card->dev, + "gf_hda frame buffer setup failed\n"); + } if (chip->driver_type == AZX_DRIVER_LOONGSON) { bus->polling_mode = 1; diff --git a/sound/hda/controllers/intel.h b/sound/hda/controllers/intel.h index 4efb3b0fc2d81..e6f1f443bea11 100644 --- a/sound/hda/controllers/intel.h +++ b/sound/hda/controllers/intel.h @@ -44,4 +44,15 @@ struct hda_intel_stream { #define azx_dev_to_istream(azx_dev) \ container_of(azx_dev, struct hda_intel_stream, azx_dev) +struct gf_private { + struct hda_intel hda; + + phys_addr_t diu_fb_stream_ofs[2]; + void __iomem *diu_fb_stream_vaddr[2]; + unsigned int diu_fb_stream_pos[2]; + + phys_addr_t diu_fb_bdl_ofs[2]; + void __iomem *diu_fb_bdl_vaddr[2]; +}; + #endif diff --git a/sound/hda/controllers/phytium.c b/sound/hda/controllers/phytium.c new file mode 100644 index 0000000000000..65064914a608d --- /dev/null +++ b/sound/hda/controllers/phytium.c @@ -0,0 +1,1088 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Implementation of primary ALSA driver code for Phytium HD Audio. + * + * Copyright(c) 2018-2022, Phytium Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "phytium.h" + +#include "intel_trace.h" + +/* position fix mode */ +enum { + POS_FIX_AUTO, + POS_FIX_LPIB, + POS_FIX_POSBUF, + POS_FIX_VIACOMBO, + POS_FIX_COMBO, +}; + +/* Define IN stream 0 FIFO size offset in VIA controller */ +#define VIA_IN_STREAM0_FIFO_SIZE_OFFSET 0x90 + +/* FT have 4 playback and 4 capture */ +#define FT4C_NUM_CAPTURE 4 +#define FT4C_NUM_PLAYBACK 4 + +#define DWORD_BYTE_WIDTH 4 +#define BYTE_BIT_WIDTH 8 + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +static char *model[SNDRV_CARDS]; +static int position_fix[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1}; +static int bdl_pos_adj[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1}; +static int probe_mask[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1}; +static int probe_only[SNDRV_CARDS]; +static int jackpoll_ms[SNDRV_CARDS]; +static int single_cmd = -1; +static int enable_msi = -1; +#ifdef CONFIG_SND_HDA_INPUT_BEEP +static bool beep_mode[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = + CONFIG_SND_HDA_INPUT_BEEP_MODE}; +#endif + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Intel HD audio interface."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Intel HD audio interface."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Intel HD audio interface."); +module_param_array(model, charp, NULL, 0444); +MODULE_PARM_DESC(model, "Use the given board model."); +module_param_array(position_fix, int, NULL, 0444); +MODULE_PARM_DESC(position_fix, "DMA pointer read method. (-1 = system default, 0 = auto, 1 = LPIB, 2 = POSBUF, 3 = VIACOMBO, 4 = COMBO)."); +module_param_array(bdl_pos_adj, int, NULL, 0644); +MODULE_PARM_DESC(bdl_pos_adj, "BDL position adjustment offset."); +module_param_array(probe_mask, int, NULL, 0444); +MODULE_PARM_DESC(probe_mask, "Bitmask to probe codecs (default = -1)."); +module_param_array(probe_only, int, NULL, 0444); +MODULE_PARM_DESC(probe_only, "Only probing and no codec initialization."); +module_param_array(jackpoll_ms, int, NULL, 0444); +MODULE_PARM_DESC(jackpoll_ms, "Ms between polling for jack events (default = 0, using unsol events only)"); +module_param(single_cmd, bint, 0444); +MODULE_PARM_DESC(single_cmd, "Use single command to communicate with codecs (for debugging only)."); +module_param(enable_msi, bint, 0444); +MODULE_PARM_DESC(enable_msi, "Enable Message Signaled Interrupt (MSI)"); +#ifdef CONFIG_SND_HDA_INPUT_BEEP +module_param_array(beep_mode, bool, NULL, 0444); +MODULE_PARM_DESC(beep_mode, "Select HDA Beep registration mode (0=off, 1=on) (default=1)."); +#endif + +#ifdef CONFIG_PM +static int power_save = CONFIG_SND_HDA_POWER_SAVE_DEFAULT; +module_param(power_save, bint, 0644); +MODULE_PARM_DESC(power_save, + "Automatic power-saving timeout (in seconds, 0 = disable)."); +#else +#define power_save 0 +#endif + +static int align_buffer_size = -1; +module_param(align_buffer_size, bint, 0644); +MODULE_PARM_DESC(align_buffer_size, + "Force buffer and period sizes to be multiple of 128 bytes."); + +/* driver types */ +enum { + AZX_DRIVER_ICH, + AZX_DRIVER_PCH, + AZX_DRIVER_SCH, + AZX_DRIVER_HDMI, + AZX_DRIVER_ATI, + AZX_DRIVER_ATIHDMI, + AZX_DRIVER_ATIHDMI_NS, + AZX_DRIVER_VIA, + AZX_DRIVER_SIS, + AZX_DRIVER_ULI, + AZX_DRIVER_NVIDIA, + AZX_DRIVER_TERA, + AZX_DRIVER_CTX, + AZX_DRIVER_CTHDA, + AZX_DRIVER_CMEDIA, + AZX_DRIVER_GENERIC, + AZX_DRIVER_FT, + AZX_NUM_DRIVERS, /* keep this as last entry */ +}; + +/* NOP for other archs */ +static inline void mark_pages_wc(struct azx *chip, struct snd_dma_buffer *buf, + bool on) +{ +} + +static inline void mark_runtime_wc(struct azx *chip, struct azx_dev *azx_dev, + struct snd_pcm_substream *substream, bool on) +{ +} + +static int azx_acquire_irq(struct azx *chip, int do_disconnect); + +/* calculate runtime delay from LPIB */ +static int azx_get_delay_from_lpib(struct azx *chip, struct azx_dev *azx_dev, + unsigned int pos) +{ + struct snd_pcm_substream *substream = azx_dev->core.substream; + int stream = substream->stream; + unsigned int lpib_pos = azx_get_pos_lpib(chip, azx_dev); + int delay; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + delay = pos - lpib_pos; + else + delay = lpib_pos - pos; + if (delay < 0) { + if (delay >= azx_dev->core.delay_negative_threshold) + delay = 0; + else + delay += azx_dev->core.bufsize; + } + + if (delay >= azx_dev->core.period_bytes) { + dev_info(chip->card->dev, + "Unstable LPIB (%d >= %d); disabling LPIB delay counting\n", + delay, azx_dev->core.period_bytes); + delay = 0; + chip->driver_caps &= ~AZX_DCAPS_COUNT_LPIB_DELAY; + chip->get_delay[stream] = NULL; + } + + return bytes_to_frames(substream->runtime, delay); +} + +static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev); + +/* called from IRQ */ +static int azx_position_check(struct azx *chip, struct azx_dev *azx_dev) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + int ok; + + ok = azx_position_ok(chip, azx_dev); + if (ok == 1) { + azx_dev->irq_pending = 0; + return ok; + } else if (ok == 0) { + /* bogus IRQ, process it later */ + azx_dev->irq_pending = 1; + schedule_work(&hda->irq_pending_work); + } + return 0; +} + +static int azx_ft_link_power(struct azx *chip, bool enable) +{ + return 0; +} + +/* + * Check whether the current DMA position is acceptable for updating + * periods. Returns non-zero if it's OK. + * + * Many HD-audio controllers appear pretty inaccurate about + * the update-IRQ timing. The IRQ is issued before actually the + * data is processed. So, we need to process it afterwords in a + * workqueue. + */ +static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev) +{ + struct snd_pcm_substream *substream = azx_dev->core.substream; + int stream = substream->stream; + u32 wallclk; + unsigned int pos; + + wallclk = (azx_readl(chip, WALLCLK) - azx_dev->core.start_wallclk); + + if (wallclk < (azx_dev->core.period_wallclk * 2) / 3) + return -1; /* bogus (too early) interrupt */ + + if (chip->get_position[stream]) + pos = chip->get_position[stream](chip, azx_dev); + else { /* use the position buffer as default */ + pos = azx_get_pos_posbuf(chip, azx_dev); + if (!pos || pos == (u32)-1) { + dev_info(chip->card->dev, + "Invalid position buffer, using LPIB read method instead.\n"); + chip->get_position[stream] = azx_get_pos_lpib; + if (chip->get_position[0] == azx_get_pos_lpib && + chip->get_position[1] == azx_get_pos_lpib) + azx_bus(chip)->use_posbuf = false; + pos = azx_get_pos_lpib(chip, azx_dev); + chip->get_delay[stream] = NULL; + } else { + chip->get_position[stream] = azx_get_pos_posbuf; + if (chip->driver_caps & AZX_DCAPS_COUNT_LPIB_DELAY) + chip->get_delay[stream] = azx_get_delay_from_lpib; + } + } + + if (pos >= azx_dev->core.bufsize) + pos = 0; + + if (WARN_ONCE(!azx_dev->core.period_bytes, + "hda-ft: zero azx_dev->period_bytes")) + return -1; /* this shouldn't happen! */ + if (wallclk < (azx_dev->core.period_wallclk * 5) / 4 && + pos % azx_dev->core.period_bytes > azx_dev->core.period_bytes / 2) + /* NG - it's below the first next period boundary */ + return chip->bdl_pos_adj ? 0 : -1; + + azx_dev->core.start_wallclk += wallclk; + + return 1; /* OK, it's fine */ +} + +/* The work for pending PCM period updates. */ +static void azx_irq_pending_work(struct work_struct *work) +{ + struct hda_ft *hda = container_of(work, struct hda_ft, irq_pending_work); + struct azx *chip = &hda->chip; + struct hdac_bus *bus = azx_bus(chip); + struct hdac_stream *s; + int pending, ok; + + if (!hda->irq_pending_warned) { + dev_info(chip->card->dev, + "IRQ timing workaround is activated for card #%d. Suggest a bigger bdl_pos_adj.\n", + chip->card->number); + hda->irq_pending_warned = 1; + } + + for (;;) { + pending = 0; + spin_lock_irq(&bus->reg_lock); + list_for_each_entry(s, &bus->stream_list, list) { + struct azx_dev *azx_dev = stream_to_azx_dev(s); + + if (!azx_dev->irq_pending || + !s->substream || !s->running) + continue; + ok = azx_position_ok(chip, azx_dev); + if (ok > 0) { + azx_dev->irq_pending = 0; + spin_unlock(&bus->reg_lock); + snd_pcm_period_elapsed(s->substream); + spin_lock(&bus->reg_lock); + } else if (ok < 0) { + /* + * Too early: give up on this stream for this + * round, as the Intel implementation does. A + * persistently early stream must not keep the + * worker busy-spinning; the flag stays set and + * the next interrupt reschedules the work. + */ + continue; + } else { + pending++; + } + } + spin_unlock_irq(&bus->reg_lock); + if (!pending) + return; + msleep(1); + } +} + +/* clear irq_pending flags and assure no on-going workq */ +static void azx_clear_irq_pending(struct azx *chip) +{ + struct hdac_bus *bus = azx_bus(chip); + struct hdac_stream *s; + + spin_lock_irq(&bus->reg_lock); + list_for_each_entry(s, &bus->stream_list, list) { + struct azx_dev *azx_dev = stream_to_azx_dev(s); + + azx_dev->irq_pending = 0; + } + spin_unlock_irq(&bus->reg_lock); +} + +static int azx_acquire_irq(struct azx *chip, int do_disconnect) +{ + struct hdac_bus *bus = azx_bus(chip); + + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + struct platform_device *pdev = to_platform_device(hda->dev); + int irq_id = platform_get_irq(pdev, 0); + int err; + + if (irq_id < 0) + return irq_id; + + err = request_irq(irq_id, azx_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip); + if (err) { + dev_err(chip->card->dev, + "unable to request IRQ %d, disabling device\n", + irq_id); + if (do_disconnect) + snd_card_disconnect(chip->card); + return err; + } + bus->irq = irq_id; + + return 0; +} + +/* get the current DMA position with correction on VIA chips */ +static unsigned int azx_via_get_position(struct azx *chip, + struct azx_dev *azx_dev) +{ + unsigned int link_pos, mini_pos, bound_pos; + unsigned int mod_link_pos, mod_dma_pos, mod_mini_pos; + unsigned int fifo_size; + + link_pos = snd_hdac_stream_get_pos_lpib(azx_stream(azx_dev)); + if (azx_dev->core.substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Playback, no problem using link position */ + return link_pos; + } + + /* Capture */ + /* For new chipset, + * use mod to get the DMA position just like old chipset + */ + mod_dma_pos = le32_to_cpu(*azx_dev->core.posbuf); + mod_dma_pos %= azx_dev->core.period_bytes; + + /* azx_dev->fifo_size can't get FIFO size of in stream. + * Get from base address + offset. + */ + fifo_size = readw(azx_bus(chip)->remap_addr + + VIA_IN_STREAM0_FIFO_SIZE_OFFSET); + + if (azx_dev->insufficient) { + /* Link position never gather than FIFO size */ + if (link_pos <= fifo_size) + return 0; + + azx_dev->insufficient = 0; + } + + if (link_pos <= fifo_size) + mini_pos = azx_dev->core.bufsize + link_pos - fifo_size; + else + mini_pos = link_pos - fifo_size; + + /* Find nearest previous boudary */ + mod_mini_pos = mini_pos % azx_dev->core.period_bytes; + mod_link_pos = link_pos % azx_dev->core.period_bytes; + if (mod_link_pos >= fifo_size) + bound_pos = link_pos - mod_link_pos; + else if (mod_dma_pos >= mod_mini_pos) + bound_pos = mini_pos - mod_mini_pos; + else { + bound_pos = mini_pos - mod_mini_pos + azx_dev->core.period_bytes; + if (bound_pos >= azx_dev->core.bufsize) + bound_pos = 0; + } + + /* Calculate real DMA position we want */ + return bound_pos + mod_dma_pos; +} + +#ifdef CONFIG_PM +static DEFINE_MUTEX(card_list_lock); +static LIST_HEAD(card_list); + +static void azx_add_card_list(struct azx *chip) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + + mutex_lock(&card_list_lock); + list_add(&hda->list, &card_list); + mutex_unlock(&card_list_lock); +} + +static void azx_del_card_list(struct azx *chip) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + + mutex_lock(&card_list_lock); + list_del_init(&hda->list); + mutex_unlock(&card_list_lock); +} + +#else +#define azx_add_card_list(chip) /* NOP */ +#define azx_del_card_list(chip) /* NOP */ +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM +static bool azx_is_pm_ready(struct snd_card *card) +{ + struct azx *chip; + + if (!card) + return false; + + chip = card->private_data; + return !chip->disabled && chip->running; +} + +static int azx_runtime_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + struct hda_ft *hda; + + if (!azx_is_pm_ready(card)) + return 0; + + chip = card->private_data; + hda = container_of(chip, struct hda_ft, chip); + + azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) | + STATESTS_INT_MASK); + azx_stop_chip(chip); + if (azx_bus(chip)->irq >= 0) + synchronize_irq(azx_bus(chip)->irq); + azx_clear_irq_pending(chip); + cancel_work_sync(&hda->irq_pending_work); + azx_enter_link_reset(chip); + + return 0; +} + +static int azx_runtime_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + struct hda_codec *codec; + int status; + + if (!azx_is_pm_ready(card)) + return 0; + + chip = card->private_data; + + /* STATESTS is cleared by the controller reset below. */ + status = azx_readw(chip, STATESTS); + snd_hdac_bus_exit_link_reset(azx_bus(chip)); + usleep_range(1000, 1200); + azx_init_chip(chip, 0); + azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) & + ~STATESTS_INT_MASK); + + if (status && !chip->bus.shutdown) { + list_for_each_codec(codec, &chip->bus) + if (status & BIT(codec->addr)) + pm_request_resume(hda_codec_dev(codec)); + } + + return 0; +} + +static int azx_runtime_idle(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + + if (!card) + return 0; + + chip = card->private_data; + if (chip->disabled) + return 0; + + if (azx_bus(chip)->codec_powered || !chip->running) + return -EBUSY; + + return 0; +} + +static int azx_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + int err; + + if (!azx_is_pm_ready(card)) + return 0; + + err = pm_runtime_force_suspend(dev); + if (err < 0) + return err; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + return 0; +} + +static int azx_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + int err; + + if (!azx_is_pm_ready(card)) + return 0; + + err = pm_runtime_force_resume(dev); + if (err < 0) + return err; + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static const struct dev_pm_ops azx_pm = { + SYSTEM_SLEEP_PM_OPS(azx_suspend, azx_resume) + RUNTIME_PM_OPS(azx_runtime_suspend, azx_runtime_resume, + azx_runtime_idle) +}; + +#define hda_ft_pm (&azx_pm) +#else +#define hda_ft_pm NULL +#endif /* CONFIG_PM */ + +static int azx_probe_continue(struct azx *chip); +static void azx_probe_failed(struct azx *chip); + +/* + * destructor + */ +static int azx_free(struct azx *chip) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + struct hdac_bus *bus = azx_bus(chip); + struct device *hddev = hda->dev; + + azx_del_card_list(chip); + + complete_all(&hda->probe_wait); + + if (bus->chip_init) { + azx_clear_irq_pending(chip); + cancel_work_sync(&hda->irq_pending_work); + azx_stop_all_streams(chip); + azx_stop_chip(chip); + } + + if (bus->irq >= 0) { + free_irq(bus->irq, (void *)chip); + bus->irq = -1; + } + + /* remap_addr may be NULL when called from an early probe failure */ + if (bus->remap_addr) + devm_iounmap(hddev, bus->remap_addr); + + azx_free_stream_pages(chip); + azx_free_streams(chip); + snd_hdac_bus_exit(bus); + + return 0; +} + +static int azx_dev_disconnect(struct snd_device *device) +{ + struct azx *chip = device->device_data; + + chip->bus.shutdown = 1; + return 0; +} + +static int azx_dev_free(struct snd_device *device) +{ + return azx_free(device->device_data); +} + +static int check_position_fix(struct azx *chip, int fix) +{ + switch (fix) { + case POS_FIX_AUTO: + case POS_FIX_LPIB: + case POS_FIX_POSBUF: + case POS_FIX_VIACOMBO: + case POS_FIX_COMBO: + return fix; + } + + if (chip->driver_caps & AZX_DCAPS_POSFIX_LPIB) { + dev_dbg(chip->card->dev, "Using LPIB position fix\n"); + return POS_FIX_LPIB; + } + return POS_FIX_AUTO; +} + +static void assign_position_fix(struct azx *chip, int fix) +{ + static azx_get_pos_callback_t callbacks[] = { + [POS_FIX_AUTO] = NULL, + [POS_FIX_LPIB] = azx_get_pos_lpib, + [POS_FIX_POSBUF] = azx_get_pos_posbuf, + [POS_FIX_VIACOMBO] = azx_via_get_position, + [POS_FIX_COMBO] = azx_get_pos_lpib, + }; + + chip->get_position[0] = chip->get_position[1] = callbacks[fix]; + + /* combo mode uses LPIB only for playback */ + if (fix == POS_FIX_COMBO) + chip->get_position[1] = NULL; + + if (fix == POS_FIX_POSBUF && + (chip->driver_caps & AZX_DCAPS_COUNT_LPIB_DELAY)) { + chip->get_delay[0] = chip->get_delay[1] = + azx_get_delay_from_lpib; + } + +} + +#define AZX_FORCE_CODEC_MASK 0x100 + +static void check_probe_mask(struct azx *chip, int dev) +{ + chip->codec_probe_mask = probe_mask[dev]; + + /* check forced option */ + if (chip->codec_probe_mask != -1 && + (chip->codec_probe_mask & AZX_FORCE_CODEC_MASK)) { + azx_bus(chip)->codec_mask = chip->codec_probe_mask & 0xff; + dev_info(chip->card->dev, "codec_mask forced to 0x%x\n", + (int)azx_bus(chip)->codec_mask); + } +} + +static void azx_probe_work(struct work_struct *work) +{ + struct hda_ft *hda = container_of(work, struct hda_ft, probe_work); + int err; + + err = pm_runtime_resume_and_get(hda->dev); + if (err < 0) { + azx_probe_failed(&hda->chip); + pm_runtime_disable(hda->dev); + return; + } + + err = azx_probe_continue(&hda->chip); + pm_runtime_put(hda->dev); + if (err < 0) + pm_runtime_disable(hda->dev); +} + +/* + * constructor + */ +static const struct hda_controller_ops axi_hda_ops; + +static int hda_ft_create(struct snd_card *card, struct platform_device *pdev, + int dev, unsigned int driver_caps, + struct azx **rchip) +{ + static struct snd_device_ops ops = { + .dev_disconnect = azx_dev_disconnect, + .dev_free = azx_dev_free, + }; + struct hda_ft *hda; + struct azx *chip; + int err; + + *rchip = NULL; + + hda = devm_kzalloc(&pdev->dev, sizeof(*hda), GFP_KERNEL); + if (!hda) + return -ENOMEM; + hda->dev = &pdev->dev; + chip = &hda->chip; + mutex_init(&chip->open_mutex); + chip->card = card; + chip->ops = &axi_hda_ops; + chip->driver_caps = driver_caps; + chip->driver_type = driver_caps & 0xff; + chip->dev_index = dev; + if (jackpoll_ms[dev] >= 50 && jackpoll_ms[dev] <= 60000) + chip->jackpoll_interval = msecs_to_jiffies(jackpoll_ms[dev]); + INIT_LIST_HEAD(&chip->pcm_list); + INIT_WORK(&hda->irq_pending_work, azx_irq_pending_work); + INIT_LIST_HEAD(&hda->list); + + init_completion(&hda->probe_wait); + assign_position_fix(chip, check_position_fix(chip, position_fix[dev])); + check_probe_mask(chip, dev); + + if (single_cmd < 0) /* allow fallback to single_cmd at errors */ + chip->fallback_to_single_cmd = 0; + else /* explicitly set to single_cmd or not */ + chip->single_cmd = single_cmd; + + if (bdl_pos_adj[dev] < 0) { + switch (chip->driver_type) { + case AZX_DRIVER_FT: + bdl_pos_adj[dev] = 32; + break; + default: + bdl_pos_adj[dev] = 32; + break; + } + } + chip->bdl_pos_adj = bdl_pos_adj[dev]; + + err = azx_bus_init(chip, model[dev]); + if (err < 0) + return err; + + chip->bus.core.aligned_mmio = 1; + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err < 0) { + dev_err(card->dev, "Error creating device [card]!\n"); + azx_free(chip); + return err; + } + + /* continue probing in work context as may trigger request module */ + INIT_WORK(&hda->probe_work, azx_probe_work); + + *rchip = chip; + + return 0; +} + +static int azx_first_init(struct azx *chip) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + struct platform_device *pdev = to_platform_device(hda->dev); + struct device *hddev = hda->dev; + + int dev = chip->dev_index; + bool full_reset; + + struct snd_card *card = chip->card; + struct hdac_bus *bus = azx_bus(chip); + int err; + unsigned short gcap; + unsigned int dma_bits = 64; + + struct resource *res; + const struct acpi_device_id *match; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hda->regs = devm_ioremap_resource(hddev, res); + if (IS_ERR(hda->regs)) + return PTR_ERR(hda->regs); + + bus->addr = res->start; + bus->remap_addr = hda->regs; + if (bus->remap_addr == NULL) { + dev_err(card->dev, "ioremap error\n"); + return -ENXIO; + } + + bus->cmd_resend = 1; + + if (azx_acquire_irq(chip, 0) < 0) + return -EBUSY; + + synchronize_irq(bus->irq); + + gcap = azx_readw(chip, GCAP); + dev_dbg(card->dev, "chipset global capabilities = 0x%x\n", gcap); + + /* disable 64bit DMA address on some devices */ + if (chip->driver_caps & AZX_DCAPS_NO_64BIT) { + dev_dbg(card->dev, "Disabling 64bit DMA\n"); + gcap &= ~AZX_GCAP_64OK; + } + + /* disable buffer size rounding to 128-byte multiples if supported */ + if (align_buffer_size >= 0) + chip->align_buffer_size = !!align_buffer_size; + else { + if (chip->driver_caps & AZX_DCAPS_NO_ALIGN_BUFSIZE) + chip->align_buffer_size = 0; + else + chip->align_buffer_size = 1; + } + + if (has_acpi_companion(hddev)) { + match = acpi_match_device(hddev->driver->acpi_match_table, hddev); + if (!match) { + dev_err(hddev, "Error ACPI match data is missing\n"); + return -ENODEV; + } + set_dma_ops(hddev, NULL); + acpi_dma_configure(hddev, DEV_DMA_NON_COHERENT); + } + + /* allow 64bit DMA address if supported by H/W */ + if (!(gcap & AZX_GCAP_64OK)) + dma_bits = 32; + if (!dma_set_mask(hddev, DMA_BIT_MASK(dma_bits))) { + dma_set_coherent_mask(hddev, DMA_BIT_MASK(dma_bits)); + } else { + dma_set_mask(hddev, DMA_BIT_MASK(32)); + dma_set_coherent_mask(hddev, DMA_BIT_MASK(32)); + } + + /* read number of streams from GCAP register instead of using + * hardcoded value + */ + chip->capture_streams = (gcap >> 8) & 0x0f; + chip->playback_streams = (gcap >> 12) & 0x0f; + if (!chip->playback_streams && !chip->capture_streams) { + /* gcap didn't give any info, switching to old method */ + chip->playback_streams = FT4C_NUM_PLAYBACK; + chip->capture_streams = FT4C_NUM_CAPTURE; + } + chip->capture_index_offset = 0; + chip->playback_index_offset = chip->capture_streams; + chip->num_streams = chip->playback_streams + chip->capture_streams; + + /* initialize streams */ + err = azx_init_streams(chip); + if (err < 0) + return err; + + err = azx_alloc_stream_pages(chip); + if (err < 0) + return err; + + full_reset = (probe_only[dev] & 2) ? false : true; + azx_init_chip(chip, full_reset); + + /* codec detection */ + if (!azx_bus(chip)->codec_mask) { + dev_err(card->dev, "no codecs found!\n"); + return -ENODEV; + } + + strscpy(card->driver, "ft-hda", sizeof(card->driver)); + strscpy(card->shortname, "ft-hda", sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx irq %i", + card->shortname, bus->addr, bus->irq); + + return 0; +} + + +static const struct hda_controller_ops axi_hda_ops = { + .position_check = azx_position_check, + .link_power = azx_ft_link_power, +}; + +static DECLARE_BITMAP(probed_devs, SNDRV_CARDS); + +static int hda_ft_probe(struct platform_device *pdev) +{ + const unsigned int driver_flags = AZX_DRIVER_FT | + AZX_DCAPS_PM_RUNTIME; + struct snd_card *card; + struct hda_ft *hda; + struct azx *chip; + bool schedule_probe; + int err; + int dev; + + dev = find_first_zero_bit(probed_devs, SNDRV_CARDS); + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + set_bit(dev, probed_devs); + return -ENOENT; + } + + err = snd_card_new(&pdev->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err < 0) { + dev_err(&pdev->dev, "Error creating card!\n"); + return err; + } + + err = hda_ft_create(card, pdev, dev, driver_flags, &chip); + if (err < 0) + goto out_free; + card->private_data = chip; + hda = container_of(chip, struct hda_ft, chip); + + dev_set_drvdata(&pdev->dev, card); + pm_runtime_enable(&pdev->dev); + + schedule_probe = !chip->disabled; + set_bit(dev, probed_devs); + + if (schedule_probe) + schedule_work(&hda->probe_work); + + if (chip->disabled) + complete_all(&hda->probe_wait); + return 0; + +out_free: + snd_card_free(card); + return err; +} + +/* number of codec slots for each chipset: 0 = default slots (i.e. 4) */ +static unsigned int azx_max_codecs[AZX_NUM_DRIVERS] = { + [AZX_DRIVER_FT] = 4, +}; + +static int azx_probe_continue(struct azx *chip) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + int dev = chip->dev_index; + int err; + + hda->probe_continued = 1; + + err = azx_first_init(chip); + if (err < 0) + goto out_free; + +#ifdef CONFIG_SND_HDA_INPUT_BEEP + chip->beep_mode = beep_mode[dev]; +#endif + + /* create codec instances */ + err = azx_probe_codecs(chip, azx_max_codecs[chip->driver_type]); + if (err < 0) + goto out_free; + + if ((probe_only[dev] & 1) == 0) { + err = azx_codec_configure(chip); + if (err < 0) + goto out_free; + } + + err = snd_card_register(chip->card); + if (err < 0) + goto out_free; + + chip->running = 1; + azx_add_card_list(chip); + snd_hda_set_power_save(&chip->bus, power_save * 1000); + + return err; + +out_free: + azx_probe_failed(chip); + return err; +} + +static void azx_probe_failed(struct azx *chip) +{ + struct hda_ft *hda = container_of(chip, struct hda_ft, chip); + struct hdac_bus *bus = azx_bus(chip); + + if (bus->irq >= 0) { + free_irq(bus->irq, (void *)chip); + bus->irq = -1; + } + chip->disabled = 1; + /* release the reserved slot so a later rebind can reclaim it */ + clear_bit(chip->dev_index, probed_devs); + /* + * Clear the driver data before freeing the card: the platform + * device stays bound and its remove/shutdown/PM callbacks must + * not dereference the freed card, mirroring snd-hda-intel. + */ + dev_set_drvdata(hda->dev, NULL); + if (chip->card) + snd_card_free(chip->card); +} + +static void hda_ft_remove(struct platform_device *pdev) +{ + struct snd_card *card = dev_get_drvdata(&pdev->dev); + struct azx *chip; + struct hda_ft *hda; + int err; + + if (card) { + /* cancel the pending probing work */ + chip = card->private_data; + hda = container_of(chip, struct hda_ft, chip); + cancel_work_sync(&hda->probe_work); + chip->bus.shutdown = 1; + err = pm_runtime_resume_and_get(&pdev->dev); + pm_runtime_disable(&pdev->dev); + clear_bit(chip->dev_index, probed_devs); + + snd_card_free(card); + if (err >= 0) + pm_runtime_put_noidle(&pdev->dev); + } +} + +static void hda_ft_shutdown(struct platform_device *pdev) +{ + struct snd_card *card = dev_get_drvdata(&pdev->dev); + struct azx *chip; + + if (!card) + return; + chip = card->private_data; + if (chip && chip->running) { + chip->bus.shutdown = 1; + if (pm_runtime_resume_and_get(&pdev->dev) < 0) + return; + azx_stop_chip(chip); + pm_runtime_put_noidle(&pdev->dev); + } +} + +static const struct of_device_id hda_ft_of_match[] = { + { .compatible = "phytium,hda" }, + {}, +}; +MODULE_DEVICE_TABLE(of, hda_ft_of_match); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id hda_ft_acpi_match[] = { + { .id = "PHYT0006" }, + { .id = "FTHD0001" }, + {} +}; +MODULE_DEVICE_TABLE(acpi, hda_ft_acpi_match); +#else +#define hda_ft_acpi_match NULL +#endif + +static struct platform_driver ft_platform_hda = { + .driver = { + .name = "ft-hda", + .pm = hda_ft_pm, + .of_match_table = hda_ft_of_match, + .acpi_match_table = hda_ft_acpi_match, + }, + .probe = hda_ft_probe, + .remove = hda_ft_remove, + .shutdown = hda_ft_shutdown, +}; + +module_platform_driver(ft_platform_hda); + +MODULE_DESCRIPTION("FT HDA bus driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/hda/controllers/phytium.h b/sound/hda/controllers/phytium.h new file mode 100644 index 0000000000000..09ac99f0c29c9 --- /dev/null +++ b/sound/hda/controllers/phytium.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Implementation of primary ALSA driver code base for Phytium HD Audio. + * + * Copyright(c) 2018-2022, Phytium Technology Co., Ltd. + */ +#ifndef __SOUND_HDA_PHYTIUM_H__ +#define __SOUND_HDA_PHYTIUM_H__ + +#include "hda_controller.h" + +struct hda_ft { + struct azx chip; + struct device *dev; + void __iomem *regs; + + /* for pending irqs */ + struct work_struct irq_pending_work; + + /* sync probing */ + struct completion probe_wait; + struct work_struct probe_work; + + /* card list (for power_save trigger) */ + struct list_head list; + + /* extra flags */ + unsigned int irq_pending_warned:1; + unsigned int probe_continued:1; + +}; + +#endif diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c index bfe8170457247..9a2e112aa9093 100644 --- a/sound/hda/core/controller.c +++ b/sound/hda/core/controller.c @@ -209,22 +209,14 @@ static int snd_hdac_bus_get_response_pio(struct hdac_bus *bus, return 0; } -/** - * snd_hdac_bus_send_cmd_corb - send a command verb via CORB - * @bus: HD-audio core bus - * @val: encoded verb value to send - * - * Returns zero for success or a negative error code. - */ -static int snd_hdac_bus_send_cmd_corb(struct hdac_bus *bus, unsigned int val) +/* number of retransmissions for a verb that produced no response */ +#define CORB_SEND_RETRIES 6 + +/* caller holds bus->reg_lock */ +static int corb_write_cmd(struct hdac_bus *bus, unsigned int val) { - unsigned int addr = azx_command_addr(val); unsigned int wp, rp; - guard(spinlock_irq)(&bus->reg_lock); - - bus->last_cmd[azx_command_addr(val)] = val; - /* add command to corb */ wp = snd_hdac_chip_readw(bus, CORBWP); if (wp == 0xffff) { @@ -240,13 +232,65 @@ static int snd_hdac_bus_send_cmd_corb(struct hdac_bus *bus, unsigned int val) return -EAGAIN; } - bus->rirb.cmds[addr]++; bus->corb.buf[wp] = cpu_to_le32(val); snd_hdac_chip_writew(bus, CORBWP, wp); return 0; } +/** + * snd_hdac_bus_send_cmd_corb - send a command verb via CORB + * @bus: HD-audio core bus + * @val: encoded verb value to send + * + * Returns zero for success or a negative error code. + */ +static int snd_hdac_bus_send_cmd_corb(struct hdac_bus *bus, unsigned int val) +{ + unsigned int addr = azx_command_addr(val); + int cmds_before, cmds_after; + int err, i; + + guard(spinlock_irq)(&bus->reg_lock); + + bus->last_cmd[addr] = val; + + err = corb_write_cmd(bus, val); + if (err) + return err; + /* Retries are copies of one logical verb, not new responses. */ + bus->rirb.cmds[addr]++; + + if (!bus->cmd_resend) + return 0; + + /* + * Controllers with cmd_resend occasionally lose a CORB write: the + * verb never reaches the codec and no response ever arrives. + * Retransmit the verb a few times, but keep one response reference + * for the logical command. A lost copy cannot produce a response, + * so counting every copy would make a successful retry time out. + * The wait runs with interrupts enabled; the per-codec counter is + * updated by snd_hdac_bus_update_rirb(). + */ + cmds_before = bus->rirb.cmds[addr]; + for (i = 0; i < CORB_SEND_RETRIES; i++) { + spin_unlock_irq(&bus->reg_lock); + udelay(80); + spin_lock_irq(&bus->reg_lock); + + cmds_after = bus->rirb.cmds[addr]; + if (cmds_after < cmds_before) + break; + + err = corb_write_cmd(bus, val); + if (err) + return err; + } + + return 0; +} + #define AZX_RIRB_EX_UNSOL_EV (1<<4) /** @@ -359,6 +403,18 @@ static int snd_hdac_bus_get_response_rirb(struct hdac_bus *bus, if (!bus->polling_mode) finish_wait(&bus->rirb_wq, &wait); + /* + * With command resend, the timed-out verb may still account for + * several retransmitted copies in rirb.cmds. Copies whose CORB + * write was lost never respond, so the wait would never complete + * and every following verb on this codec would inherit the stale + * references and time out in turn. Reset the counter for this + * codec: a response of a merely slow copy that lands afterwards + * is then dropped as spurious by snd_hdac_bus_update_rirb(). + */ + if (bus->cmd_resend) + bus->rirb.cmds[addr] = 0; + return -EIO; }