Skip to content
Permalink
Browse files
mfd: ocelot: add support for external mfd control over SPI for the VS…
…C7512

Create a single SPI MFD ocelot device that manages the SPI bus on the
external chip and can handle requests for regmaps. This should allow any
ocelot driver (pinctrl, miim, etc.) to be used externally, provided they
utilize regmaps.

Signed-off-by: Colin Foster <colin.foster@in-advantage.com>
  • Loading branch information
colin-foster-in-advantage authored and intel-lab-lkp committed Dec 18, 2021
1 parent c122052 commit ec7f2a8290ac0b1f74a64884f16444c56edb274f
Show file tree
Hide file tree
Showing 5 changed files with 560 additions and 0 deletions.
@@ -958,6 +958,21 @@ config MFD_MENF21BMC
This driver can also be built as a module. If so the module
will be called menf21bmc.

config MFD_OCELOT_CORE
tristate "Microsemi Ocelot External Control Support"
select MFD_CORE
help
Say yes here to add support for Ocelot chips (VSC7511, VSC7512,
VSC7513, VSC7514) controlled externally.

config MFD_OCELOT_SPI
tristate "Microsemi Ocelot SPI interface"
depends on MFD_OCELOT_CORE
depends on SPI_MASTER
select REGMAP_SPI
help
Say yes here to add control to the MFD_OCELOT chips via SPI.

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

obj-$(CONFIG_MFD_OCELOT_CORE) += ocelot-core.o
obj-$(CONFIG_MFD_OCELOT_SPI) += ocelot-spi.o

obj-$(CONFIG_EZX_PCAP) += ezx-pcap.o
obj-$(CONFIG_MFD_CPCAP) += motorola-cpcap.o

@@ -0,0 +1,149 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/*
* Copyright 2021 Innovative Advantage Inc.
*/

#include <asm/byteorder.h>
#include <linux/spi/spi.h>
#include <linux/kconfig.h>
#include <linux/module.h>
#include <linux/regmap.h>

#include "ocelot-mfd.h"

#define REG(reg, offset) [reg] = offset

enum ocelot_mfd_gcb_regs {
GCB_SOFT_RST,
GCB_REG_MAX,
};

enum ocelot_mfd_gcb_regfields {
GCB_SOFT_RST_CHIP_RST,
GCB_REGFIELD_MAX,
};

static const u32 vsc7512_gcb_regmap[] = {
REG(GCB_SOFT_RST, 0x0008),
};

static const struct reg_field vsc7512_mfd_gcb_regfields[GCB_REGFIELD_MAX] = {
[GCB_SOFT_RST_CHIP_RST] = REG_FIELD(vsc7512_gcb_regmap[GCB_SOFT_RST], 0, 0),
};

struct ocelot_mfd_core {
struct ocelot_mfd_config *config;
struct regmap *gcb_regmap;
struct regmap_field *gcb_regfields[GCB_REGFIELD_MAX];
};

static const struct resource vsc7512_gcb_resource = {
.start = 0x71070000,
.end = 0x7107022b,
.name = "devcpu_gcb",
};

static int ocelot_mfd_reset(struct ocelot_mfd_core *core)
{
int ret;

dev_info(core->config->dev, "resetting ocelot chip\n");

ret = regmap_field_write(core->gcb_regfields[GCB_SOFT_RST_CHIP_RST], 1);
if (ret)
return ret;

/*
* Note: This is adapted from the PCIe reset strategy. The manual doesn't
* suggest how to do a reset over SPI, and the register strategy isn't
* possible.
*/
msleep(100);

ret = core->config->init_bus(core->config);
if (ret)
return ret;

return 0;
}

void ocelot_mfd_get_resource_name(char *name, const struct resource *res,
int size)
{
if (res->name)
snprintf(name, size - 1, "ocelot_mfd-%s", res->name);
else
snprintf(name, size - 1, "ocelot_mfd@0x%08x", res->start);
}
EXPORT_SYMBOL(ocelot_mfd_get_resource_name);

static struct regmap *ocelot_mfd_regmap_init(struct ocelot_mfd_core *core,
const struct resource *res)
{
struct device *dev = core->config->dev;
struct regmap *regmap;
char name[32];

ocelot_mfd_get_resource_name(name, res, sizeof(name) - 1);

regmap = dev_get_regmap(dev, name);

if (!regmap)
regmap = core->config->get_regmap(core->config, res, name);

return regmap;
}

int ocelot_mfd_init(struct ocelot_mfd_config *config)
{
struct device *dev = config->dev;
const struct reg_field *regfield;
struct ocelot_mfd_core *core;
int i, ret;

core = devm_kzalloc(dev, sizeof(struct ocelot_mfd_config), GFP_KERNEL);
if (!core)
return -ENOMEM;

dev_set_drvdata(dev, core);

core->config = config;

/* Create regmaps and regfields here */
core->gcb_regmap = ocelot_mfd_regmap_init(core, &vsc7512_gcb_resource);
if (!core->gcb_regmap)
return -ENOMEM;

for (i = 0; i < GCB_REGFIELD_MAX; i++) {
regfield = &vsc7512_mfd_gcb_regfields[i];
core->gcb_regfields[i] =
devm_regmap_field_alloc(dev, core->gcb_regmap,
*regfield);
if (!core->gcb_regfields[i])
return -ENOMEM;
}

/* Prepare the chip */
ret = ocelot_mfd_reset(core);
if (ret) {
dev_err(dev, "ocelot mfd reset failed with code %d\n", ret);
return ret;
}

/* Create and loop over all child devices here */

return 0;
}
EXPORT_SYMBOL(ocelot_mfd_init);

int ocelot_mfd_remove(struct ocelot_mfd_config *config)
{
/* Loop over all children and remove them */

return 0;
}
EXPORT_SYMBOL(ocelot_mfd_remove);

MODULE_DESCRIPTION("Ocelot Chip MFD driver");
MODULE_AUTHOR("Colin Foster <colin.foster@in-advantage.com>");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0 OR MIT */
/*
* Copyright 2021 Innovative Advantage Inc.
*/

#include <linux/regmap.h>

struct ocelot_mfd_config {
struct device *dev;
struct regmap *(*get_regmap)(struct ocelot_mfd_config *config,
const struct resource *res,
const char *name);
int (*init_bus)(struct ocelot_mfd_config *config);
};

void ocelot_mfd_get_resource_name(char *name, const struct resource *res,
int size);
int ocelot_mfd_init(struct ocelot_mfd_config *config);
int ocelot_mfd_remove(struct ocelot_mfd_config *config);

0 comments on commit ec7f2a8

Please sign in to comment.