#define DRIVER_VERSION "v4.1.2" #define DRIVER_AUTHOR "Multi-Tech" #define DRIVER_DESC "MTS driver to supervise MTAC slots" #define DRIVER_NAME "mtac-slots" #define DEBUG 0 #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. */ log_debug("mtac_clear_port_pins: State of mtac mutex is %s", 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))) { gpio_free(pin->pin.gpio); } } mtac_port_info[port_index]->gpio_pins = NULL; log_debug("Unlock mtac_mutex"); 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) { log_debug("mtac_set_port_pins: State of mtac mutex is %s", 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) { log_error("kzalloc of attribute [%s] failed", _name); return NULL; } sysfs_attr_init(_attr); attr_name = kstrdup(_name, GFP_KERNEL); if (! attr_name) { log_error("GFP_KERNEL dup failed for attribute [%s]", _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) { log_error("kobject->name is NULL"); return -1; } if (sscanf(name, "ap%d", &port) < 1) { log_error("failed to scan port from kobject->name [%s]", name); return -1; } if (port < 1 || port > NUM_AP) { log_error("port number %d is invalid", 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) { log_error("mtac_port_from_kobject returned %d", 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 { log_error("attribute [%s] not found", 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) { log_error("failed to create attribute [%s] in port %d", 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) { log_error("failed to create attribute [%s] in port %d", 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) { log_error("failed to create attribute [%s] in port %d", 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) { log_error("failed to create attribute [%s] in port %d", 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; log_debug("mtac_gpio_pin_by_attr_name: State of mtac mutex is %s", 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); log_error("pin with attr name [%s] not found", 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) { log_error("mtac_port_from_kobject returned %d", port); return -EINVAL; } pin = mtac_gpio_pin_by_attr_name(attr->attr.name, port); if (!pin) { return -ENODEV; } log_debug("mtac_attr_show_ap_gpio_pin: State of mtac mutex is %s", mutex_is_locked(&mtac_mutex) ? "locked" : "unlocked"); mutex_lock(&mtac_mutex); 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) { log_error("mtac_port_from_kobject returned %d", 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; } log_debug("mtac_attr_store_ap_gpio_pin: State of mtac mutex is %s", mutex_is_locked(&mtac_mutex) ? "locked" : "unlocked"); mutex_lock(&mtac_mutex); 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)) log_error("display_port: Must always hold the mtac_mutex here, but mutex was not locked"); app = (struct mts_ap_eeprom_layout *)mts_ap_eeprom[port_index]; log_info("accessory card %d vendor-id: %.32s", port, app->vendor_id); log_info("accessory card %d product-id: %.32s", port, app->product_id); log_info("accessory card %d device-id: %.32s", port, app->device_id); log_info("accessory card %d hw-version: %.32s", 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))) { log_debug("Request name:%s label: %s pin: %d", pin->name, pin->pin.label, pin->pin.gpio); ret = gpio_request_one(pin->pin.gpio, pin->pin.flags, pin->pin.label); if (ret) log_debug("could not request pin %s (%d) but it could have already been requested under a different pin name", pin->name, ret); } } } #include struct gpio_pin *mtac_gpio_pin_by_name(const char *name, int port_index) { struct gpio_pin *pin; log_debug("mtac_gpio_pin_by_name: State of mtac mutex is %s", 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); log_error("pin named %s not found", 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; log_debug("mtac_gpio_pin_by_num: State of mtac mutex is %s", 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); log_error("pin numbered %u not found", 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; log_debug("mtac_find enter"); for (port_index = 0; port_index < NUM_AP; port_index++) { log_debug("mtac_find: port_index: %d State of mtac mutex is %s", port_index, mutex_is_locked(&mtac_mutex) ? "locked" : "unlocked"); mutex_lock(&mtac_mutex); if (mtac_port_info[port_index] != NULL) { log_debug("Accessory Port %d of %d is in use",port_index+1,NUM_AP); mutex_unlock(&mtac_mutex); continue; } app = (struct mts_ap_eeprom_layout *)mts_ap_eeprom[port_index]; log_debug("target_product_id=%s, eeprom=%s",target_product_id,app->product_id); if (mts_ap_eeprom[port_index][0] == 0xFF) { log_info("uninitialized eeprom on accessory card %d", port_index); mutex_unlock(&mtac_mutex); continue; } else if (mts_ap_eeprom[port_index][0] == 0x0) { log_debug("no accessory card inserted in port_index %d", port_index); mutex_unlock(&mtac_mutex); continue; } else if (strstr(app->product_id, target_product_id)) { log_debug("strstr matches"); mtac_port_info[port_index] = kzalloc(sizeof(struct ap_info), GFP_KERNEL); if (! mtac_port_info[port_index]) { log_error("alloc of port info failed"); mutex_unlock(&mtac_mutex); return -ENOSPC; } set_info(mtac_port_info[port_index]); if (! mtac_port_info[port_index]->setup(port_index+1)) { log_error("accessory port %d setup failed", 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++; log_debug("slot_count=%d",slot_count); } } else log_debug("len prod_id: %d eeprom pr id: %d",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; log_debug("mtac_free enter"); 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++; log_debug("Free port %d product_id: %s mtac_port_info: %p", port_index + 1, product_id,mtac_port_info[port_index]); if(mtac_port_info[port_index]) log_debug("setup: %p setup ptr: %p",mtac_port_info[port_index]->setup,setup); if (mtac_port_info[port_index] && (mtac_port_info[port_index]->setup == setup)) { log_debug("port_index %d is occupied by us, teardown next",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; log_debug("init: " 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])); log_info("EEPROM contents loaded (%s)", eeprom_file_name[port_index]); } else { log_error("EEPROM invalid size (%s:%d)", eeprom_file_name[port_index], fw->size); } release_firmware(fw); } else { log_error("EEPROM unable to read (%s:%d)", 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) log_info("no accessory card inserted in port %d", port_index+1); else display_port(port_index); } mutex_unlock(&mtac_mutex); return 0; } static void __exit mtac_exit(void) { log_info("exiting"); } module_init(mtac_init); module_exit(mtac_exit); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_VERSION(DRIVER_VERSION); MODULE_LICENSE("GPL");