mirror of
https://github.com/Fishwaldo/linux-bl808.git
synced 2025-03-16 03:54:10 +00:00
Immutable branch between MFD Net and Pinctrl due for the v6.0 merge window
-----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEdrbJNaO+IJqU8IdIUa+KL4f8d2EFAmMa40oACgkQUa+KL4f8 d2GiGw/+KC2umt0rczbESO2gHtHOQVXXtjQrQzX4WElC6hH5qVqQlHcxKwsNRmfI oB5Ff4mkZ8V1XUtdxXT0zw5ouLjyTXU++6P2QRm4BNWiXFKBzOe4F5TrwbaoRM3j 6kEZiTzWegyqReoTlYWLTJiiH4HA4JzSp/g6dBV4IVcHWjwXZg8p1ZIsLh68bOGi 2E7j2H+XvRlEpnPBpg1fd8Q59xTdvylpvve9uv+sGSA4V4kmSBD9WcPlLkdvvUvH 2M275g1ZDLsunSIkUk3RqiZDQVxVU+E+RXG5TreWY5wp1Q6xGoSCXNKldYPLxUhp RTxIRClOc/5hMxQiHY2PkVy06uTRUsVealir/xLDwtTomfk/Om8lBa0BlAxOMqK6 qnG+OSQ5u26mnQB9DBa2tXs5pw6/W2ofFg06yO+z4vXrtjqMUJ/2OgzprbaxBITy XIPREGitMAJD+/EOlvcUI/B0tSOAuhAj2MbGXJAmpP8i3WKBO4fDyev5deoVTP3g NDEXnUXIaEd+B+mmt0x1D8IOBDmwIQ9BigNBlEl0huO8M6L3YsBcFUhYvEOKJCQ5 B93wRtmW3wR3STSq8pyfBj37fDZPqVIvWPhOtt/IvRS9ozZnVvgVoSxHql0t8WxE VmvqshD9EmawF4Y5wZ0nPDgGgGnZXpn1xmTKfweW99deb3lXwzA= =HdIo -----END PGP SIGNATURE----- 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>
This commit is contained in:
commit
2bd178c5ea
13 changed files with 795 additions and 49 deletions
160
Documentation/devicetree/bindings/mfd/mscc,ocelot.yaml
Normal file
160
Documentation/devicetree/bindings/mfd/mscc,ocelot.yaml
Normal file
|
@ -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>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
...
|
||||
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
161
drivers/mfd/ocelot-core.c
Normal file
161
drivers/mfd/ocelot-core.c
Normal file
|
@ -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);
|
299
drivers/mfd/ocelot-spi.c
Normal file
299
drivers/mfd/ocelot-spi.c
Normal file
|
@ -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);
|
49
drivers/mfd/ocelot.h
Normal file
49
drivers/mfd/ocelot.h
Normal file
|
@ -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
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) \
|
||||
|
|
62
include/linux/mfd/ocelot.h
Normal file
62
include/linux/mfd/ocelot.h
Normal file
|
@ -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
|
Loading…
Add table
Reference in a new issue