mirror of
https://github.com/torvalds/linux.git
synced 2024-11-10 22:21:40 +00:00
c21ff0939d
Add support for the Ocelot SERDES module to support functionality of all non-internal phy ports. Signed-off-by: Colin Foster <colin.foster@in-advantage.com> Signed-off-by: David S. Miller <davem@davemloft.net>
235 lines
7.7 KiB
C
235 lines
7.7 KiB
C
// 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 0x00000024
|
|
|
|
#define VSC7512_PHY_RES_START 0x710700f0
|
|
#define VSC7512_PHY_RES_SIZE 0x00000004
|
|
|
|
#define VSC7512_GPIO_RES_START 0x71070034
|
|
#define VSC7512_GPIO_RES_SIZE 0x0000006c
|
|
|
|
#define VSC7512_SIO_CTRL_RES_START 0x710700f8
|
|
#define VSC7512_SIO_CTRL_RES_SIZE 0x00000100
|
|
|
|
#define VSC7512_HSIO_RES_START 0x710d0000
|
|
#define VSC7512_HSIO_RES_SIZE 0x00000128
|
|
|
|
#define VSC7512_ANA_RES_START 0x71880000
|
|
#define VSC7512_ANA_RES_SIZE 0x00010000
|
|
|
|
#define VSC7512_QS_RES_START 0x71080000
|
|
#define VSC7512_QS_RES_SIZE 0x00000100
|
|
|
|
#define VSC7512_QSYS_RES_START 0x71800000
|
|
#define VSC7512_QSYS_RES_SIZE 0x00200000
|
|
|
|
#define VSC7512_REW_RES_START 0x71030000
|
|
#define VSC7512_REW_RES_SIZE 0x00010000
|
|
|
|
#define VSC7512_SYS_RES_START 0x71010000
|
|
#define VSC7512_SYS_RES_SIZE 0x00010000
|
|
|
|
#define VSC7512_S0_RES_START 0x71040000
|
|
#define VSC7512_S1_RES_START 0x71050000
|
|
#define VSC7512_S2_RES_START 0x71060000
|
|
#define VCAP_RES_SIZE 0x00000400
|
|
|
|
#define VSC7512_PORT_0_RES_START 0x711e0000
|
|
#define VSC7512_PORT_1_RES_START 0x711f0000
|
|
#define VSC7512_PORT_2_RES_START 0x71200000
|
|
#define VSC7512_PORT_3_RES_START 0x71210000
|
|
#define VSC7512_PORT_4_RES_START 0x71220000
|
|
#define VSC7512_PORT_5_RES_START 0x71230000
|
|
#define VSC7512_PORT_6_RES_START 0x71240000
|
|
#define VSC7512_PORT_7_RES_START 0x71250000
|
|
#define VSC7512_PORT_8_RES_START 0x71260000
|
|
#define VSC7512_PORT_9_RES_START 0x71270000
|
|
#define VSC7512_PORT_10_RES_START 0x71280000
|
|
#define VSC7512_PORT_RES_SIZE 0x00010000
|
|
|
|
#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 resource vsc7512_serdes_resources[] = {
|
|
DEFINE_RES_REG_NAMED(VSC7512_HSIO_RES_START, VSC7512_HSIO_RES_SIZE, "hsio"),
|
|
};
|
|
|
|
static const struct resource vsc7512_switch_resources[] = {
|
|
DEFINE_RES_REG_NAMED(VSC7512_ANA_RES_START, VSC7512_ANA_RES_SIZE, "ana"),
|
|
DEFINE_RES_REG_NAMED(VSC7512_HSIO_RES_START, VSC7512_HSIO_RES_SIZE, "hsio"),
|
|
DEFINE_RES_REG_NAMED(VSC7512_QS_RES_START, VSC7512_QS_RES_SIZE, "qs"),
|
|
DEFINE_RES_REG_NAMED(VSC7512_QSYS_RES_START, VSC7512_QSYS_RES_SIZE, "qsys"),
|
|
DEFINE_RES_REG_NAMED(VSC7512_REW_RES_START, VSC7512_REW_RES_SIZE, "rew"),
|
|
DEFINE_RES_REG_NAMED(VSC7512_SYS_RES_START, VSC7512_SYS_RES_SIZE, "sys"),
|
|
DEFINE_RES_REG_NAMED(VSC7512_S0_RES_START, VCAP_RES_SIZE, "s0"),
|
|
DEFINE_RES_REG_NAMED(VSC7512_S1_RES_START, VCAP_RES_SIZE, "s1"),
|
|
DEFINE_RES_REG_NAMED(VSC7512_S2_RES_START, VCAP_RES_SIZE, "s2"),
|
|
DEFINE_RES_REG_NAMED(VSC7512_PORT_0_RES_START, VSC7512_PORT_RES_SIZE, "port0"),
|
|
DEFINE_RES_REG_NAMED(VSC7512_PORT_1_RES_START, VSC7512_PORT_RES_SIZE, "port1"),
|
|
DEFINE_RES_REG_NAMED(VSC7512_PORT_2_RES_START, VSC7512_PORT_RES_SIZE, "port2"),
|
|
DEFINE_RES_REG_NAMED(VSC7512_PORT_3_RES_START, VSC7512_PORT_RES_SIZE, "port3"),
|
|
DEFINE_RES_REG_NAMED(VSC7512_PORT_4_RES_START, VSC7512_PORT_RES_SIZE, "port4"),
|
|
DEFINE_RES_REG_NAMED(VSC7512_PORT_5_RES_START, VSC7512_PORT_RES_SIZE, "port5"),
|
|
DEFINE_RES_REG_NAMED(VSC7512_PORT_6_RES_START, VSC7512_PORT_RES_SIZE, "port6"),
|
|
DEFINE_RES_REG_NAMED(VSC7512_PORT_7_RES_START, VSC7512_PORT_RES_SIZE, "port7"),
|
|
DEFINE_RES_REG_NAMED(VSC7512_PORT_8_RES_START, VSC7512_PORT_RES_SIZE, "port8"),
|
|
DEFINE_RES_REG_NAMED(VSC7512_PORT_9_RES_START, VSC7512_PORT_RES_SIZE, "port9"),
|
|
DEFINE_RES_REG_NAMED(VSC7512_PORT_10_RES_START, VSC7512_PORT_RES_SIZE, "port10")
|
|
};
|
|
|
|
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,
|
|
}, {
|
|
.name = "ocelot-serdes",
|
|
.of_compatible = "mscc,vsc7514-serdes",
|
|
.num_resources = ARRAY_SIZE(vsc7512_serdes_resources),
|
|
.resources = vsc7512_serdes_resources,
|
|
}, {
|
|
.name = "ocelot-ext-switch",
|
|
.of_compatible = "mscc,vsc7512-switch",
|
|
.num_resources = ARRAY_SIZE(vsc7512_switch_resources),
|
|
.resources = vsc7512_switch_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);
|