diff --git a/MAINTAINERS b/MAINTAINERS index d3be0808e00e1b..fd0f5723312426 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11404,6 +11404,12 @@ M: Oliver Neukum S: Maintained F: drivers/usb/image/microtek.* +MIKROBUS ADDON BOARD DRIVER +M: Vaishnav M A +S: Maintained +W: https://elinux.org/Mikrobus +F: drivers/misc/mikrobus/ + MIPS M: Thomas Bogendoerfer L: linux-mips@vger.kernel.org diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 1a11e1ca9ab428..aacb32257bea21 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 c7bd01ac62917f..45486dd77da525 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 00000000000000..c3b93e12daad33 --- /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 00000000000000..1f80ed4064d8b8 --- /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 00000000000000..78c96c63763243 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +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 00000000000000..9684d315f564a9 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 00000000000000..60ebca560f0dc6 --- /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 +#include +#include +#include + +#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 00000000000000..a195d1c264930f --- /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 6e62fe47871223..79c8cab9ef9643 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;