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