Re: [PATCH] RFC : mikroBUS driver for add-on boards

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Sun, Jul 26, 2020 at 6:18 PM Alex Elder <elder@xxxxxxxxxx> wrote:
>
> 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.
>
Thank You, Alex, for your review and support, I have gone through
your initial code review and will make the changes as you
suggested.

> 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.
>
I am not exactly sure if there exists a single publicly available
document that gives an overview of mikrobus ports being
used over Greybus. under https://elinux.org/Mikrobus some
initial discussions and proposals are added.

> 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?
>
Yes, it should be possible to use a Beaglebone Black with a mikroBUS
cape for testing, the config array passed to the add_port will change,
this should be the values for the mikrobus port 1 on the mikroBUS cape
printf "%b" '\x02\x01\x00\x3c\x32\x30' > /sys/bus/mikrobus/add_port

I understand that this is not a proper way to pass the mikrobus-port
parameters, in the actual submission this information will be from
a corresponding device tree overlay fragment.

The status of different add-on boards supported is currently updated
here: https://github.com/vaishnav98/manifesto/wiki/click_info and the
corresponding manifests are available here :
https://github.com/vaishnav98/manifesto/tree/mikrobusv2

> 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?
>
The main reason for Greybus manifest to be extended is to describe
devices on I2C, SPI, UART behind a greybus device when there
is a need to bind an existing kernel driver for the device, the need for
this does not arise with the Project Ara modules, since they had
a firmware on the module, but a mikrobus add-on board is a
simple device which can contain one or more sensors, displays
or other communication modules and only the GBPHY class and
a subset of protocols (GPIO,I2C,SPI,UART,PWM)  are relevant to
the mikroBUS.


> 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)?
>
Yes, the plan is to have the real driver code to adhere to the
a newer version of the spec, the main thing that will change
is the mechanism of fetching the manifest binary from a
non-volatile storage on-board the add-on board. Currently
an I2C EEPROM on a default address(0x57) is used to fetch
the manifest binary, but this method will not work in cases like
the Beaglebone Black mikrobus cape where the I2C bus for
all the 4 different mikrobus port will be the same, this is the
major change expected.
> 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 = (char *)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





[Index of Archives]     [Asterisk App Development]     [PJ SIP]     [Gnu Gatekeeper]     [IETF Sipping]     [Info Cyrus]     [ALSA User]     [Fedora Linux Users]     [Linux SCTP]     [DCCP]     [Gimp]     [Yosemite News]     [Deep Creek Hot Springs]     [Yosemite Campsites]     [ISDN Cause Codes]     [Asterisk Books]

  Powered by Linux