On 7/24/20 7:06 AM, Vaishnav M A wrote:
> Attached is a patch for the mikroBUS driver which helps to
> instantiate an add-on board device on a mikrobus port by fetching
> the device identifier manifest binary from an EEPROM on-board
> the device. mikroBUS is an add-on board socket standard by
> MikroElektronika that can be freely used by anyone
> following the guidelines, more details and discussions on
> the status of the driver can be found here in this eLinux wiki:
> https://elinux.org/Mikrobus
Vaishnav, I am finally looking at this a little more closely
today. More than anything I want to compliment you on all
this work. I think it looks like a great use of Greybus for
essentially its intended purpose, and I would love to see
it extended as needed to support what you're doing here.
At first glance your patch looks good, but I want to take the
time to give it a thorough review. Unfortunately I did not
follow your progress on the GSoC project (which you posted
about last year here), and have not followed any discussion
since then, so it's taking me a little time to come up to speed
on it. I'm hoping you might help me (and others) do this more
quickly.
I am scanning through some of the materials online and I find
there is quite a lot. That includes information about both
your project and about mikroBUS. Zeroing in on things that
fairly concisely describe the way things really work would be
very valuable. Can you point me directly at something that
gives an overview of both the hardware and software
architecture (specifically as it's used with Greybus)? If
not, I'm open to finding other ways to get in synch. I'll
expand on this a little more below.
This isn't strictly necessary, but if I wanted to reproduce the
hardware setup you use to validate this code, what hardware
is required? For example, is it possible to use a BeagleBone Black
with a MikroBus Cape for testing, or must a PocktBeagle be used?
Is there one or several MikroBus clickboards that would be the
best for basic testing?
Can you provide a short summary of why the Greybus manifest
format needed to be extended? Can you summarize how a mikroBUS
add-on board differs from an Ara module? And what they have in
common?
This patch is an RFC, and you say that v3 of the mikroBUS spec
is being developed. Is your plan to have the "real" code (when
you submit it) adhere to the newer version of the spec? Are
there specific things that you expect will be included in v3
that will how the driver works (compared to this RFC)?
Please realize I'm asking these questions so I can be more
effective in evaluating what you're doing here. I'd like to
provide feedback not just on the code, but on the design that
underlies it, and for that I need to get better informed. My
hope is that you can help me find or gather that information
as quickly as possible.
And now I'll go give a quick initial review of the code...
Thanks.
-Alex
> In the current state of the driver, more than 80 different
> add-on boards have been tested on the BeagleBoard.org
> PocketBeagle and the manifest binary is generated using the same
> manifesto tool used to generate Greybus manifest binaries,
> The pull request to manifesto to add new descriptors specific
> to mikrobus is here : https://github.com/projectara/manifesto/pull/2
> The utilization of Greybus manifest binaries here is not entirely
> coincidental, We are evaluating ways to add mikroBUS sockets and
> devices via Greybus expansion.
>
> The mikroBUS standard includes SPI, I2C, UART, PWM, ADC, GPIO
> and power (3.3V and 5V) connections to interface common embedded
> peripherals, There are more than 750 add-on boards ranging from
> wireless connectivity boards to human-machine interface sensors
> which conform to the mikroBUS standard, out of which more than 140
> boards already have support in the Linux kernel.Today, there is no
> mainlinesolution for enabling mikroBUS add-on boards at run-time, the
> most straight forward method for loading drivers is to provide
> device-tree overlay fragments at boot time, this method suffers
> from the need to maintain a large out-of-tree database for which there
> is need to maintain an overlay for every mikroBUS add-on board for every
> Linux system and for every mikroBUS socket on that system.
>
> The mikroBUS driver tries to solve the problem by using extended version
> of the greybus manifest to describe the add-on board device specific
> information required by the device driver and uses it along with the fixed
> port specific information to probe the specific device driver.The manifest
> binary is now fetched from an I2C EEPROM over the I2C bus on the mikroBUS
> port(subject to change in mikroBUS v3 specification) and enumerate drivers
> for the add-on devices.There is also ongoing work to define a mikroBUS
> v3 standard in which the addon board includes a non-volatile storage to
> store the device identifier manifest binary, once the mikroBUS v3 standard
> is released, the mikroBUS can be seen as a probeable bus such that the
> kernel can discover the device on the bus at boot time.
>
> The driver also has a few debug SysFS interfaces for testing on add-on
> boards without an EEPROM, these can be used in the following manner:
> (example for mikroBUS port 1 on BeagleBoard.org PocketBeagle):
>
> printf "%b" '\x01\x00\x00\x59\x32\x17' > /sys/bus/mikrobus/add_port
>
> The bytes in the byte array sequence are (in order):
>
> * i2c_adap_nr
> * spi_master_nr
> * serdev_ctlr_nr
> * rst_gpio_nr
> * pwm_gpio_nr
> * int_gpio_nr
> * add_port sysFS entry is purely for debug and in the actual state
> of the driver, port configuration will be loaded from a suitable
> device tree overlay fragment.
>
> echo 0 > /sys/bus/mikrobus/del_port (to delete the attached port)
> echo 1 > /sys/class/mikrobus-port/mikrobus-0/rescan (to rescan the EEPROM
> contents on the I2C bus on the mikroBUS port).
>
> cat board_manifest.mnfb > /sys/class/mikrobus-port/mikrobus-0/new_device
> * debug interface to pass the manifest binary in case an EEPROM is absent
> echo 0 > /sys/class/mikrobus-port/mikrobus-0/delete_device
> * to unload the loaded board on the mikrobus port
>
> These sysFS interfaces are only implemented for debug purposes and
> in the actual implementation of the driver these will be removed and
> the manifest binary will be fetched from the non volatile storage on-board
> the device.
>
> The mikroBUS driver enable the mikroBUS to be a probeable bus such that
> the kernel can discover the device and automatically load the drivers.
> There are already several Linux platforms with mikroBUS sockets and the
> mikroBUS driver helps to reduce the time to develop and debug
> support for various mikroBUS add-on boards. Further, it opens up
> the possibility for support under dynamically instantiated buses
> such as with Greybus.
>
> Please let know the feedback you have on this patch or the approach used.
>
> Thanks,
>
> Vaishnav M A
>
> Signed-off-by: Vaishnav M A <vaishnav@xxxxxxxxxxxxxxx>
> ---
> MAINTAINERS | 6 +
> drivers/misc/Kconfig | 1 +
> drivers/misc/Makefile | 1 +
> drivers/misc/mikrobus/Kconfig | 16 +
> drivers/misc/mikrobus/Makefile | 5 +
> drivers/misc/mikrobus/mikrobus_core.c | 649 ++++++++++++++++++++++
> drivers/misc/mikrobus/mikrobus_core.h | 130 +++++
> drivers/misc/mikrobus/mikrobus_manifest.c | 390 +++++++++++++
> drivers/misc/mikrobus/mikrobus_manifest.h | 21 +
> include/linux/greybus/greybus_manifest.h | 53 ++
> 10 files changed, 1272 insertions(+)
> create mode 100644 drivers/misc/mikrobus/Kconfig
> create mode 100644 drivers/misc/mikrobus/Makefile
> create mode 100644 drivers/misc/mikrobus/mikrobus_core.c
> create mode 100644 drivers/misc/mikrobus/mikrobus_core.h
> create mode 100644 drivers/misc/mikrobus/mikrobus_manifest.c
> create mode 100644 drivers/misc/mikrobus/mikrobus_manifest.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index d53db30d1365..9a049746203f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -11402,6 +11402,12 @@ M: Oliver Neukum <oliver@xxxxxxxxxx>
> S: Maintained
> F: drivers/usb/image/microtek.*
>
> +MIKROBUS ADDON BOARD DRIVER
> +M: Vaishnav M A <vaishnav@xxxxxxxxxxxxxxx>
> +S: Maintained
> +W: https://elinux.org/Mikrobus
> +F: drivers/misc/mikrobus/
> +
> MIPS
> M: Thomas Bogendoerfer <tsbogend@xxxxxxxxxxxxxxxx>
> L: linux-mips@xxxxxxxxxxxxxxx
> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> index e1b1ba5e2b92..334f0c39d56b 100644
> --- a/drivers/misc/Kconfig
> +++ b/drivers/misc/Kconfig
> @@ -472,4 +472,5 @@ source "drivers/misc/ocxl/Kconfig"
> source "drivers/misc/cardreader/Kconfig"
> source "drivers/misc/habanalabs/Kconfig"
> source "drivers/misc/uacce/Kconfig"
> +source "drivers/misc/mikrobus/Kconfig"
> endmenu
> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
> index c7bd01ac6291..45486dd77da5 100644
> --- a/drivers/misc/Makefile
> +++ b/drivers/misc/Makefile
> @@ -40,6 +40,7 @@ obj-$(CONFIG_VMWARE_BALLOON) += vmw_balloon.o
> obj-$(CONFIG_PCH_PHUB) += pch_phub.o
> obj-y += ti-st/
> obj-y += lis3lv02d/
> +obj-y += mikrobus/
> obj-$(CONFIG_ALTERA_STAPL) +=altera-stapl/
> obj-$(CONFIG_INTEL_MEI) += mei/
> obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/
> diff --git a/drivers/misc/mikrobus/Kconfig b/drivers/misc/mikrobus/Kconfig
> new file mode 100644
> index 000000000000..c3b93e12daad
> --- /dev/null
> +++ b/drivers/misc/mikrobus/Kconfig
> @@ -0,0 +1,16 @@
> +menuconfig MIKROBUS
> + tristate "Module for instantiating devices on mikroBUS ports"
> + help
> + This option enables the mikroBUS driver. mikroBUS is an add-on
> + board socket standard that offers maximum expandability with
> + the smallest number of pins. The mikroBUS driver helps in
> + instantiating devices on the mikroBUS port with identifier
> + data fetched from an EEPROM on the device, more details on
> + the mikroBUS driver support and discussion can be found in
> + this eLinux wiki : elinux.org/Mikrobus
> +
> +
> + Say Y here to enable support for this driver.
> +
> + To compile this code as a module, chose M here: the module
> + will be called mikrobus.ko
> diff --git a/drivers/misc/mikrobus/Makefile b/drivers/misc/mikrobus/Makefile
> new file mode 100644
> index 000000000000..1f80ed4064d8
> --- /dev/null
> +++ b/drivers/misc/mikrobus/Makefile
> @@ -0,0 +1,5 @@
> +# SPDX-License-Identifier: GPL-2.0
> +# mikroBUS Core
> +
> +mikrobus-y := mikrobus_core.o mikrobus_manifest.o
> +obj-$(CONFIG_MIKROBUS) += mikrobus.o
> \ No newline at end of file
> diff --git a/drivers/misc/mikrobus/mikrobus_core.c b/drivers/misc/mikrobus/mikrobus_core.c
> new file mode 100644
> index 000000000000..78c96c637632
> --- /dev/null
> +++ b/drivers/misc/mikrobus/mikrobus_core.c
> @@ -0,0 +1,649 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * mikroBUS driver for instantiating add-on
> + * board devices with an identifier EEPROM
> + *
> + * Copyright 2020 Vaishnav M A, BeagleBoard.org Foundation.
> + */
> +
> +#define pr_fmt(fmt) "mikrobus: " fmt
> +
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/idr.h>
> +#include <linux/init.h>
> +#include <linux/jump_label.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/mutex.h>
> +#include <linux/device.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/i2c.h>
> +#include <linux/gpio.h>
> +#include <linux/gpio/machine.h>
> +#include <linux/nvmem-consumer.h>
> +#include <linux/nvmem-provider.h>
> +#include <linux/interrupt.h>
> +#include <linux/spi/spi.h>
> +#include <linux/serdev.h>
> +#include <linux/property.h>
> +#include <linux/slab.h>
> +
> +#include "mikrobus_core.h"
> +#include "mikrobus_manifest.h"
> +
> +#define ATMEL_24C32_I2C_ADDR 0x57
> +
> +static DEFINE_IDR(mikrobus_port_idr);
> +static struct class_compat *mikrobus_port_compat_class;
> +static bool is_registered;
> +
> +static ssize_t add_port_store(struct bus_type *bt, const char *buf,
> + size_t count)
> +{
> + struct mikrobus_port_config *cfg;
> +
> + if (count < sizeof(*cfg)) {
> + pr_err("add_port: incorrect config data received: %s\n", buf);
> + return -EINVAL;
> + }
> + mikrobus_register_port_config((void *)buf);
> + return count;
> +}
> +BUS_ATTR_WO(add_port);
> +
> +static ssize_t del_port_store(struct bus_type *bt, const char *buf,
> + size_t count)
> +{
> + int id;
> + char end;
> + int res;
> +
> + res = sscanf(buf, "%d%c", &id, &end);
> + if (res < 1) {
> + pr_err("delete_port: cannot parse mikrobus port ID\n");
> + return -EINVAL;
> + }
> + if (!idr_find(&mikrobus_port_idr, id)) {
> + pr_err("attempting to delete unregistered port [%d]\n", id);
> + return -EINVAL;
> + }
> + mikrobus_del_port(idr_find(&mikrobus_port_idr, id));
> + return count;
> +}
> +BUS_ATTR_WO(del_port);
> +
> +static struct attribute *mikrobus_attrs[] = {
> + &bus_attr_add_port.attr,
> + &bus_attr_del_port.attr,
> + NULL
> +};
> +ATTRIBUTE_GROUPS(mikrobus);
> +
> +struct bus_type mikrobus_bus_type = {
> + .name = "mikrobus",
> + .bus_groups = mikrobus_groups,
> +};
> +EXPORT_SYMBOL_GPL(mikrobus_bus_type);
> +
> +static int mikrobus_port_scan_eeprom(struct mikrobus_port *port)
> +{
> + char header[12];
> + struct addon_board_info *board;
> + int manifest_size;
> + int retval;
> + char *buf;
> +
> + nvmem_device_read(port->eeprom, 0, 12, header);
> + manifest_size = mikrobus_manifest_header_validate(header, 12);
> + if (manifest_size > 0) {
> + buf = kzalloc(manifest_size, GFP_KERNEL);
> + nvmem_device_read(port->eeprom, 0, manifest_size, buf);
> + board = kzalloc(sizeof(*board), GFP_KERNEL);
> + if (!board)
> + return -ENOMEM;
> + INIT_LIST_HEAD(&board->manifest_descs);
> + INIT_LIST_HEAD(&board->devices);
> + retval = mikrobus_manifest_parse(board, (void *)buf, manifest_size);
> + if (!retval) {
> + pr_err("failed to parse manifest, size: %d", manifest_size);
> + return -EINVAL;
> + }
> + retval = mikrobus_register_board(port, board);
> + if (retval) {
> + pr_err("failed to register board: %s", board->name);
> + return -EINVAL;
> + }
> + kfree(buf);
> + } else {
> + pr_err("inavlide manifest port %d", port->id);
> + return -EINVAL;
> + }
> + return 0;
> +}
> +
> +static ssize_t name_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + return sprintf(buf, "%s\n", to_mikrobus_port(dev)->name);
> +}
> +static DEVICE_ATTR_RO(name);
> +
> +static ssize_t new_device_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct mikrobus_port *port = to_mikrobus_port(dev);
> + struct addon_board_info *board;
> + int retval;
> +
> + if (port->board == NULL) {
> + board = kzalloc(sizeof(*board), GFP_KERNEL);
> + if (!board)
> + return -EINVAL;
> + INIT_LIST_HEAD(&board->manifest_descs);
> + INIT_LIST_HEAD(&board->devices);
> + } else {
> + pr_err("port %d already has board registered", port->id);
> + return -EINVAL;
> + }
> + retval = mikrobus_manifest_parse(board, (void *)buf, count);
> + if (!retval) {
> + pr_err("failed to parse manifest");
> + return -EINVAL;
> + }
> + retval = mikrobus_register_board(port, board);
> + if (retval) {
> + pr_err("failed to register board: %s", board->name);
> + return -EINVAL;
> + }
> + return count;
> +}
> +static DEVICE_ATTR_WO(new_device);
> +
> +static ssize_t rescan_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct mikrobus_port *port = to_mikrobus_port(dev);
> + int id;
> + char end;
> + int res;
> + int retval;
> +
> + res = sscanf(buf, "%d%c", &id, &end);
> + if (res < 1) {
> + pr_err("rescan: Can't parse trigger\n");
> + return -EINVAL;
> + }
> + if (port->board != NULL) {
> + pr_err("port %d already has board registered", port->id);
> + return -EINVAL;
> + }
> + retval = mikrobus_port_scan_eeprom(port);
> + if (retval) {
> + pr_err("port %d board register from manifest failed", port->id);
> + return -EINVAL;
> + }
> + return count;
> +}
> +static DEVICE_ATTR_WO(rescan);
> +
> +static ssize_t delete_device_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int id;
> + char end;
> + int res;
> + struct mikrobus_port *port = to_mikrobus_port(dev);
> +
> + res = sscanf(buf, "%d%c", &id, &end);
> + if (res < 1) {
> + pr_err("delete_board: Can't parse board ID\n");
> + return -EINVAL;
> + }
> + if (port->board == NULL) {
> + pr_err("delete_board: port does not have any boards registered\n");
> + return -EINVAL;
> + }
> + mikrobus_unregister_board(port, port->board);
> + return count;
> +}
> +static DEVICE_ATTR_IGNORE_LOCKDEP(delete_device, 0200, NULL, delete_device_store);
> +
> +static struct attribute *mikrobus_port_attrs[] = {
> + &dev_attr_new_device.attr, &dev_attr_rescan.attr,
> + &dev_attr_delete_device.attr, &dev_attr_name.attr, NULL};
> +ATTRIBUTE_GROUPS(mikrobus_port);
> +
> +static void mikrobus_dev_release(struct device *dev)
> +{
> + struct mikrobus_port *port = to_mikrobus_port(dev);
> +
> + idr_remove(&mikrobus_port_idr, port->id);
> + kfree(port);
> +}
> +
> +struct device_type mikrobus_port_type = {
> + .groups = mikrobus_port_groups,
> + .release = mikrobus_dev_release,
> +};
> +EXPORT_SYMBOL_GPL(mikrobus_port_type);
> +
> +static int mikrobus_get_irq(struct mikrobus_port *port, int irqno,
> + int irq_type)
> +{
> + int irq;
> +
> + switch (irqno) {
> + case MIKROBUS_GPIO_INT:
> + irq = gpiod_to_irq(port->int_gpio);
> + break;
> + case MIKROBUS_GPIO_RST:
> + irq = gpiod_to_irq(port->rst_gpio);
> + break;
> + case MIKROBUS_GPIO_PWM:
> + irq = gpiod_to_irq(port->pwm_gpio);
> + break;
> + default:
> + return -EINVAL;
> + }
> + if (irq < 0) {
> + pr_err("Could not get irq for irq type: %d", irqno);
> + return -EINVAL;
> + }
> + irq_set_irq_type(irq, irq_type);
> + return irq;
> +}
> +
> +static int mikrobus_setup_gpio(struct gpio_desc *gpio, int gpio_state)
> +{
> + int retval;
> +
> + if (gpio_state == MIKROBUS_GPIO_UNUSED)
> + return 0;
> + switch (gpio_state) {
> + case MIKROBUS_GPIO_INPUT:
> + retval = gpiod_direction_input(gpio);
> + break;
> + case MIKROBUS_GPIO_OUTPUT_HIGH:
> + retval = gpiod_direction_output(gpio, 1);
> + gpiod_set_value_cansleep(gpio, 1);
> + break;
> + case MIKROBUS_GPIO_OUTPUT_LOW:
> + retval = gpiod_direction_output(gpio, 0);
> + gpiod_set_value_cansleep(gpio, 0);
> + break;
> + default:
> + return -EINVAL;
> + }
> + return retval;
> +}
> +
> +static void mikrobus_spi_device_delete(struct spi_master *master, unsigned int cs)
> +{
> + struct device *dev;
> + char str[32];
> +
> + snprintf(str, sizeof(str), "%s.%u", dev_name(&master->dev), cs);
> + dev = bus_find_device_by_name(&spi_bus_type, NULL, str);
> + if (dev != NULL) {
> + spi_unregister_device(to_spi_device(dev));
> + put_device(dev);
> + }
> +}
> +
> +static char *mikrobus_get_gpio_chip_name(struct mikrobus_port *port, int gpio)
> +{
> + char *name;
> + struct gpio_chip *gpiochip;
> +
> + switch (gpio) {
> + case MIKROBUS_GPIO_INT:
> + gpiochip = gpiod_to_chip(port->int_gpio);
> + name = kmemdup(gpiochip->label, 40, GFP_KERNEL);
> + break;
> + case MIKROBUS_GPIO_RST:
> + gpiochip = gpiod_to_chip(port->rst_gpio);
> + name = kmemdup(gpiochip->label, 40, GFP_KERNEL);
> + break;
> + case MIKROBUS_GPIO_PWM:
> + gpiochip = gpiod_to_chip(port->pwm_gpio);
> + name = kmemdup(gpiochip->label, 40, GFP_KERNEL);
> + break;
> + }
> + return name;
> +}
> +
> +static int mikrobus_get_gpio_hwnum(struct mikrobus_port *port, int gpio)
> +{
> + int hwnum;
> + struct gpio_chip *gpiochip;
> +
> + switch (gpio) {
> + case MIKROBUS_GPIO_INT:
> + gpiochip = gpiod_to_chip(port->int_gpio);
> + hwnum = desc_to_gpio(port->int_gpio) - gpiochip->base;
> + break;
> + case MIKROBUS_GPIO_RST:
> + gpiochip = gpiod_to_chip(port->rst_gpio);
> + hwnum = desc_to_gpio(port->rst_gpio) - gpiochip->base;
> + break;
> + case MIKROBUS_GPIO_PWM:
> + gpiochip = gpiod_to_chip(port->pwm_gpio);
> + hwnum = desc_to_gpio(port->pwm_gpio) - gpiochip->base;
> + break;
> + }
> + return hwnum;
> +}
> +
> +static void release_mikrobus_device(struct board_device_info *dev)
> +{
> + list_del(&dev->links);
> + kfree(dev);
> +}
> +
> +static void release_board_devices(struct addon_board_info *info)
> +{
> + struct board_device_info *dev;
> + struct board_device_info *next;
> +
> + list_for_each_entry_safe(dev, next, &info->devices, links)
> + release_mikrobus_device(dev);
> +}
> +
> +static int mikrobus_register_device(struct mikrobus_port *port,
> + struct board_device_info *dev, char *board_name)
> +{
> + struct i2c_board_info *i2c;
> + struct spi_board_info *spi;
> + struct gpiod_lookup_table *lookup;
> + char devname[40];
> + int i;
> +
> + pr_info(" registering device : %s\n", dev->drv_name);
> +
> + if (dev->gpio_lookup != NULL) {
> + lookup = dev->gpio_lookup;
> + if (dev->protocol == MIKROBUS_PROTOCOL_SPI) {
> + snprintf(devname, sizeof(devname), "%s.%u",
> + dev_name(&port->spi_mstr->dev), dev->reg);
> + lookup->dev_id = kmemdup(devname, 40, GFP_KERNEL);
> + } else if (dev->protocol == MIKROBUS_PROTOCOL_I2C)
> + lookup->dev_id = dev->drv_name;
> + pr_info(" adding lookup table : %s\n", lookup->dev_id);
> + for (i = 0; i < dev->num_gpio_resources; i++) {
> + lookup->table[i].key =
> + mikrobus_get_gpio_chip_name(port, lookup->table[i].chip_hwnum);
> + lookup->table[i].chip_hwnum =
> + mikrobus_get_gpio_hwnum(port, lookup->table[i].chip_hwnum);
> + lookup->table[i].flags = GPIO_ACTIVE_HIGH;
> + }
> + gpiod_add_lookup_table(lookup);
> + }
> + switch (dev->protocol) {
> + case MIKROBUS_PROTOCOL_SPI:
> + spi = kzalloc(sizeof(*spi), GFP_KERNEL);
> + if (!spi)
> + return -ENOMEM;
> + strncpy(spi->modalias, dev->drv_name, sizeof(spi->modalias) - 1);
> + if (dev->irq)
> + spi->irq = mikrobus_get_irq(port, dev->irq, dev->irq_type);
> + if (dev->properties)
> + spi->properties = dev->properties;
> + spi->chip_select = dev->reg;
> + spi->max_speed_hz = dev->max_speed_hz;
> + spi->mode = dev->mode;
> + mikrobus_spi_device_delete(port->spi_mstr, dev->reg);
> + dev->dev_client = (void *)spi_new_device(port->spi_mstr, spi);
> + break;
> + case MIKROBUS_PROTOCOL_I2C:
> + i2c = kzalloc(sizeof(*i2c), GFP_KERNEL);
> + if (!i2c)
> + return -ENOMEM;
> + strncpy(i2c->type, dev->drv_name, sizeof(i2c->type) - 1);
> + if (dev->irq)
> + i2c->irq = mikrobus_get_irq(port, dev->irq, dev->irq_type);
> + if (dev->properties)
> + i2c->properties = dev->properties;
> + i2c->addr = dev->reg;
> + dev->dev_client = (void *)i2c_new_client_device(port->i2c_adap, i2c);
> + break;
> + case MIKROBUS_PROTOCOL_UART:
> + pr_info("SERDEV devices support not yet added");
> + break;
> + default:
> + return -EINVAL;
> + }
> + return 0;
> +}
> +
> +static void mikrobus_unregister_device(struct mikrobus_port *port, struct board_device_info *dev,
> + char *board_name)
> +{
> + pr_info(" removing device : %s\n", dev->drv_name);
> + if (dev->gpio_lookup != NULL) {
> + gpiod_remove_lookup_table(dev->gpio_lookup);
> + kfree(dev->gpio_lookup);
> + }
> + if (dev->properties != NULL)
> + kfree(dev->properties);
> + switch (dev->protocol) {
> + case MIKROBUS_PROTOCOL_SPI:
> + spi_unregister_device((struct spi_device *)dev->dev_client);
> + break;
> + case MIKROBUS_PROTOCOL_I2C:
> + i2c_unregister_device((struct i2c_client *)dev->dev_client);
> + break;
> + case MIKROBUS_PROTOCOL_UART:
> + pr_err("SERDEV devices support not yet added");
> + break;
> + }
> +}
> +
> +int mikrobus_register_board(struct mikrobus_port *port, struct addon_board_info *board)
> +{
> + struct board_device_info *devinfo;
> + struct board_device_info *next;
> + int retval;
> +
> + if (WARN_ON(list_empty(&board->devices)))
> + return false;
> +
> + retval = mikrobus_setup_gpio(port->pwm_gpio, board->pwm_gpio_state);
> + if (retval) {
> + pr_err("mikrobus_setup_gpio : can't setup pwm gpio state: (%d)\n", retval);
> + return retval;
> + }
> + retval = mikrobus_setup_gpio(port->int_gpio, board->int_gpio_state);
> + if (retval) {
> + pr_err("mikrobus_setup_gpio : can't setup int gpio state: (%d)\n", retval);
> + return retval;
> + }
> + retval = mikrobus_setup_gpio(port->rst_gpio, board->rst_gpio_state);
> + if (retval) {
> + pr_err("mikrobus_setup_gpio : can't setup rst gpio state: (%d)\n", retval);
> + return retval;
> + }
> + list_for_each_entry_safe(devinfo, next, &board->devices, links)
> + mikrobus_register_device(port, devinfo, board->name);
> + port->board = board;
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(mikrobus_register_board);
> +
> +void mikrobus_unregister_board(struct mikrobus_port *port, struct addon_board_info *board)
> +{
> + struct board_device_info *devinfo;
> + struct board_device_info *next;
> +
> + if (WARN_ON(list_empty(&board->devices)))
> + return;
> + port->board = NULL;
> + list_for_each_entry_safe(devinfo, next, &board->devices, links)
> + mikrobus_unregister_device(port, devinfo, board->name);
> + release_board_devices(board);
> + kfree(board);
> + port->board = NULL;
> +}
> +EXPORT_SYMBOL_GPL(mikrobus_unregister_board);
> +
> +int mikrobus_register_port_config(struct mikrobus_port_config *cfg)
> +{
> + struct mikrobus_port *port;
> + int retval;
> +
> + if (WARN_ON(!is_registered))
> + return -EAGAIN;
> + port = kzalloc(sizeof(*port), GFP_KERNEL);
> + if (!port)
> + return -ENOMEM;
> + port->pwm_gpio = gpio_to_desc(cfg->pwm_gpio_nr);
> + port->int_gpio = gpio_to_desc(cfg->int_gpio_nr);
> + port->rst_gpio = gpio_to_desc(cfg->rst_gpio_nr);
> + port->spi_mstr = spi_busnum_to_master(cfg->spi_master_nr);
> + port->i2c_adap = i2c_get_adapter(cfg->i2c_adap_nr);
> + retval = mikrobus_register_port(port);
> + if (retval) {
> + pr_err("port : can't register port from config (%d)\n", retval);
> + return retval;
> + }
> + return retval;
> +}
> +EXPORT_SYMBOL_GPL(mikrobus_register_port_config);
> +
> +static struct i2c_board_info mikrobus_eeprom_info = {
> + I2C_BOARD_INFO("24c32", ATMEL_24C32_I2C_ADDR),
> +};
> +
> +static int mikrobus_port_probe_eeprom(struct mikrobus_port *port)
> +{
> + struct i2c_client *eeprom_client;
> + struct nvmem_device *eeprom;
> + char dev_name[40];
> +
> + eeprom_client = i2c_new_client_device(port->i2c_adap, &mikrobus_eeprom_info);
> + if (!IS_ERR(eeprom_client)) {
> + pr_info(" mikrobus port %d default eeprom is probed at %02x\n", port->id,
> + eeprom_client->addr);
> + snprintf(dev_name, sizeof(dev_name), "%d-%04x0", port->i2c_adap->nr,
> + eeprom_client->addr);
> + eeprom = nvmem_device_get(&eeprom_client->dev, dev_name);
> + if (IS_ERR(eeprom)) {
> + pr_err(" mikrobus port %d eeprom nvmem device probe failed\n", port->id);
> + i2c_unregister_device(eeprom_client);
> + port->eeprom = NULL;
> + return 0;
> + }
> + } else {
> + pr_info(" mikrobus port %d default eeprom probe failed\n", port->id);
> + return 0;
> + }
> + port->eeprom = eeprom;
> + port->eeprom_client = eeprom_client;
> + return 0;
> +}
> +
> +int mikrobus_register_port(struct mikrobus_port *port)
> +{
> + int retval;
> + int id;
> +
> + if (WARN_ON(!is_registered))
> + return -EAGAIN;
> + id = idr_alloc(&mikrobus_port_idr, port, 0, 0, GFP_KERNEL);
> + if (id < 0)
> + return id;
> + port->id = id;
> + port->dev.bus = &mikrobus_bus_type;
> + port->dev.type = &mikrobus_port_type;
> + strncpy(port->name, "mikrobus-port", sizeof(port->name) - 1);
> + dev_set_name(&port->dev, "mikrobus-%d", port->id);
> + pr_info("registering port mikrobus-%d\n ", port->id);
> + retval = device_register(&port->dev);
> + if (retval) {
> + pr_err("port '%d': can't register device (%d)\n", port->id, retval);
> + put_device(&port->dev);
> + return retval;
> + }
> + retval = class_compat_create_link(mikrobus_port_compat_class, &port->dev,
> + port->dev.parent);
> + if (retval)
> + dev_warn(&port->dev, "failed to create compatibility class link\n");
> + if (!port->eeprom) {
> + pr_info("mikrobus port %d eeprom empty probing default eeprom\n", port->id);
> + retval = mikrobus_port_probe_eeprom(port);
> + }
> + if (port->eeprom) {
> + retval = mikrobus_port_scan_eeprom(port);
> + if (retval)
> + dev_warn(&port->dev, "failed to register board from manifest\n");
> + }
> + return retval;
> +}
> +EXPORT_SYMBOL_GPL(mikrobus_register_port);
> +
> +void mikrobus_del_port(struct mikrobus_port *port)
> +{
> + struct mikrobus_port *found;
> +
> + found = idr_find(&mikrobus_port_idr, port->id);
> + if (found != port) {
> + pr_err("attempting to delete unregistered port [%s]\n", port->name);
> + return;
> + }
> + if (port->board != NULL) {
> + pr_err("attempting to delete port with registered boards, port [%s]\n",
> + port->name);
> + return;
> + }
> +
> + if (port->eeprom) {
> + nvmem_device_put(port->eeprom);
> + i2c_unregister_device(port->eeprom_client);
> + }
> +
> + class_compat_remove_link(mikrobus_port_compat_class, &port->dev,
> + port->dev.parent);
> + device_unregister(&port->dev);
> + idr_remove(&mikrobus_port_idr, port->id);
> + memset(&port->dev, 0, sizeof(port->dev));
> +}
> +EXPORT_SYMBOL_GPL(mikrobus_del_port);
> +
> +static int __init mikrobus_init(void)
> +{
> + int retval;
> +
> + retval = bus_register(&mikrobus_bus_type);
> + if (retval) {
> + pr_err("bus_register failed (%d)\n", retval);
> + return retval;
> + }
> + mikrobus_port_compat_class = class_compat_register("mikrobus-port");
> + if (!mikrobus_port_compat_class) {
> + pr_err("class_compat register failed (%d)\n", retval);
> + retval = -ENOMEM;
> + goto class_err;
> + }
> + is_registered = true;
> + return 0;
> +class_err:
> + bus_unregister(&mikrobus_bus_type);
> + idr_destroy(&mikrobus_port_idr);
> + is_registered = false;
> + return retval;
> +}
> +subsys_initcall(mikrobus_init);
> +
> +static void __exit mikrobus_exit(void)
> +{
> + bus_unregister(&mikrobus_bus_type);
> + class_compat_unregister(mikrobus_port_compat_class);
> + idr_destroy(&mikrobus_port_idr);
> +}
> +module_exit(mikrobus_exit);
> +
> +MODULE_AUTHOR("Vaishnav M A <vaishnav@xxxxxxxxxxxxxxx>");
> +MODULE_DESCRIPTION("mikroBUS main module");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/misc/mikrobus/mikrobus_core.h b/drivers/misc/mikrobus/mikrobus_core.h
> new file mode 100644
> index 000000000000..9684d315f564
> --- /dev/null
> +++ b/drivers/misc/mikrobus/mikrobus_core.h
> @@ -0,0 +1,130 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * mikroBUS Driver for instantiating add-on
> + * board devices with an identifier EEPROM
> + *
> + * Copyright 2020 Vaishnav M A, BeagleBoard.org Foundation.
> + */
> +
> +#ifndef __MIKROBUS_H
> +#define __MIKROBUS_H
> +
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/i2c.h>
> +#include <linux/gpio.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/gpio/machine.h>
> +#include <linux/spi/spi.h>
> +#include <linux/idr.h>
> +#include <linux/init.h>
> +#include <linux/jump_label.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/device.h>
> +#include <linux/of_device.h>
> +#include <linux/serdev.h>
> +#include <linux/of.h>
> +#include <linux/property.h>
> +#include <linux/slab.h>
> +
> +#define MIKROBUS_VERSION_MAJOR 0x00
> +#define MIKROBUS_VERSION_MINOR 0x02
> +
> +extern struct bus_type mikrobus_bus_type;
> +extern struct device_type mikrobus_port_type;
> +
> +enum mikrobus_property_type {
> + MIKROBUS_PROPERTY_TYPE_LINK = 0x00,
> + MIKROBUS_PROPERTY_TYPE_GPIO = 0x01,
> + MIKROBUS_PROPERTY_TYPE_U8 = 0x02,
> + MIKROBUS_PROPERTY_TYPE_U16 = 0x03,
> + MIKROBUS_PROPERTY_TYPE_U32 = 0x04,
> + MIKROBUS_PROPERTY_TYPE_U64 = 0x05,
> +};
> +
> +enum mikrobus_gpio_pin {
> + MIKROBUS_GPIO_INVALID = 0x00,
> + MIKROBUS_GPIO_INT = 0x01,
> + MIKROBUS_GPIO_RST = 0x02,
> + MIKROBUS_GPIO_PWM = 0x03,
> +};
> +
> +enum mikrobus_protocol {
> + MIKROBUS_PROTOCOL_SPI = 0x01,
> + MIKROBUS_PROTOCOL_I2C = 0x02,
> + MIKROBUS_PROTOCOL_UART = 0x03,
> + MIKROBUS_PROTOCOL_SPI_GPIOCS = 0x04,
> + MIKROBUS_PROTOCOL_I2C_MUX = 0x05
> +};
> +
> +enum mikrobus_gpio_state {
> + MIKROBUS_GPIO_UNUSED = 0x00,
> + MIKROBUS_GPIO_INPUT = 0x01,
> + MIKROBUS_GPIO_OUTPUT_HIGH = 0x02,
> + MIKROBUS_GPIO_OUTPUT_LOW = 0x03,
> +};
> +
> +struct mikrobus_port_config {
> + __u8 i2c_adap_nr;
> + __u8 spi_master_nr;
> + __u8 serdev_ctlr_nr;
> + __u8 rst_gpio_nr;
> + __u8 pwm_gpio_nr;
> + __u8 int_gpio_nr;
> +} __packed;
> +
> +struct board_device_info {
> + struct list_head links;
> + int id;
> + char *drv_name;
> + unsigned short protocol;
> + unsigned short reg;
> + u32 max_speed_hz;
> + unsigned int mode;
> + int irq;
> + int irq_type;
> + int cs_gpio;
> + unsigned short num_gpio_resources;
> + unsigned short num_properties;
> + struct property_entry *properties;
> + struct gpiod_lookup_table *gpio_lookup;
> + void *dev_client;
> +};
> +
> +struct addon_board_info {
> + char *name;
> + unsigned short num_devices;
> + unsigned short rst_gpio_state;
> + unsigned short pwm_gpio_state;
> + unsigned short int_gpio_state;
> + struct list_head manifest_descs;
> + struct list_head devices;
> +};
> +
> +struct mikrobus_port {
> + char name[48];
> + struct module *owner;
> + struct device dev;
> + int id;
> + struct gpio_desc *pwm_gpio;
> + struct gpio_desc *int_gpio;
> + struct gpio_desc *rst_gpio;
> + struct spi_master *spi_mstr;
> + struct i2c_adapter *i2c_adap;
> + struct addon_board_info *board;
> + struct i2c_client *eeprom_client;
> + struct nvmem_device *eeprom;
> +};
> +#define to_mikrobus_port(d) container_of(d, struct mikrobus_port, dev)
> +
> +int mikrobus_register_board(struct mikrobus_port *port,
> + struct addon_board_info *board);
> +void mikrobus_unregister_board(struct mikrobus_port *port,
> + struct addon_board_info *board);
> +int mikrobus_register_port_config(struct mikrobus_port_config *cfg);
> +int mikrobus_register_port(struct mikrobus_port *port);
> +void mikrobus_del_port(struct mikrobus_port *port);
> +
> +#endif /* __MIKROBUS_H */
> diff --git a/drivers/misc/mikrobus/mikrobus_manifest.c b/drivers/misc/mikrobus/mikrobus_manifest.c
> new file mode 100644
> index 000000000000..60ebca560f0d
> --- /dev/null
> +++ b/drivers/misc/mikrobus/mikrobus_manifest.c
> @@ -0,0 +1,390 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * mikroBUS manifest parsing, an
> + * extension to Greybus Manifest Parsing
> + * under drivers/greybus/manifest.c
> + *
> + * Copyright 2014-2015 Google Inc.
> + * Copyright 2014-2015 Linaro Ltd.
> + */
> +
> +#define pr_fmt(fmt) "mikrobus_manifest: " fmt
> +
> +#include <linux/bits.h>
> +#include <linux/types.h>
> +#include <linux/property.h>
> +#include <linux/greybus/greybus_manifest.h>
> +
> +#include "mikrobus_manifest.h"
> +
> +struct manifest_desc {
> + struct list_head links;
> + size_t size;
> + void *data;
> + enum greybus_descriptor_type type;
> +};
> +
> +static void release_manifest_descriptor(struct manifest_desc *descriptor)
> +{
> + list_del(&descriptor->links);
> + kfree(descriptor);
> +}
> +
> +static void release_manifest_descriptors(struct addon_board_info *info)
> +{
> + struct manifest_desc *descriptor;
> + struct manifest_desc *next;
> +
> + list_for_each_entry_safe(descriptor, next, &info->manifest_descs, links)
> + release_manifest_descriptor(descriptor);
> +}
> +
> +static int identify_descriptor(struct addon_board_info *info, struct greybus_descriptor *desc,
> + size_t size)
> +{
> + struct greybus_descriptor_header *desc_header = &desc->header;
> + struct manifest_desc *descriptor;
> + size_t desc_size;
> + size_t expected_size;
> +
> + if (size < sizeof(*desc_header))
> + return -EINVAL;
> + desc_size = le16_to_cpu(desc_header->size);
> + if (desc_size > size)
> + return -EINVAL;
> + expected_size = sizeof(*desc_header);
> + switch (desc_header->type) {
> + case GREYBUS_TYPE_STRING:
> + expected_size += sizeof(struct greybus_descriptor_string);
> + expected_size += desc->string.length;
> + expected_size = ALIGN(expected_size, 4);
> + break;
> + case GREYBUS_TYPE_PROPERTY:
> + expected_size += sizeof(struct greybus_descriptor_property);
> + expected_size += desc->property.length;
> + expected_size = ALIGN(expected_size, 4);
> + break;
> + case GREYBUS_TYPE_DEVICE:
> + expected_size += sizeof(struct greybus_descriptor_device);
> + break;
> + case GREYBUS_TYPE_MIKROBUS:
> + expected_size += sizeof(struct greybus_descriptor_mikrobus);
> + break;
> + case GREYBUS_TYPE_INTERFACE:
> + expected_size += sizeof(struct greybus_descriptor_interface);
> + break;
> + case GREYBUS_TYPE_CPORT:
> + expected_size += sizeof(struct greybus_descriptor_cport);
> + break;
> + case GREYBUS_TYPE_BUNDLE:
> + expected_size += sizeof(struct greybus_descriptor_bundle);
> + break;
> + case GREYBUS_TYPE_INVALID:
> + default:
> + return -EINVAL;
> + }
> +
> + descriptor = kzalloc(sizeof(*descriptor), GFP_KERNEL);
> + if (!descriptor)
> + return -ENOMEM;
> + descriptor->size = desc_size;
> + descriptor->data = "" *)desc + sizeof(*desc_header);
> + descriptor->type = desc_header->type;
> + list_add_tail(&descriptor->links, &info->manifest_descs);
> + return desc_size;
> +}
> +
> +static char *mikrobus_string_get(struct addon_board_info *info, u8 string_id)
> +{
> + struct greybus_descriptor_string *desc_string;
> + struct manifest_desc *descriptor;
> + bool found = false;
> + char *string;
> +
> + if (!string_id)
> + return NULL;
> +
> + list_for_each_entry(descriptor, &info->manifest_descs, links) {
> + if (descriptor->type != GREYBUS_TYPE_STRING)
> + continue;
> + desc_string = descriptor->data;
> + if (desc_string->id == string_id) {
> + found = true;
> + break;
> + }
> + }
> + if (!found)
> + return ERR_PTR(-ENOENT);
> + string = kmemdup(&desc_string->string, desc_string->length + 1, GFP_KERNEL);
> + if (!string)
> + return ERR_PTR(-ENOMEM);
> + string[desc_string->length] = '\0';
> + return string;
> +}
> +
> +static void mikrobus_state_get(struct addon_board_info *info)
> +{
> + struct greybus_descriptor_mikrobus *mikrobus;
> + struct greybus_descriptor_interface *interface;
> + struct manifest_desc *descriptor;
> + bool found = false;
> +
> + list_for_each_entry(descriptor, &info->manifest_descs, links) {
> + if (descriptor->type == GREYBUS_TYPE_MIKROBUS) {
> + mikrobus = descriptor->data;
> + found = true;
> + break;
> + }
> + }
> +
> + if (found) {
> + info->num_devices = mikrobus->num_devices;
> + info->rst_gpio_state = mikrobus->rst_gpio_state;
> + info->pwm_gpio_state = mikrobus->pwm_gpio_state;
> + info->int_gpio_state = mikrobus->int_gpio_state;
> + } else {
> + info->num_devices = 1;
> + info->rst_gpio_state = MIKROBUS_GPIO_UNUSED;
> + info->pwm_gpio_state = MIKROBUS_GPIO_UNUSED;
> + info->int_gpio_state = MIKROBUS_GPIO_UNUSED;
> + }
> +
> + list_for_each_entry(descriptor, &info->manifest_descs, links) {
> + if (descriptor->type == GREYBUS_TYPE_INTERFACE) {
> + interface = descriptor->data;
> + break;
> + }
> + }
> + info->name = mikrobus_string_get(info, interface->product_stringid);
> +}
> +
> +static struct property_entry *
> +mikrobus_property_entry_get(struct addon_board_info *info, u8 *prop_link,
> + int num_properties)
> +{
> + struct greybus_descriptor_property *desc_property;
> + struct manifest_desc *descriptor;
> + struct property_entry *properties;
> + int i;
> + char *prop_name;
> + bool found = false;
> + u8 *val_u8;
> + u16 *val_u16;
> + u32 *val_u32;
> + u64 *val_u64;
> +
> + properties = kcalloc(num_properties, sizeof(*properties), GFP_KERNEL);
> + if (!properties)
> + return ERR_PTR(-ENOMEM);
> + for (i = 0; i < num_properties; i++) {
> + list_for_each_entry(descriptor, &info->manifest_descs, links) {
> + if (descriptor->type != GREYBUS_TYPE_PROPERTY)
> + continue;
> + desc_property = descriptor->data;
> + if (desc_property->id == prop_link[i]) {
> + found = true;
> + break;
> + }
> + }
> + if (!found)
> + return ERR_PTR(-ENOENT);
> + prop_name = mikrobus_string_get(info, desc_property->propname_stringid);
> + switch (desc_property->type) {
> + case MIKROBUS_PROPERTY_TYPE_U8:
> + val_u8 = kmemdup(&desc_property->value,
> + (desc_property->length) * sizeof(u8), GFP_KERNEL);
> + if (desc_property->length == 1)
> + properties[i] = PROPERTY_ENTRY_U8(prop_name, *val_u8);
> + else
> + properties[i] = PROPERTY_ENTRY_U8_ARRAY_LEN(
> + prop_name, (void *)desc_property->value, desc_property->length);
> + break;
> + case MIKROBUS_PROPERTY_TYPE_U16:
> + val_u16 = kmemdup(&desc_property->value,
> + (desc_property->length) * sizeof(u16), GFP_KERNEL);
> + if (desc_property->length == 1)
> + properties[i] = PROPERTY_ENTRY_U16(prop_name, *val_u16);
> + else
> + properties[i] = PROPERTY_ENTRY_U16_ARRAY_LEN(
> + prop_name, (void *)desc_property->value, desc_property->length);
> + break;
> + case MIKROBUS_PROPERTY_TYPE_U32:
> + val_u32 = kmemdup(&desc_property->value,
> + (desc_property->length) * sizeof(u32), GFP_KERNEL);
> + if (desc_property->length == 1)
> + properties[i] = PROPERTY_ENTRY_U32(prop_name, *val_u32);
> + else
> + properties[i] = PROPERTY_ENTRY_U32_ARRAY_LEN(
> + prop_name, (void *)desc_property->value, desc_property->length);
> + break;
> + case MIKROBUS_PROPERTY_TYPE_U64:
> + val_u64 = kmemdup(&desc_property->value,
> + (desc_property->length) * sizeof(u64), GFP_KERNEL);
> + if (desc_property->length == 1)
> + properties[i] = PROPERTY_ENTRY_U64(prop_name, *val_u64);
> + else
> + properties[i] = PROPERTY_ENTRY_U64_ARRAY_LEN(
> + prop_name, (void *)desc_property->value, desc_property->length);
> + break;
> + default:
> + return ERR_PTR(-EINVAL);
> + }
> + }
> + return properties;
> +}
> +
> +static u8 *mikrobus_property_link_get(struct addon_board_info *info, u8 prop_id,
> + u8 prop_type)
> +{
> + struct greybus_descriptor_property *desc_property;
> + struct manifest_desc *descriptor;
> + bool found = false;
> + u8 *val_u8;
> +
> + if (!prop_id)
> + return NULL;
> + list_for_each_entry(descriptor, &info->manifest_descs, links) {
> + if (descriptor->type != GREYBUS_TYPE_PROPERTY)
> + continue;
> + desc_property = descriptor->data;
> + if (desc_property->id == prop_id && desc_property->type == prop_type) {
> + found = true;
> + break;
> + }
> + }
> + if (!found)
> + return ERR_PTR(-ENOENT);
> + val_u8 = kmemdup(&desc_property->value, desc_property->length, GFP_KERNEL);
> + return val_u8;
> +}
> +
> +static int mikrobus_manifest_attach_device(struct addon_board_info *info,
> + struct greybus_descriptor_device *dev_desc)
> +{
> + struct board_device_info *dev;
> + struct gpiod_lookup_table *lookup;
> + struct greybus_descriptor_property *desc_property;
> + struct manifest_desc *descriptor;
> + int i;
> + u8 *prop_link;
> + u8 *gpio_desc_link;
> +
> + dev = kzalloc(sizeof(*dev), GFP_KERNEL);
> + if (!dev)
> + return -ENOMEM;
> + dev->id = dev_desc->id;
> + dev->drv_name = mikrobus_string_get(info, dev_desc->driver_stringid);
> + dev->protocol = dev_desc->protocol;
> + dev->reg = dev_desc->reg;
> + dev->irq = dev_desc->irq;
> + dev->irq_type = dev_desc->irq_type;
> + dev->max_speed_hz = le32_to_cpu(dev_desc->max_speed_hz);
> + dev->mode = dev_desc->mode;
> + dev->cs_gpio = dev_desc->cs_gpio;
> + dev->num_gpio_resources = dev_desc->num_gpio_resources;
> + dev->num_properties = dev_desc->num_properties;
> + pr_info("device %d , number of properties=%d , number of gpio resources=%d\n",
> + dev->id, dev->num_properties, dev->num_gpio_resources);
> + if (dev->num_properties > 0) {
> + prop_link = mikrobus_property_link_get(info, dev_desc->prop_link,
> + MIKROBUS_PROPERTY_TYPE_LINK);
> + dev->properties = mikrobus_property_entry_get(info, prop_link, dev->num_properties);
> + }
> + if (dev->num_gpio_resources > 0) {
> + lookup = kzalloc(struct_size(lookup, table, dev->num_gpio_resources),
> + GFP_KERNEL);
> + if (!lookup)
> + return -ENOMEM;
> + gpio_desc_link = mikrobus_property_link_get(info, dev_desc->gpio_link,
> + MIKROBUS_PROPERTY_TYPE_GPIO);
> + for (i = 0; i < dev->num_gpio_resources; i++) {
> + list_for_each_entry(descriptor, &info->manifest_descs, links) {
> + if (descriptor->type != GREYBUS_TYPE_PROPERTY)
> + continue;
> + desc_property = descriptor->data;
> + if (desc_property->id == gpio_desc_link[i]) {
> + lookup->table[i].chip_hwnum = *desc_property->value;
> + lookup->table[i].con_id =
> + mikrobus_string_get(info, desc_property->propname_stringid);
> + break;
> + }
> + }
> + }
> + dev->gpio_lookup = lookup;
> + }
> + list_add_tail(&dev->links, &info->devices);
> + return 0;
> +}
> +
> +static int mikrobus_manifest_parse_devices(struct addon_board_info *info)
> +{
> + struct greybus_descriptor_device *desc_device;
> + struct manifest_desc *desc, *next;
> + int devcount = 0;
> +
> + if (WARN_ON(!list_empty(&info->devices)))
> + return false;
> + list_for_each_entry_safe(desc, next, &info->manifest_descs, links) {
> + if (desc->type != GREYBUS_TYPE_DEVICE)
> + continue;
> + desc_device = desc->data;
> + mikrobus_manifest_attach_device(info, desc_device);
> + devcount++;
> + }
> + return devcount;
> +}
> +
> +bool mikrobus_manifest_parse(struct addon_board_info *info, void *data,
> + size_t size)
> +{
> + struct greybus_manifest *manifest;
> + struct greybus_manifest_header *header;
> + struct greybus_descriptor *desc;
> + u16 manifest_size;
> + int dev_count;
> + int desc_size;
> +
> + if (WARN_ON(!list_empty(&info->manifest_descs)))
> + return false;
> + if (size < sizeof(*header))
> + return false;
> + manifest = data;
> + header = &manifest->header;
> + manifest_size = le16_to_cpu(header->size);
> + if (manifest_size != size)
> + return false;
> + if (header->version_major > MIKROBUS_VERSION_MAJOR)
> + return false;
> + desc = manifest->descriptors;
> + size -= sizeof(*header);
> + while (size) {
> + desc_size = identify_descriptor(info, desc, size);
> + if (desc_size < 0) {
> + pr_err("invalid manifest descriptor");
> + return -EINVAL;
> + }
> + desc = (struct greybus_descriptor *)((char *)desc + desc_size);
> + size -= desc_size;
> + }
> + mikrobus_state_get(info);
> + dev_count = mikrobus_manifest_parse_devices(info);
> + pr_info(" %s manifest parsed with %d device(s)\n", info->name,
> + info->num_devices);
> + release_manifest_descriptors(info);
> + return true;
> +}
> +
> +size_t mikrobus_manifest_header_validate(void *data, size_t size)
> +{
> + struct greybus_manifest_header *header;
> + u16 manifest_size;
> +
> + if (size < sizeof(*header))
> + return 0;
> +
> + header = data;
> + manifest_size = le16_to_cpu(header->size);
> + if (header->version_major > MIKROBUS_VERSION_MAJOR)
> + return 0;
> + return manifest_size;
> +}
> diff --git a/drivers/misc/mikrobus/mikrobus_manifest.h b/drivers/misc/mikrobus/mikrobus_manifest.h
> new file mode 100644
> index 000000000000..a195d1c26493
> --- /dev/null
> +++ b/drivers/misc/mikrobus/mikrobus_manifest.h
> @@ -0,0 +1,21 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * mikroBUS manifest definition
> + * extension to Greybus Manifest Definition
> + *
> + * Copyright 2014-2015 Google Inc.
> + * Copyright 2014-2015 Linaro Ltd.
> + *
> + * Released under the GPLv2 and BSD licenses.
> + */
> +
> +#ifndef __MIKROBUS_MANIFEST_H
> +#define __MIKROBUS_MANIFEST_H
> +
> +#include "mikrobus_core.h"
> +
> +bool mikrobus_manifest_parse(struct addon_board_info *info, void *data,
> + size_t size);
> +size_t mikrobus_manifest_header_validate(void *data, size_t size);
> +
> +#endif /* __MIKROBUS_MANIFEST_H */
> diff --git a/include/linux/greybus/greybus_manifest.h b/include/linux/greybus/greybus_manifest.h
> index 6e62fe478712..79c8cab9ef96 100644
> --- a/include/linux/greybus/greybus_manifest.h
> +++ b/include/linux/greybus/greybus_manifest.h
> @@ -23,6 +23,9 @@ enum greybus_descriptor_type {
> GREYBUS_TYPE_STRING = 0x02,
> GREYBUS_TYPE_BUNDLE = 0x03,
> GREYBUS_TYPE_CPORT = 0x04,
> + GREYBUS_TYPE_MIKROBUS = 0x05,
> + GREYBUS_TYPE_PROPERTY = 0x06,
> + GREYBUS_TYPE_DEVICE = 0x07,
> };
>
> enum greybus_protocol {
> @@ -151,6 +154,53 @@ struct greybus_descriptor_cport {
> __u8 protocol_id; /* enum greybus_protocol */
> } __packed;
>
> +/*
> + * A mikrobus descriptor is used to describe the details
> + * about the add-on board connected to the mikrobus port.
> + * It describes the number of indivdual devices on the
> + * add-on board and the default states of the GPIOs
> + */
> +struct greybus_descriptor_mikrobus {
> + __u8 num_devices;
> + __u8 rst_gpio_state;
> + __u8 pwm_gpio_state;
> + __u8 int_gpio_state;
> +} __packed;
> +
> +/*
> + * A property descriptor is used to pass named properties
> + * to device drivers through the unified device properties
> + * interface under linux/property.h
> + */
> +struct greybus_descriptor_property {
> + __u8 length;
> + __u8 id;
> + __u8 propname_stringid;
> + __u8 type;
> + __u8 value[0];
> +} __packed;
> +
> +/*
> + * A device descriptor is used to describe the
> + * details required by a add-on board device
> + * driver.
> + */
> +struct greybus_descriptor_device {
> + __u8 id;
> + __u8 driver_stringid;
> + __u8 num_properties;
> + __u8 protocol;
> + __le32 max_speed_hz;
> + __u8 reg;
> + __u8 mode;
> + __u8 num_gpio_resources;
> + __u8 cs_gpio;
> + __u8 irq;
> + __u8 irq_type;
> + __u8 prop_link;
> + __u8 gpio_link;
> +} __packed;
> +
> struct greybus_descriptor_header {
> __le16 size;
> __u8 type; /* enum greybus_descriptor_type */
> @@ -164,6 +214,9 @@ struct greybus_descriptor {
> struct greybus_descriptor_interface interface;
> struct greybus_descriptor_bundle bundle;
> struct greybus_descriptor_cport cport;
> + struct greybus_descriptor_mikrobus mikrobus;
> + struct greybus_descriptor_property property;
> + struct greybus_descriptor_device device;
> };
> } __packed;
>
>
_______________________________________________
greybus-dev mailing list
greybus-dev@xxxxxxxxxxxxxxxx
https://lists.linaro.org/mailman/listinfo/greybus-dev
_______________________________________________ greybus-dev mailing list greybus-dev@xxxxxxxxxxxxxxxx https://lists.linaro.org/mailman/listinfo/greybus-dev