// SPDX-License-Identifier: GPL-2.0
/*
* Turris Mox module configuration bus driver
*
* Copyright (C) 2019 Marek Behun <marek.behun@nic.cz>
*/
#include <dt-bindings/bus/moxtet.h>
#include <linux/bitops.h>
#include <linux/debugfs.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/moxtet.h>
#include <linux/mutex.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/spi/spi.h>
/*
* @name: module name for sysfs
* @hwirq_base: base index for IRQ for this module (-1 if no IRQs)
* @nirqs: how many interrupts does the shift register provide
* @desc: module description for kernel log
*/
static const struct {
const char *name;
int hwirq_base;
int nirqs;
const char *desc;
} mox_module_table[] = {
/* do not change order of this array! */
{ NULL, 0, 0, NULL },
{ "sfp", -1, 0, "MOX D (SFP cage)" },
{ "pci", MOXTET_IRQ_PCI, 1, "MOX B (Mini-PCIe)" },
{ "topaz", MOXTET_IRQ_TOPAZ, 1, "MOX C (4 port switch)" },
{ "peridot", MOXTET_IRQ_PERIDOT(0), 1, "MOX E (8 port switch)" },
{ "usb3", MOXTET_IRQ_USB3, 2, "MOX F (USB 3.0)" },
{ "pci-bridge", -1, 0, "MOX G (Mini-PCIe bridge)" },
};
static inline bool mox_module_known(unsigned int id)
{
return id >= TURRIS_MOX_MODULE_FIRST && id <= TURRIS_MOX_MODULE_LAST;
}
static inline const char *mox_module_name(unsigned int id)
{
if (mox_module_known(id))
return mox_module_table[id].name;
else
return "unknown";
}
#define DEF_MODULE_ATTR(name, fmt, ...) \
static ssize_t \
module_##name##_show(struct device *dev, struct device_attribute *a, \
char *buf) \
{ \
struct moxtet_device *mdev = to_moxtet_device(dev); \
return sprintf(buf, (fmt), __VA_ARGS__); \
} \
static DEVICE_ATTR_RO(module_##name)
DEF_MODULE_ATTR(id, "0x%x\n", mdev->id);
DEF_MODULE_ATTR(name, "%s\n", mox_module_name(mdev->id));
DEF_MODULE_ATTR(description, "%s\n",
mox_module_known(mdev->id) ? mox_module_table[mdev->id].desc
: "");
static struct attribute *moxtet_dev_attrs[] = {
&dev_attr_module_id.attr,
&dev_attr_module_name.attr,
&dev_attr_module_description.attr,
NULL,
};
static const struct attribute_group moxtet_dev_group = {
.attrs = moxtet_dev_attrs,
};
static const struct attribute_group *moxtet_dev_groups[] = {
&moxtet_dev_group,
NULL,
};
static int moxtet_match(struct device *dev, struct device_driver *drv)
{
struct moxtet_device *mdev = to_moxtet_device(dev);
struct moxtet_driver *tdrv = to_moxtet_driver(drv);
const enum turris_mox_module_id *t;
if (of_driver_match_device(dev, drv))
return 1;
if (!tdrv->id_table)
return 0;
for (t = tdrv->id_table; *t; ++t)
if (*t == mdev->id)
return 1;
return 0;
}
static struct bus_type moxtet_bus_type = {
.name = "moxtet",
.dev_groups = moxtet_dev_groups,
.match = moxtet_match,
};
int __moxtet_register_driver(struct module *owner,
struct moxtet_driver *mdrv)
{
mdrv->driver.owner = owner;
mdrv->driver.bus = &moxtet_bus_type;
return driver_register(&mdrv->driver);
}
EXPORT_SYMBOL_GPL(__moxtet_register_driver);
static int moxtet_dev_check(struct device *dev, void *data)
{
struct moxtet_device *mdev = to_moxtet_device(dev);
struct moxtet_device *new_dev = data;
if (mdev->moxtet == new_dev->moxtet && mdev->id == new_dev->id &&
mdev->idx == new_dev->idx)
return -EBUSY;
return 0;
}
static void moxtet_dev_release(struct device *dev)
{
struct moxtet_device *mdev = to_moxtet_device(dev);
put_device(mdev->moxtet->dev);
kfree(mdev);
}
static struct moxtet_device *
moxtet_alloc_device(struct moxtet *moxtet)
{
struct moxtet_device *dev;
if (!get_device(moxtet->dev))
return NULL;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
put_device(moxtet->dev);
return NULL;
}
dev->moxtet = moxtet;
dev->dev.parent = moxtet->dev;
dev->dev.bus = &moxtet_bus_type;
dev->dev.release = moxtet_dev_release;
device_initialize(&dev->dev);
return dev;
}
static int moxtet_add_device(struct moxtet_device *dev)
{
static DEFINE_MUTEX(add_mutex);
int ret;
if (dev->idx >= TURRIS_MOX_MAX_MODULES || dev->id > 0xf)
return