diff options
author | Jakub Kicinski <kuba@kernel.org> | 2022-09-19 14:21:53 -0700 |
---|---|---|
committer | Jakub Kicinski <kuba@kernel.org> | 2022-09-19 14:21:54 -0700 |
commit | 2bd178c5ea73ab66ac8496960c4a4d9acdef5c24 (patch) | |
tree | bbce21898e7e87405777e4d41550bd342a791055 | |
parent | 2f8a786f472445d66348859af8795aa6ffd9ff8c (diff) | |
parent | f3e893626abeac3cdd9ba41d3395dc6c1b7d5ad6 (diff) |
Merge tag 'ib-mfd-net-pinctrl-v6.0' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd
Lee Jones says:
====================
Immutable branch between MFD, Net and Pinctrl due for the v6.0 merge window
* tag 'ib-mfd-net-pinctrl-v6.0' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd:
mfd: ocelot: Add support for the vsc7512 chip via spi
dt-bindings: mfd: ocelot: Add bindings for VSC7512
resource: add define macro for register address resources
pinctrl: microchip-sgpio: add ability to be used in a non-mmio configuration
pinctrl: microchip-sgpio: allow sgpio driver to be used as a module
pinctrl: ocelot: add ability to be used in a non-mmio configuration
net: mdio: mscc-miim: add ability to be used in a non-mmio configuration
mfd: ocelot: Add helper to get regmap from a resource
====================
Link: https://lore.kernel.org/r/YxrjyHcceLOFlT/c@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-rw-r--r-- | Documentation/devicetree/bindings/mfd/mscc,ocelot.yaml | 160 | ||||
-rw-r--r-- | MAINTAINERS | 7 | ||||
-rw-r--r-- | drivers/mfd/Kconfig | 21 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 3 | ||||
-rw-r--r-- | drivers/mfd/ocelot-core.c | 161 | ||||
-rw-r--r-- | drivers/mfd/ocelot-spi.c | 299 | ||||
-rw-r--r-- | drivers/mfd/ocelot.h | 49 | ||||
-rw-r--r-- | drivers/net/mdio/mdio-mscc-miim.c | 42 | ||||
-rw-r--r-- | drivers/pinctrl/Kconfig | 5 | ||||
-rw-r--r-- | drivers/pinctrl/pinctrl-microchip-sgpio.c | 14 | ||||
-rw-r--r-- | drivers/pinctrl/pinctrl-ocelot.c | 16 | ||||
-rw-r--r-- | include/linux/ioport.h | 5 | ||||
-rw-r--r-- | include/linux/mfd/ocelot.h | 62 |
13 files changed, 795 insertions, 49 deletions
diff --git a/Documentation/devicetree/bindings/mfd/mscc,ocelot.yaml b/Documentation/devicetree/bindings/mfd/mscc,ocelot.yaml new file mode 100644 index 000000000000..8bf45a5673a4 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/mscc,ocelot.yaml @@ -0,0 +1,160 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/mscc,ocelot.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Ocelot Externally-Controlled Ethernet Switch + +maintainers: + - Colin Foster <colin.foster@in-advantage.com> + +description: | + The Ocelot ethernet switch family contains chips that have an internal CPU + (VSC7513, VSC7514) and chips that don't (VSC7511, VSC7512). All switches have + the option to be controlled externally, which is the purpose of this driver. + + The switch family is a multi-port networking switch that supports many + interfaces. Additionally, the device can perform pin control, MDIO buses, and + external GPIO expanders. + +properties: + compatible: + enum: + - mscc,vsc7512 + + reg: + maxItems: 1 + + "#address-cells": + const: 1 + + "#size-cells": + const: 1 + + spi-max-frequency: + maxItems: 1 + +patternProperties: + "^pinctrl@[0-9a-f]+$": + type: object + $ref: /schemas/pinctrl/mscc,ocelot-pinctrl.yaml + + "^gpio@[0-9a-f]+$": + type: object + $ref: /schemas/pinctrl/microchip,sparx5-sgpio.yaml + properties: + compatible: + enum: + - mscc,ocelot-sgpio + + "^mdio@[0-9a-f]+$": + type: object + $ref: /schemas/net/mscc,miim.yaml + properties: + compatible: + enum: + - mscc,ocelot-miim + +required: + - compatible + - reg + - '#address-cells' + - '#size-cells' + - spi-max-frequency + +additionalProperties: false + +examples: + - | + ocelot_clock: ocelot-clock { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <125000000>; + }; + + spi { + #address-cells = <1>; + #size-cells = <0>; + + soc@0 { + compatible = "mscc,vsc7512"; + spi-max-frequency = <2500000>; + reg = <0>; + #address-cells = <1>; + #size-cells = <1>; + + mdio@7107009c { + compatible = "mscc,ocelot-miim"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x7107009c 0x24>; + + sw_phy0: ethernet-phy@0 { + reg = <0x0>; + }; + }; + + mdio@710700c0 { + compatible = "mscc,ocelot-miim"; + pinctrl-names = "default"; + pinctrl-0 = <&miim1_pins>; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x710700c0 0x24>; + + sw_phy4: ethernet-phy@4 { + reg = <0x4>; + }; + }; + + gpio: pinctrl@71070034 { + compatible = "mscc,ocelot-pinctrl"; + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&gpio 0 0 22>; + reg = <0x71070034 0x6c>; + + sgpio_pins: sgpio-pins { + pins = "GPIO_0", "GPIO_1", "GPIO_2", "GPIO_3"; + function = "sg0"; + }; + + miim1_pins: miim1-pins { + pins = "GPIO_14", "GPIO_15"; + function = "miim"; + }; + }; + + gpio@710700f8 { + compatible = "mscc,ocelot-sgpio"; + #address-cells = <1>; + #size-cells = <0>; + bus-frequency = <12500000>; + clocks = <&ocelot_clock>; + microchip,sgpio-port-ranges = <0 15>; + pinctrl-names = "default"; + pinctrl-0 = <&sgpio_pins>; + reg = <0x710700f8 0x100>; + + sgpio_in0: gpio@0 { + compatible = "microchip,sparx5-sgpio-bank"; + reg = <0>; + gpio-controller; + #gpio-cells = <3>; + ngpios = <64>; + }; + + sgpio_out1: gpio@1 { + compatible = "microchip,sparx5-sgpio-bank"; + reg = <1>; + gpio-controller; + #gpio-cells = <3>; + ngpios = <64>; + }; + }; + }; + }; + +... + diff --git a/MAINTAINERS b/MAINTAINERS index 74036b51911d..6705fb8bfd3a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14746,6 +14746,13 @@ F: net/dsa/tag_ocelot.c F: net/dsa/tag_ocelot_8021q.c F: tools/testing/selftests/drivers/net/ocelot/* +OCELOT EXTERNAL SWITCH CONTROL +M: Colin Foster <colin.foster@in-advantage.com> +S: Supported +F: Documentation/devicetree/bindings/mfd/mscc,ocelot.yaml +F: drivers/mfd/ocelot* +F: include/linux/mfd/ocelot.h + OCXL (Open Coherent Accelerator Processor Interface OpenCAPI) DRIVER M: Frederic Barrat <fbarrat@linux.ibm.com> M: Andrew Donnellan <ajd@linux.ibm.com> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index abb58ab1a1a4..c3dd1fe8d8c9 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -963,6 +963,27 @@ config MFD_MENF21BMC This driver can also be built as a module. If so the module will be called menf21bmc. +config MFD_OCELOT + tristate "Microsemi Ocelot External Control Support" + depends on SPI_MASTER + select MFD_CORE + select REGMAP_SPI + help + Ocelot is a family of networking chips that support multiple ethernet + and fibre interfaces. In addition to networking, they contain several + other functions, including pinctrl, MDIO, and communication with + external chips. While some chips have an internal processor capable of + running an OS, others don't. All chips can be controlled externally + through different interfaces, including SPI, I2C, and PCIe. + + Say yes here to add support for Ocelot chips (VSC7511, VSC7512, + VSC7513, VSC7514) controlled externally. + + To compile this driver as a module, choose M here: the module will be + called ocelot-soc. + + If unsure, say N. + config EZX_PCAP bool "Motorola EZXPCAP Support" depends on SPI_MASTER diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 858cacf659d6..0004b7e86220 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -120,6 +120,9 @@ obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o obj-$(CONFIG_MFD_CORE) += mfd-core.o +ocelot-soc-objs := ocelot-core.o ocelot-spi.o +obj-$(CONFIG_MFD_OCELOT) += ocelot-soc.o + obj-$(CONFIG_EZX_PCAP) += ezx-pcap.o obj-$(CONFIG_MFD_CPCAP) += motorola-cpcap.o diff --git a/drivers/mfd/ocelot-core.c b/drivers/mfd/ocelot-core.c new file mode 100644 index 000000000000..1816d52c65c5 --- /dev/null +++ b/drivers/mfd/ocelot-core.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/* + * Core driver for the Ocelot chip family. + * + * The VSC7511, 7512, 7513, and 7514 can be controlled internally via an + * on-chip MIPS processor, or externally via SPI, I2C, PCIe. This core driver is + * intended to be the bus-agnostic glue between, for example, the SPI bus and + * the child devices. + * + * Copyright 2021-2022 Innovative Advantage Inc. + * + * Author: Colin Foster <colin.foster@in-advantage.com> + */ + +#include <linux/bits.h> +#include <linux/device.h> +#include <linux/export.h> +#include <linux/iopoll.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/mfd/core.h> +#include <linux/mfd/ocelot.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/types.h> + +#include <soc/mscc/ocelot.h> + +#include "ocelot.h" + +#define REG_GCB_SOFT_RST 0x0008 + +#define BIT_SOFT_CHIP_RST BIT(0) + +#define VSC7512_MIIM0_RES_START 0x7107009c +#define VSC7512_MIIM1_RES_START 0x710700c0 +#define VSC7512_MIIM_RES_SIZE 0x024 + +#define VSC7512_PHY_RES_START 0x710700f0 +#define VSC7512_PHY_RES_SIZE 0x004 + +#define VSC7512_GPIO_RES_START 0x71070034 +#define VSC7512_GPIO_RES_SIZE 0x06c + +#define VSC7512_SIO_CTRL_RES_START 0x710700f8 +#define VSC7512_SIO_CTRL_RES_SIZE 0x100 + +#define VSC7512_GCB_RST_SLEEP_US 100 +#define VSC7512_GCB_RST_TIMEOUT_US 100000 + +static int ocelot_gcb_chip_rst_status(struct ocelot_ddata *ddata) +{ + int val, err; + + err = regmap_read(ddata->gcb_regmap, REG_GCB_SOFT_RST, &val); + if (err) + return err; + + return val; +} + +int ocelot_chip_reset(struct device *dev) +{ + struct ocelot_ddata *ddata = dev_get_drvdata(dev); + int ret, val; + + /* + * Reset the entire chip here to put it into a completely known state. + * Other drivers may want to reset their own subsystems. The register + * self-clears, so one write is all that is needed and wait for it to + * clear. + */ + ret = regmap_write(ddata->gcb_regmap, REG_GCB_SOFT_RST, BIT_SOFT_CHIP_RST); + if (ret) + return ret; + + return readx_poll_timeout(ocelot_gcb_chip_rst_status, ddata, val, !val, + VSC7512_GCB_RST_SLEEP_US, VSC7512_GCB_RST_TIMEOUT_US); +} +EXPORT_SYMBOL_NS(ocelot_chip_reset, MFD_OCELOT); + +static const struct resource vsc7512_miim0_resources[] = { + DEFINE_RES_REG_NAMED(VSC7512_MIIM0_RES_START, VSC7512_MIIM_RES_SIZE, "gcb_miim0"), + DEFINE_RES_REG_NAMED(VSC7512_PHY_RES_START, VSC7512_PHY_RES_SIZE, "gcb_phy"), +}; + +static const struct resource vsc7512_miim1_resources[] = { + DEFINE_RES_REG_NAMED(VSC7512_MIIM1_RES_START, VSC7512_MIIM_RES_SIZE, "gcb_miim1"), +}; + +static const struct resource vsc7512_pinctrl_resources[] = { + DEFINE_RES_REG_NAMED(VSC7512_GPIO_RES_START, VSC7512_GPIO_RES_SIZE, "gcb_gpio"), +}; + +static const struct resource vsc7512_sgpio_resources[] = { + DEFINE_RES_REG_NAMED(VSC7512_SIO_CTRL_RES_START, VSC7512_SIO_CTRL_RES_SIZE, "gcb_sio"), +}; + +static const struct mfd_cell vsc7512_devs[] = { + { + .name = "ocelot-pinctrl", + .of_compatible = "mscc,ocelot-pinctrl", + .num_resources = ARRAY_SIZE(vsc7512_pinctrl_resources), + .resources = vsc7512_pinctrl_resources, + }, { + .name = "ocelot-sgpio", + .of_compatible = "mscc,ocelot-sgpio", + .num_resources = ARRAY_SIZE(vsc7512_sgpio_resources), + .resources = vsc7512_sgpio_resources, + }, { + .name = "ocelot-miim0", + .of_compatible = "mscc,ocelot-miim", + .of_reg = VSC7512_MIIM0_RES_START, + .use_of_reg = true, + .num_resources = ARRAY_SIZE(vsc7512_miim0_resources), + .resources = vsc7512_miim0_resources, + }, { + .name = "ocelot-miim1", + .of_compatible = "mscc,ocelot-miim", + .of_reg = VSC7512_MIIM1_RES_START, + .use_of_reg = true, + .num_resources = ARRAY_SIZE(vsc7512_miim1_resources), + .resources = vsc7512_miim1_resources, + }, +}; + +static void ocelot_core_try_add_regmap(struct device *dev, + const struct resource *res) +{ + if (dev_get_regmap(dev, res->name)) + return; + + ocelot_spi_init_regmap(dev, res); +} + +static void ocelot_core_try_add_regmaps(struct device *dev, + const struct mfd_cell *cell) +{ + int i; + + for (i = 0; i < cell->num_resources; i++) + ocelot_core_try_add_regmap(dev, &cell->resources[i]); +} + +int ocelot_core_init(struct device *dev) +{ + int i, ndevs; + + ndevs = ARRAY_SIZE(vsc7512_devs); + + for (i = 0; i < ndevs; i++) + ocelot_core_try_add_regmaps(dev, &vsc7512_devs[i]); + + return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, vsc7512_devs, ndevs, NULL, 0, NULL); +} +EXPORT_SYMBOL_NS(ocelot_core_init, MFD_OCELOT); + +MODULE_DESCRIPTION("Externally Controlled Ocelot Chip Driver"); +MODULE_AUTHOR("Colin Foster <colin.foster@in-advantage.com>"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(MFD_OCELOT_SPI); diff --git a/drivers/mfd/ocelot-spi.c b/drivers/mfd/ocelot-spi.c new file mode 100644 index 000000000000..0f097f4829d1 --- /dev/null +++ b/drivers/mfd/ocelot-spi.c @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/* + * SPI core driver for the Ocelot chip family. + * + * This driver will handle everything necessary to allow for communication over + * SPI to the VSC7511, VSC7512, VSC7513 and VSC7514 chips. The main functions + * are to prepare the chip's SPI interface for a specific bus speed, and a host + * processor's endianness. This will create and distribute regmaps for any + * children. + * + * Copyright 2021-2022 Innovative Advantage Inc. + * + * Author: Colin Foster <colin.foster@in-advantage.com> + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/export.h> +#include <linux/ioport.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/spi/spi.h> +#include <linux/types.h> +#include <linux/units.h> + +#include "ocelot.h" + +#define REG_DEV_CPUORG_IF_CTRL 0x0000 +#define REG_DEV_CPUORG_IF_CFGSTAT 0x0004 + +#define CFGSTAT_IF_NUM_VCORE (0 << 24) +#define CFGSTAT_IF_NUM_VRAP (1 << 24) +#define CFGSTAT_IF_NUM_SI (2 << 24) +#define CFGSTAT_IF_NUM_MIIM (3 << 24) + +#define VSC7512_DEVCPU_ORG_RES_START 0x71000000 +#define VSC7512_DEVCPU_ORG_RES_SIZE 0x38 + +#define VSC7512_CHIP_REGS_RES_START 0x71070000 +#define VSC7512_CHIP_REGS_RES_SIZE 0x14 + +static const struct resource vsc7512_dev_cpuorg_resource = + DEFINE_RES_REG_NAMED(VSC7512_DEVCPU_ORG_RES_START, + VSC7512_DEVCPU_ORG_RES_SIZE, + "devcpu_org"); + +static const struct resource vsc7512_gcb_resource = + DEFINE_RES_REG_NAMED(VSC7512_CHIP_REGS_RES_START, + VSC7512_CHIP_REGS_RES_SIZE, + "devcpu_gcb_chip_regs"); + +static int ocelot_spi_initialize(struct device *dev) +{ + struct ocelot_ddata *ddata = dev_get_drvdata(dev); + u32 val, check; + int err; + + val = OCELOT_SPI_BYTE_ORDER; + + /* + * The SPI address must be big-endian, but we want the payload to match + * our CPU. These are two bits (0 and 1) but they're repeated such that + * the write from any configuration will be valid. The four + * configurations are: + * + * 0b00: little-endian, MSB first + * | 111111 | 22221111 | 33222222 | + * | 76543210 | 54321098 | 32109876 | 10987654 | + * + * 0b01: big-endian, MSB first + * | 33222222 | 22221111 | 111111 | | + * | 10987654 | 32109876 | 54321098 | 76543210 | + * + * 0b10: little-endian, LSB first + * | 111111 | 11112222 | 22222233 | + * | 01234567 | 89012345 | 67890123 | 45678901 | + * + * 0b11: big-endian, LSB first + * | 22222233 | 11112222 | 111111 | | + * | 45678901 | 67890123 | 89012345 | 01234567 | + */ + err = regmap_write(ddata->cpuorg_regmap, REG_DEV_CPUORG_IF_CTRL, val); + if (err) + return err; + + /* + * Apply the number of padding bytes between a read request and the data + * payload. Some registers have access times of up to 1us, so if the + * first payload bit is shifted out too quickly, the read will fail. + */ + val = ddata->spi_padding_bytes; + err = regmap_write(ddata->cpuorg_regmap, REG_DEV_CPUORG_IF_CFGSTAT, val); + if (err) + return err; + + /* + * After we write the interface configuration, read it back here. This + * will verify several different things. The first is that the number of + * padding bytes actually got written correctly. These are found in bits + * 0:3. + * + * The second is that bit 16 is cleared. Bit 16 is IF_CFGSTAT:IF_STAT, + * and will be set if the register access is too fast. This would be in + * the condition that the number of padding bytes is insufficient for + * the SPI bus frequency. + * + * The last check is for bits 31:24, which define the interface by which + * the registers are being accessed. Since we're accessing them via the + * serial interface, it must return IF_NUM_SI. + */ + check = val | CFGSTAT_IF_NUM_SI; + + err = regmap_read(ddata->cpuorg_regmap, REG_DEV_CPUORG_IF_CFGSTAT, &val); + if (err) + return err; + + if (check != val) + return -ENODEV; + + return 0; +} + +static const struct regmap_config ocelot_spi_regmap_config = { + .reg_bits = 24, + .reg_stride = 4, + .reg_downshift = 2, + .val_bits = 32, + + .write_flag_mask = 0x80, + + .use_single_write = true, + .can_multi_write = false, + + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_NATIVE, +}; + +static int ocelot_spi_regmap_bus_read(void *context, const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct spi_transfer xfers[3] = {0}; + struct device *dev = context; + struct ocelot_ddata *ddata; + struct spi_device *spi; + struct spi_message msg; + unsigned int index = 0; + + ddata = dev_get_drvdata(dev); + spi = to_spi_device(dev); + + xfers[index].tx_buf = reg; + xfers[index].len = reg_size; + index++; + + if (ddata->spi_padding_bytes) { + xfers[index].len = ddata->spi_padding_bytes; + xfers[index].tx_buf = ddata->dummy_buf; + xfers[index].dummy_data = 1; + index++; + } + + xfers[index].rx_buf = val; + xfers[index].len = val_size; + index++; + + spi_message_init_with_transfers(&msg, xfers, index); + + return spi_sync(spi, &msg); +} + +static int ocelot_spi_regmap_bus_write(void *context, const void *data, size_t count) +{ + struct device *dev = context; + struct spi_device *spi = to_spi_device(dev); + + return spi_write(spi, data, count); +} + +static const struct regmap_bus ocelot_spi_regmap_bus = { + .write = ocelot_spi_regmap_bus_write, + .read = ocelot_spi_regmap_bus_read, +}; + +struct regmap *ocelot_spi_init_regmap(struct device *dev, const struct resource *res) +{ + struct regmap_config regmap_config; + + memcpy(®map_config, &ocelot_spi_regmap_config, sizeof(regmap_config)); + + regmap_config.name = res->name; + regmap_config.max_register = resource_size(res) - 1; + regmap_config.reg_base = res->start; + + return devm_regmap_init(dev, &ocelot_spi_regmap_bus, dev, ®map_config); +} +EXPORT_SYMBOL_NS(ocelot_spi_init_regmap, MFD_OCELOT_SPI); + +static int ocelot_spi_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ocelot_ddata *ddata; + struct regmap *r; + int err; + + ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + spi_set_drvdata(spi, ddata); + + if (spi->max_speed_hz <= 500000) { + ddata->spi_padding_bytes = 0; + } else { + /* + * Calculation taken from the manual for IF_CFGSTAT:IF_CFG. + * Register access time is 1us, so we need to configure and send + * out enough padding bytes between the read request and data + * transmission that lasts at least 1 microsecond. + */ + ddata->spi_padding_bytes = 1 + (spi->max_speed_hz / HZ_PER_MHZ + 2) / 8; + + ddata->dummy_buf = devm_kzalloc(dev, ddata->spi_padding_bytes, GFP_KERNEL); + if (!ddata->dummy_buf) + return -ENOMEM; + } + + spi->bits_per_word = 8; + + err = spi_setup(spi); + if (err) + return dev_err_probe(&spi->dev, err, "Error performing SPI setup\n"); + + r = ocelot_spi_init_regmap(dev, &vsc7512_dev_cpuorg_resource); + if (IS_ERR(r)) + return PTR_ERR(r); + + ddata->cpuorg_regmap = r; + + r = ocelot_spi_init_regmap(dev, &vsc7512_gcb_resource); + if (IS_ERR(r)) + return PTR_ERR(r); + + ddata->gcb_regmap = r; + + /* + * The chip must be set up for SPI before it gets initialized and reset. + * This must be done before calling init, and after a chip reset is + * performed. + */ + err = ocelot_spi_initialize(dev); + if (err) + return dev_err_probe(dev, err, "Error initializing SPI bus\n"); + + err = ocelot_chip_reset(dev); + if (err) + return dev_err_probe(dev, err, "Error resetting device\n"); + + /* + * A chip reset will clear the SPI configuration, so it needs to be done + * again before we can access any registers. + */ + err = ocelot_spi_initialize(dev); + if (err) + return dev_err_probe(dev, err, "Error initializing SPI bus after reset\n"); + + err = ocelot_core_init(dev); + if (err) + return dev_err_probe(dev, err, "Error initializing Ocelot core\n"); + + return 0; +} + +static const struct spi_device_id ocelot_spi_ids[] = { + { "vsc7512", 0 }, + { } +}; + +static const struct of_device_id ocelot_spi_of_match[] = { + { .compatible = "mscc,vsc7512" }, + { } +}; +MODULE_DEVICE_TABLE(of, ocelot_spi_of_match); + +static struct spi_driver ocelot_spi_driver = { + .driver = { + .name = "ocelot-soc", + .of_match_table = ocelot_spi_of_match, + }, + .id_table = ocelot_spi_ids, + .probe = ocelot_spi_probe, +}; +module_spi_driver(ocelot_spi_driver); + +MODULE_DESCRIPTION("SPI Controlled Ocelot Chip Driver"); +MODULE_AUTHOR("Colin Foster <colin.foster@in-advantage.com>"); +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_IMPORT_NS(MFD_OCELOT); diff --git a/drivers/mfd/ocelot.h b/drivers/mfd/ocelot.h new file mode 100644 index 000000000000..b8bc2f1486e2 --- /dev/null +++ b/drivers/mfd/ocelot.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright 2021, 2022 Innovative Advantage Inc. */ + +#ifndef _MFD_OCELOT_H +#define _MFD_OCELOT_H + +#include <linux/kconfig.h> + +struct device; +struct regmap; +struct resource; + +/** + * struct ocelot_ddata - Private data for an external Ocelot chip + * @gcb_regmap: General Configuration Block regmap. Used for + * operations like chip reset. + * @cpuorg_regmap: CPU Device Origin Block regmap. Used for operations + * like SPI bus configuration. + * @spi_padding_bytes: Number of padding bytes that must be thrown out before + * read data gets returned. This is calculated during + * initialization based on bus speed. + * @dummy_buf: Zero-filled buffer of spi_padding_bytes size. The dummy + * bytes that will be sent out between the address and + * data of a SPI read operation. + */ +struct ocelot_ddata { + struct regmap *gcb_regmap; + struct regmap *cpuorg_regmap; + int spi_padding_bytes; + void *dummy_buf; +}; + +int ocelot_chip_reset(struct device *dev); +int ocelot_core_init(struct device *dev); + +/* SPI-specific routines that won't be necessary for other interfaces */ +struct regmap *ocelot_spi_init_regmap(struct device *dev, + const struct resource *res); + +#define OCELOT_SPI_BYTE_ORDER_LE 0x00000000 +#define OCELOT_SPI_BYTE_ORDER_BE 0x81818181 + +#ifdef __LITTLE_ENDIAN +#define OCELOT_SPI_BYTE_ORDER OCELOT_SPI_BYTE_ORDER_LE +#else +#define OCELOT_SPI_BYTE_ORDER OCELOT_SPI_BYTE_ORDER_BE +#endif + +#endif diff --git a/drivers/net/mdio/mdio-mscc-miim.c b/drivers/net/mdio/mdio-mscc-miim.c index 08541007b18a..51f68daac152 100644 --- a/drivers/net/mdio/mdio-mscc-miim.c +++ b/drivers/net/mdio/mdio-mscc-miim.c @@ -12,6 +12,7 @@ #include <linux/iopoll.h> #include <linux/kernel.h> #include <linux/mdio/mdio-mscc-miim.h> +#include <linux/mfd/ocelot.h> #include <linux/module.h> #include <linux/of_mdio.h> #include <linux/phy.h> @@ -270,44 +271,25 @@ static int mscc_miim_clk_set(struct mii_bus *bus) static int mscc_miim_probe(struct platform_device *pdev) { - struct regmap *mii_regmap, *phy_regmap = NULL; struct device_node *np = pdev->dev.of_node; + struct regmap *mii_regmap, *phy_regmap; struct device *dev = &pdev->dev; - void __iomem *regs, *phy_regs; struct mscc_miim_dev *miim; - struct resource *res; struct mii_bus *bus; int ret; - regs = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); - if (IS_ERR(regs)) { - dev_err(dev, "Unable to map MIIM registers\n"); - return PTR_ERR(regs); - } - - mii_regmap = devm_regmap_init_mmio(dev, regs, &mscc_miim_regmap_config); - - if (IS_ERR(mii_regmap)) { - dev_err(dev, "Unable to create MIIM regmap\n"); - return PTR_ERR(mii_regmap); - } + mii_regmap = ocelot_regmap_from_resource(pdev, 0, + &mscc_miim_regmap_config); + if (IS_ERR(mii_regmap)) + return dev_err_probe(dev, PTR_ERR(mii_regmap), + "Unable to create MIIM regmap\n"); /* This resource is optional */ - res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - if (res) { - phy_regs = devm_ioremap_resource(dev, res); - if (IS_ERR(phy_regs)) { - dev_err(dev, "Unable to map internal phy registers\n"); - return PTR_ERR(phy_regs); - } - - phy_regmap = devm_regmap_init_mmio(dev, phy_regs, - &mscc_miim_phy_regmap_config); - if (IS_ERR(phy_regmap)) { - dev_err(dev, "Unable to create phy register regmap\n"); - return PTR_ERR(phy_regmap); - } - } + phy_regmap = ocelot_regmap_from_resource_optional(pdev, 1, + &mscc_miim_phy_regmap_config); + if (IS_ERR(phy_regmap)) + return dev_err_probe(dev, PTR_ERR(phy_regmap), + "Unable to create phy register regmap\n"); ret = mscc_miim_setup(dev, &bus, "mscc_miim", mii_regmap, 0); if (ret < 0) { diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index 1cf74b0c42e5..d768dcf75cf1 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -292,7 +292,7 @@ config PINCTRL_MCP23S08 corresponding interrupt-controller. config PINCTRL_MICROCHIP_SGPIO - bool "Pinctrl driver for Microsemi/Microchip Serial GPIO" + tristate "Pinctrl driver for Microsemi/Microchip Serial GPIO" depends on OF depends on HAS_IOMEM select GPIOLIB @@ -310,6 +310,9 @@ config PINCTRL_MICROCHIP_SGPIO connect control signals from SFP modules and to act as an LED controller. + If compiled as a module, the module name will be + pinctrl-microchip-sgpio. + config PINCTRL_OCELOT tristate "Pinctrl driver for the Microsemi Ocelot and Jaguar2 SoCs" depends on OF diff --git a/drivers/pinctrl/pinctrl-microchip-sgpio.c b/drivers/pinctrl/pinctrl-microchip-sgpio.c index 6f55bf7d5e05..2b4167a09b3b 100644 --- a/drivers/pinctrl/pinctrl-microchip-sgpio.c +++ b/drivers/pinctrl/pinctrl-microchip-sgpio.c @@ -12,6 +12,7 @@ #include <linux/clk.h> #include <linux/gpio/driver.h> #include <linux/io.h> +#include <linux/mfd/ocelot.h> #include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/pinctrl/pinmux.h> @@ -904,7 +905,6 @@ static int microchip_sgpio_probe(struct platform_device *pdev) struct reset_control *reset; struct sgpio_priv *priv; struct clk *clk; - u32 __iomem *regs; u32 val; struct regmap_config regmap_config = { .reg_bits = 32, @@ -937,11 +937,7 @@ static int microchip_sgpio_probe(struct platform_device *pdev) return -EINVAL; } - regs = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(regs)) - return PTR_ERR(regs); - - priv->regs = devm_regmap_init_mmio(dev, regs, ®map_config); + priv->regs = ocelot_regmap_from_resource(pdev, 0, ®map_config); if (IS_ERR(priv->regs)) return PTR_ERR(priv->regs); @@ -999,6 +995,7 @@ static const struct of_device_id microchip_sgpio_gpio_of_match[] = { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, microchip_sgpio_gpio_of_match); static struct platform_driver microchip_sgpio_pinctrl_driver = { .driver = { @@ -1008,4 +1005,7 @@ static struct platform_driver microchip_sgpio_pinctrl_driver = { }, .probe = microchip_sgpio_probe, }; -builtin_platform_driver(microchip_sgpio_pinctrl_driver); +module_platform_driver(microchip_sgpio_pinctrl_driver); + +MODULE_DESCRIPTION("Microchip SGPIO Pinctrl Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pinctrl/pinctrl-ocelot.c b/drivers/pinctrl/pinctrl-ocelot.c index c5fd154990c8..340ca2373429 100644 --- a/drivers/pinctrl/pinctrl-ocelot.c +++ b/drivers/pinctrl/pinctrl-ocelot.c @@ -10,6 +10,7 @@ #include <linux/gpio/driver.h> #include <linux/interrupt.h> #include <linux/io.h> +#include <linux/mfd/ocelot.h> #include <linux/of_device.h> #include <linux/of_irq.h> #include <linux/of_platform.h> @@ -1975,7 +1976,6 @@ static int ocelot_pinctrl_probe(struct platform_device *pdev) struct ocelot_pinctrl *info; struct reset_control *reset; struct regmap *pincfg; - void __iomem *base; int ret; struct regmap_config regmap_config = { .reg_bits = 32, @@ -2004,20 +2004,14 @@ static int ocelot_pinctrl_probe(struct platform_device *pdev) "Failed to get reset\n"); reset_control_reset(reset); - base = devm_ioremap_resource(dev, - platform_get_resource(pdev, IORESOURCE_MEM, 0)); - if (IS_ERR(base)) - return PTR_ERR(base); - info->stride = 1 + (info->desc->npins - 1) / 32; regmap_config.max_register = OCELOT_GPIO_SD_MAP * info->stride + 15 * 4; - info->map = devm_regmap_init_mmio(dev, base, ®map_config); - if (IS_ERR(info->map)) { - dev_err(dev, "Failed to create regmap\n"); - return PTR_ERR(info->map); - } + info->map = ocelot_regmap_from_resource(pdev, 0, ®map_config); + if (IS_ERR(info->map)) + return dev_err_probe(dev, PTR_ERR(info->map), + "Failed to create regmap\n"); dev_set_drvdata(dev, info->map); info->dev = dev; diff --git a/include/linux/ioport.h b/include/linux/ioport.h index 616b683563a9..8a76dca9deee 100644 --- a/include/linux/ioport.h +++ b/include/linux/ioport.h @@ -172,6 +172,11 @@ enum { #define DEFINE_RES_MEM(_start, _size) \ DEFINE_RES_MEM_NAMED((_start), (_size), NULL) +#define DEFINE_RES_REG_NAMED(_start, _size, _name) \ + DEFINE_RES_NAMED((_start), (_size), (_name), IORESOURCE_REG) +#define DEFINE_RES_REG(_start, _size) \ + DEFINE_RES_REG_NAMED((_start), (_size), NULL) + #define DEFINE_RES_IRQ_NAMED(_irq, _name) \ DEFINE_RES_NAMED((_irq), 1, (_name), IORESOURCE_IRQ) #define DEFINE_RES_IRQ(_irq) \ diff --git a/include/linux/mfd/ocelot.h b/include/linux/mfd/ocelot.h new file mode 100644 index 000000000000..dd72073d2d4f --- /dev/null +++ b/include/linux/mfd/ocelot.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright 2022 Innovative Advantage Inc. */ + +#ifndef _LINUX_MFD_OCELOT_H +#define _LINUX_MFD_OCELOT_H + +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/types.h> + +struct resource; + +static inline struct regmap * +ocelot_regmap_from_resource_optional(struct platform_device *pdev, + unsigned int index, + const struct regmap_config *config) +{ + struct device *dev = &pdev->dev; + struct resource *res; + void __iomem *regs; + + /* + * Don't use _get_and_ioremap_resource() here, since that will invoke + * prints of "invalid resource" which will simply add confusion. + */ + res = platform_get_resource(pdev, IORESOURCE_MEM, index); + if (res) { + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return ERR_CAST(regs); + return devm_regmap_init_mmio(dev, regs, config); + } + + /* + * Fall back to using REG and getting the resource from the parent + * device, which is possible in an MFD configuration + */ + if (dev->parent) { + res = platform_get_resource(pdev, IORESOURCE_REG, index); + if (!res) + return NULL; + + return dev_get_regmap(dev->parent, res->name); + } + + return NULL; +} + +static inline struct regmap * +ocelot_regmap_from_resource(struct platform_device *pdev, unsigned int index, + const struct regmap_config *config) +{ + struct regmap *map; + + map = ocelot_regmap_from_resource_optional(pdev, index, config); + return map ?: ERR_PTR(-ENOENT); +} + +#endif |