#define DRIVER_VERSION "v4.1.5" #define DRIVER_AUTHOR "Multi-Tech" #define DRIVER_DESC "MTS driver to supervise MTAC slots" #define DRIVER_NAME "mtac-slots" #include #include #include #include #include #include #include #include #include #include #include "mtac.h" /* accessory card EEPROMs, read outside of driver */ uint8_t mts_ap_eeprom[NUM_AP][MTS_AP_EEPROM_SIZE]; EXPORT_SYMBOL(mts_ap_eeprom); static const char* eeprom_file_name[NUM_AP] = { #if NUM_AP > 0 #ifdef mtcdt "1-0050/eeprom", "1-0052/eeprom" #endif #ifdef mtcpmhs "2-0050/eeprom", "2-0052/eeprom" #endif #endif // NUM_AP > 0 }; /* info for accessory port (contains function pointers for setup and teardown, * gpio pin list for the device inserted into the port. */ struct ap_info* mtac_port_info[NUM_AP]; EXPORT_SYMBOL(mtac_port_info); /* Formerly port_info */ /* Protect the mtac port info structure */ DEFINE_MUTEX(mtac_mutex); EXPORT_SYMBOL(mtac_mutex); void mtac_clear_port_pins(int port_index) { char buf[32]; struct gpio_pin *pin; struct gpio_pin *pins; snprintf(buf,sizeof buf,"AP%d_",port_index+1); /* Find all the GPIO pins for this port and * free them all. */ dev_dbg(&mts_io_platform_device->dev,"mtac_clear_port_pins: State of mtac mutex is %s\n", mutex_is_locked(&mtac_mutex) ? "locked" : "unlocked"); mutex_lock(&mtac_mutex); pins = mtac_port_info[port_index]->gpio_pins; for (pin = pins; *pin->name; pin++) { if (!memcmp(pin->name,buf,strlen(buf))) { if (pin->do_gpio_desc) { gpiod_put(pin->desc); } else { gpio_free(pin->pin.gpio); } } } mtac_port_info[port_index]->gpio_pins = NULL; dev_dbg(&mts_io_platform_device->dev,"Unlock mtac_mutex\n"); mutex_unlock(&mtac_mutex); } EXPORT_SYMBOL(mtac_clear_port_pins); void mtac_set_port_pins(int port_index, struct gpio_pin *pins, struct kobject *subdir) { dev_dbg(&mts_io_platform_device->dev,"mtac_set_port_pins: State of mtac mutex is %s\n", mutex_is_locked(&mtac_mutex) ? "locked" : "unlocked"); mtac_port_info[port_index]->gpio_pins = pins; mtac_port_info[port_index]->subdirs = subdir; } EXPORT_SYMBOL(mtac_set_port_pins); struct kobj_attribute* mtac_create_attribute(const char* _name, umode_t _mode) { char* attr_name; struct kobj_attribute* _attr; _attr = kzalloc(sizeof(struct kobj_attribute), GFP_KERNEL); if (! _attr) { dev_err(&mts_io_platform_device->dev,"kzalloc of attribute [%s] failed\n", _name); return NULL; } sysfs_attr_init(_attr); attr_name = kstrdup(_name, GFP_KERNEL); if (! attr_name) { dev_err(&mts_io_platform_device->dev,"GFP_KERNEL dup failed for attribute [%s]\n", _name); return NULL; } _attr->attr.name = attr_name; _attr->attr.mode = _mode; return _attr; } EXPORT_SYMBOL(mtac_create_attribute); int mtac_port_from_kobject(struct kobject *kobj) { int port; const char *name; name = kobj->name; if (! name) { dev_err(&mts_io_platform_device->dev,"kobject->name is NULL\n"); return -1; } if (sscanf(name, "ap%d", &port) < 1) { dev_err(&mts_io_platform_device->dev,"failed to scan port from kobject->name [%s]\n", name); return -1; } if (port < 1 || port > NUM_AP) { dev_err(&mts_io_platform_device->dev,"port number %d is invalid\n", port); return -1; } return port; } EXPORT_SYMBOL(mtac_port_from_kobject); /* * This function is not normally used directly by mtac modules. */ ssize_t mtac_show_product_info(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { ssize_t value; int port; int port_index; struct mts_ap_eeprom_layout *app; port = mtac_port_from_kobject(kobj); if (port < 1) { dev_err(&mts_io_platform_device->dev,"mtac_port_from_kobject returned %d\n", port); return -1; } port_index = port - 1; app = (struct mts_ap_eeprom_layout *)mts_ap_eeprom[port_index]; if (! strcmp(attr->attr.name, "vendor-id")) { value = snprintf(buf, 32, "%s\n", app->vendor_id); } else if (! strcmp(attr->attr.name, "product-id")) { value = snprintf(buf, 32, "%s\n", app->product_id); } else if (! strcmp(attr->attr.name, "device-id")) { value = snprintf(buf, 32, "%s\n", app->device_id); } else if (! strcmp(attr->attr.name, "hw-version")) { value = snprintf(buf, 32, "%s\n", app->hw_version); } else { dev_err(&mts_io_platform_device->dev,"attribute [%s] not found\n", attr->attr.name); value = -1; } return value; } EXPORT_SYMBOL(mtac_show_product_info); bool mtac_add_product_info_attributes(int port, struct attribute** attrs, int* index) { char buf[32]; struct kobj_attribute* kobj_attr; sprintf(buf, "vendor-id"); kobj_attr = mtac_create_attribute(buf, MTS_ATTR_MODE_RO); if (! kobj_attr) { dev_err(&mts_io_platform_device->dev,"failed to create attribute [%s] in port %d\n", buf, port); return false; } kobj_attr->show = mtac_show_product_info; attrs[(*index)++] = &kobj_attr->attr; sprintf(buf, "product-id"); kobj_attr = mtac_create_attribute(buf, MTS_ATTR_MODE_RO); if (! kobj_attr) { dev_err(&mts_io_platform_device->dev,"failed to create attribute [%s] in port %d\n", buf, port); return false; } kobj_attr->show = mtac_show_product_info; attrs[(*index)++] = &kobj_attr->attr; sprintf(buf, "device-id"); kobj_attr = mtac_create_attribute(buf, MTS_ATTR_MODE_RO); if (! kobj_attr) { dev_err(&mts_io_platform_device->dev,"failed to create attribute [%s] in port %d\n", buf, port); return false; } kobj_attr->show = mtac_show_product_info; attrs[(*index)++] = &kobj_attr->attr; sprintf(buf, "hw-version"); kobj_attr = mtac_create_attribute(buf, MTS_ATTR_MODE_RO); if (! kobj_attr) { dev_err(&mts_io_platform_device->dev,"failed to create attribute [%s] in port %d\n", buf, port); return false; } kobj_attr->show = mtac_show_product_info; attrs[(*index)++] = &kobj_attr->attr; return true; } EXPORT_SYMBOL(mtac_add_product_info_attributes); /* * This function is not normally used directly by mtac modules. */ struct gpio_pin *mtac_gpio_pin_by_attr_name(const char *name, int port) { struct gpio_pin *pin; char *pin_attr_name; int port_index = port - 1; struct gpio_pin *port_gpio_pins; dev_dbg(&mts_io_platform_device->dev,"mtac_gpio_pin_by_attr_name: State of mtac mutex is %s\n", mutex_is_locked(&mtac_mutex) ? "locked" : "unlocked"); mutex_lock(&mtac_mutex); pin_attr_name = mtac_port_info[port_index]->gpio_pin_name_by_attr_name(name, port); port_gpio_pins = mtac_port_info[port_index]->gpio_pins; for (pin = port_gpio_pins; *pin->name; pin++) { if (!strcmp(pin->pin.label, pin_attr_name)) { mutex_unlock(&mtac_mutex); return pin; } } mutex_unlock(&mtac_mutex); dev_err(&mts_io_platform_device->dev,"pin with attr name [%s] not found\n", name); return NULL; } EXPORT_SYMBOL(mtac_gpio_pin_by_attr_name); ssize_t mtac_attr_show_ap_gpio_pin(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int value; int port; struct gpio_pin *pin; port = mtac_port_from_kobject(kobj); if (port < 1) { dev_err(&mts_io_platform_device->dev,"mtac_port_from_kobject returned %d\n", port); return -EINVAL; } pin = mtac_gpio_pin_by_attr_name(attr->attr.name, port); if (!pin) { return -ENODEV; } dev_dbg(&mts_io_platform_device->dev,"mtac_attr_show_ap_gpio_pin: State of mtac mutex is %s\n", mutex_is_locked(&mtac_mutex) ? "locked" : "unlocked"); mutex_lock(&mtac_mutex); if (pin->do_gpio_desc) { value = gpiod_get_value(pin->desc); } else { value = gpio_get_value(pin->pin.gpio); } mutex_unlock(&mtac_mutex); if (value < 0) { return value; } if (pin->active_low) { value = !value; } return sprintf(buf, "%d\n", value); } EXPORT_SYMBOL(mtac_attr_show_ap_gpio_pin); ssize_t mtac_attr_store_ap_gpio_pin(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int value; int port; struct gpio_pin *pin; port = mtac_port_from_kobject(kobj); if (port < 1) { dev_err(&mts_io_platform_device->dev,"mtac_port_from_kobject returned %d\n", port); return -EINVAL; } pin = mtac_gpio_pin_by_attr_name(attr->attr.name, port); if (!pin) { return -ENODEV; } if (sscanf(buf, "%i", &value) != 1) { return -EINVAL; } if (pin->active_low) { value = !value; } dev_dbg(&mts_io_platform_device->dev,"mtac_attr_store_ap_gpio_pin: State of mtac mutex is %s\n", mutex_is_locked(&mtac_mutex) ? "locked" : "unlocked"); mutex_lock(&mtac_mutex); if (pin->do_gpio_desc) { gpiod_set_value(pin->desc, value); } else { gpio_set_value(pin->pin.gpio, value); } mutex_unlock(&mtac_mutex); return count; } EXPORT_SYMBOL(mtac_attr_store_ap_gpio_pin); static void display_port(int port_index) { int port = port_index + 1; struct mts_ap_eeprom_layout *app; /* Our caller has locked the mtac_mutex */ if(!mutex_is_locked(&mtac_mutex)) dev_err(&mts_io_platform_device->dev,"display_port: Must always hold the mtac_mutex here, but mutex was not locked\n"); app = (struct mts_ap_eeprom_layout *)mts_ap_eeprom[port_index]; dev_info(&mts_io_platform_device->dev,"accessory card %d vendor-id: %.32s\n", port, app->vendor_id); dev_info(&mts_io_platform_device->dev,"accessory card %d product-id: %.32s\n", port, app->product_id); dev_info(&mts_io_platform_device->dev,"accessory card %d device-id: %.32s\n", port, app->device_id); dev_info(&mts_io_platform_device->dev,"accessory card %d hw-version: %.32s\n", port, app->hw_version); } static void acquire_gpio(struct gpio_pin *pins, int port_index) { char buf[32]; struct gpio_pin *pin; int ret; snprintf(buf,sizeof buf,"AP%d_",port_index+1); for (pin = pins; *pin->name; pin++) { if (!memcmp(pin->name,buf,strlen(buf))) { if (pin->do_gpio_desc) { dev_dbg(&mts_io_platform_device->dev,"Request name:%s label: %s\n", pin->name, pin->pin.label); pin->desc = devm_gpiod_get_optional(&mts_io_platform_device->dev, pin->name, pin->pin.flags); if (IS_ERR(pin->desc)) { dev_dbg(&mts_io_platform_device->dev, "%s: Could not get gpio %s: Error: %ld\n", __func__, pin->name, PTR_ERR(pin->desc)); } else { if (pin->desc == NULL) dev_dbg(&mts_io_platform_device->dev,"gpio_desc is null for name: %s, label: %s\n\n", pin->name, pin->pin.label); else dev_dbg(&mts_io_platform_device->dev,"Found gpio %s\n\n", pin->name); } } else { dev_dbg(&mts_io_platform_device->dev,"Request name:%s label: %s pin: %d\n", pin->name, pin->pin.label, pin->pin.gpio); ret = gpio_request_one(pin->pin.gpio, pin->pin.flags, pin->pin.label); if (ret) dev_dbg(&mts_io_platform_device->dev,"could not request pin %s (%d) but it could have already been requested under a different pin name\n", pin->name, ret); } } } } #include struct gpio_pin *mtac_gpio_pin_by_name(const char *name, int port_index) { struct gpio_pin *pin; dev_dbg(&mts_io_platform_device->dev,"mtac_gpio_pin_by_name: State of mtac mutex is %s\n", mutex_is_locked(&mtac_mutex) ? "locked" : "unlocked"); mutex_lock(&mtac_mutex); for (pin = mtac_port_info[port_index]->gpio_pins; *pin->name; pin++) { if (!strcmp(pin->name, name)) { mutex_unlock(&mtac_mutex); return pin; } } mutex_unlock(&mtac_mutex); dev_err(&mts_io_platform_device->dev,"pin named %s not found\n", name); return NULL; } EXPORT_SYMBOL(mtac_gpio_pin_by_name); /* static gpio_pins */ // A GPIO pin number must only occur once. struct gpio_pin *mtac_gpio_pin_by_num(unsigned num, int port_index) { int ipin = 0; struct gpio_pin *port_gpio_pins; dev_dbg(&mts_io_platform_device->dev,"mtac_gpio_pin_by_num: State of mtac mutex is %s\n", mutex_is_locked(&mtac_mutex) ? "locked" : "unlocked"); mutex_lock(&mtac_mutex); port_gpio_pins = mtac_port_info[port_index]->gpio_pins; while(*(port_gpio_pins[ipin].name)) { if (port_gpio_pins[ipin].pin.gpio == num) { mutex_unlock(&mtac_mutex); return &(port_gpio_pins[ipin]); } ipin++; } mutex_unlock(&mtac_mutex); dev_err(&mts_io_platform_device->dev,"pin numbered %u not found\n", num); return NULL; } EXPORT_SYMBOL(mtac_gpio_pin_by_num); /* * Depends on static gpio_pins */ int mtac_find(void(*set_info)(struct ap_info* info), const char *target_product_id) { int port_index; struct mts_ap_eeprom_layout *app; int slot_count = 0; dev_dbg(&mts_io_platform_device->dev,"mtac_find enter\n"); for (port_index = 0; port_index < NUM_AP; port_index++) { dev_dbg(&mts_io_platform_device->dev,"mtac_find: port_index: %d State of mtac mutex is %s\n", port_index, mutex_is_locked(&mtac_mutex) ? "locked" : "unlocked"); mutex_lock(&mtac_mutex); if (mtac_port_info[port_index] != NULL) { dev_dbg(&mts_io_platform_device->dev,"Accessory Port %d of %d is in use\n",port_index+1,NUM_AP); mutex_unlock(&mtac_mutex); continue; } app = (struct mts_ap_eeprom_layout *)mts_ap_eeprom[port_index]; dev_dbg(&mts_io_platform_device->dev,"target_product_id=%s, eeprom=%s\n",target_product_id,app->product_id); if (mts_ap_eeprom[port_index][0] == 0xFF) { dev_info(&mts_io_platform_device->dev,"uninitialized eeprom on accessory card %d\n", port_index); mutex_unlock(&mtac_mutex); continue; } else if (mts_ap_eeprom[port_index][0] == 0x0) { dev_dbg(&mts_io_platform_device->dev,"no accessory card inserted in port_index %d\n", port_index); mutex_unlock(&mtac_mutex); continue; } else if (strstr(app->product_id, target_product_id)) { dev_dbg(&mts_io_platform_device->dev,"strstr matches\n"); mtac_port_info[port_index] = kzalloc(sizeof(struct ap_info), GFP_KERNEL); if (! mtac_port_info[port_index]) { dev_err(&mts_io_platform_device->dev,"alloc of port info failed\n"); mutex_unlock(&mtac_mutex); return -ENOSPC; } set_info(mtac_port_info[port_index]); if (! mtac_port_info[port_index]->setup(port_index+1)) { dev_err(&mts_io_platform_device->dev,"accessory port %d setup failed\n", port_index+1); mtac_port_info[port_index]->teardown(port_index+1); kfree(mtac_port_info[port_index]); mtac_port_info[port_index] = NULL; mutex_unlock(&mtac_mutex); return -ENOSPC; } else { acquire_gpio(mtac_port_info[port_index]->gpio_pins,port_index); slot_count++; dev_dbg(&mts_io_platform_device->dev,"slot_count=%d\n",slot_count); } } else dev_dbg(&mts_io_platform_device->dev,"len prod_id: %d eeprom pr id: %d\n",strlen(target_product_id),strlen(app->product_id)); mutex_unlock(&mtac_mutex); } return slot_count; } EXPORT_SYMBOL(mtac_find); void mtac_free(const char *product_id, bool(* setup)(enum ap port), const char *link) { int port_index, count, port; char buf[32]; struct mts_ap_eeprom_layout *app; dev_dbg(&mts_io_platform_device->dev,"mtac_free enter\n"); count = 0; for (port_index = 0; port_index < NUM_AP; port_index++) { port = port_index + 1; app = (struct mts_ap_eeprom_layout *)mts_ap_eeprom[port_index]; if (app && strstr(app->product_id, product_id)) { count++; dev_dbg(&mts_io_platform_device->dev,"Free port %d product_id: %s mtac_port_info: %p\n", port_index + 1, product_id,mtac_port_info[port_index]); if(mtac_port_info[port_index]) dev_dbg(&mts_io_platform_device->dev,"setup: %p setup ptr: %p\n",mtac_port_info[port_index]->setup,setup); if (mtac_port_info[port_index] && (mtac_port_info[port_index]->setup == setup)) { dev_dbg(&mts_io_platform_device->dev,"port_index %d is occupied by us, teardown next\n",port_index); if (count > 1) { sprintf(buf, "%s-%d",link,port); } else { sprintf(buf, "%s",link); } sysfs_remove_link(mtac_port_info[port_index]->subdirs->parent, buf); mtac_port_info[port_index]->teardown(port_index+1); kfree(mtac_port_info[port_index]); mtac_port_info[port_index] = NULL; // Slot available } // MTAC Slot array is in use by our module } // Slot EEPROM says it is occupied by our module } // Loop through the ports } EXPORT_SYMBOL(mtac_free); /* * Display the EEPROM for all the slots. */ static int __init mtac_init(void) { int port_index; const struct firmware* fw = NULL; int ret; dev_dbg(&mts_io_platform_device->dev,"init: \n" DRIVER_VERSION); mutex_lock(&mtac_mutex); for (port_index = 0; port_index < NUM_AP; port_index++) { mts_ap_eeprom[port_index][0] = 0x00; /* initialize to not present */ if((ret = request_firmware_direct(&fw, eeprom_file_name[port_index], &mts_io_platform_device->dev)) == 0) { if(fw->size == sizeof(mts_ap_eeprom[0])) { memcpy(mts_ap_eeprom[port_index], fw->data, sizeof(mts_ap_eeprom[0])); dev_info(&mts_io_platform_device->dev,"EEPROM contents loaded (%s)\n", eeprom_file_name[port_index]); } else { dev_err(&mts_io_platform_device->dev,"EEPROM invalid size (%s:%d)\n", eeprom_file_name[port_index], fw->size); } release_firmware(fw); } else { dev_err(&mts_io_platform_device->dev,"EEPROM unable to read (%s:%d)\n", eeprom_file_name[port_index], ret); } if (mts_ap_eeprom[port_index][0] == 0xFF) log_alert("uninitialized eeprom on accessory card %d", port_index); else if (mts_ap_eeprom[port_index][0] == 0x0) dev_info(&mts_io_platform_device->dev,"no accessory card inserted in port %d\n", port_index+1); else display_port(port_index); } mutex_unlock(&mtac_mutex); return 0; } static void __exit mtac_exit(void) { dev_info(&mts_io_platform_device->dev,"exiting\n"); } module_init(mtac_init); module_exit(mtac_exit); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_VERSION(DRIVER_VERSION); MODULE_LICENSE("GPL");