diff options
Diffstat (limited to 'drivers')
26 files changed, 1905 insertions, 308 deletions
diff --git a/drivers/base/core.c b/drivers/base/core.c index 5e6e00bc165..abea76c36a4 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -184,6 +184,17 @@ static void device_release(struct kobject *kobj) struct device *dev = kobj_to_dev(kobj); struct device_private *p = dev->p; + /* + * Some platform devices are driven without driver attached + * and managed resources may have been acquired. Make sure + * all resources are released. + * + * Drivers still can add resources into device after device + * is deleted but alive, so release devres here to avoid + * possible memory leak. + */ + devres_release_all(dev); + if (dev->release) dev->release(dev); else if (dev->type && dev->type->release) @@ -1196,13 +1207,6 @@ void device_del(struct device *dev) bus_remove_device(dev); driver_deferred_probe_del(dev); - /* - * Some platform devices are driven without driver attached - * and managed resources may have been acquired. Make sure - * all resources are released. - */ - devres_release_all(dev); - /* Notify the platform of the removal, in case they * need to do anything... */ @@ -1861,26 +1865,20 @@ void device_shutdown(void) */ #ifdef CONFIG_PRINTK -int __dev_printk(const char *level, const struct device *dev, - struct va_format *vaf) +static int +create_syslog_header(const struct device *dev, char *hdr, size_t hdrlen) { - char dict[128]; - const char *level_extra = ""; - size_t dictlen = 0; const char *subsys; - - if (!dev) - return printk("%s(NULL device *): %pV", level, vaf); + size_t pos = 0; if (dev->class) subsys = dev->class->name; else if (dev->bus) subsys = dev->bus->name; else - goto skip; + return 0; - dictlen += snprintf(dict + dictlen, sizeof(dict) - dictlen, - "SUBSYSTEM=%s", subsys); + pos += snprintf(hdr + pos, hdrlen - pos, "SUBSYSTEM=%s", subsys); /* * Add device identifier DEVICE=: @@ -1896,32 +1894,63 @@ int __dev_printk(const char *level, const struct device *dev, c = 'b'; else c = 'c'; - dictlen++; - dictlen += snprintf(dict + dictlen, sizeof(dict) - dictlen, - "DEVICE=%c%u:%u", - c, MAJOR(dev->devt), MINOR(dev->devt)); + pos++; + pos += snprintf(hdr + pos, hdrlen - pos, + "DEVICE=%c%u:%u", + c, MAJOR(dev->devt), MINOR(dev->devt)); } else if (strcmp(subsys, "net") == 0) { struct net_device *net = to_net_dev(dev); - dictlen++; - dictlen += snprintf(dict + dictlen, sizeof(dict) - dictlen, - "DEVICE=n%u", net->ifindex); + pos++; + pos += snprintf(hdr + pos, hdrlen - pos, + "DEVICE=n%u", net->ifindex); } else { - dictlen++; - dictlen += snprintf(dict + dictlen, sizeof(dict) - dictlen, - "DEVICE=+%s:%s", subsys, dev_name(dev)); + pos++; + pos += snprintf(hdr + pos, hdrlen - pos, + "DEVICE=+%s:%s", subsys, dev_name(dev)); } -skip: - if (level[2]) - level_extra = &level[2]; /* skip past KERN_SOH "L" */ - return printk_emit(0, level[1] - '0', - dictlen ? dict : NULL, dictlen, - "%s %s: %s%pV", - dev_driver_string(dev), dev_name(dev), - level_extra, vaf); + return pos; +} +EXPORT_SYMBOL(create_syslog_header); + +int dev_vprintk_emit(int level, const struct device *dev, + const char *fmt, va_list args) +{ + char hdr[128]; + size_t hdrlen; + + hdrlen = create_syslog_header(dev, hdr, sizeof(hdr)); + + return vprintk_emit(0, level, hdrlen ? hdr : NULL, hdrlen, fmt, args); +} +EXPORT_SYMBOL(dev_vprintk_emit); + +int dev_printk_emit(int level, const struct device *dev, const char *fmt, ...) +{ + va_list args; + int r; + + va_start(args, fmt); + + r = dev_vprintk_emit(level, dev, fmt, args); + + va_end(args); + + return r; +} +EXPORT_SYMBOL(dev_printk_emit); + +static int __dev_printk(const char *level, const struct device *dev, + struct va_format *vaf) +{ + if (!dev) + return printk("%s(NULL device *): %pV", level, vaf); + + return dev_printk_emit(level[1] - '0', dev, + "%s %s: %pV", + dev_driver_string(dev), dev_name(dev), vaf); } -EXPORT_SYMBOL(__dev_printk); int dev_printk(const char *level, const struct device *dev, const char *fmt, ...) @@ -1936,6 +1965,7 @@ int dev_printk(const char *level, const struct device *dev, vaf.va = &args; r = __dev_printk(level, dev, &vaf); + va_end(args); return r; @@ -1955,6 +1985,7 @@ int func(const struct device *dev, const char *fmt, ...) \ vaf.va = &args; \ \ r = __dev_printk(kern_level, dev, &vaf); \ + \ va_end(args); \ \ return r; \ diff --git a/drivers/base/devres.c b/drivers/base/devres.c index 2360adb7a58..8731979d668 100644 --- a/drivers/base/devres.c +++ b/drivers/base/devres.c @@ -144,6 +144,48 @@ EXPORT_SYMBOL_GPL(devres_alloc); #endif /** + * devres_for_each_res - Resource iterator + * @dev: Device to iterate resource from + * @release: Look for resources associated with this release function + * @match: Match function (optional) + * @match_data: Data for the match function + * @fn: Function to be called for each matched resource. + * @data: Data for @fn, the 3rd parameter of @fn + * + * Call @fn for each devres of @dev which is associated with @release + * and for which @match returns 1. + * + * RETURNS: + * void + */ +void devres_for_each_res(struct device *dev, dr_release_t release, + dr_match_t match, void *match_data, + void (*fn)(struct device *, void *, void *), + void *data) +{ + struct devres_node *node; + struct devres_node *tmp; + unsigned long flags; + + if (!fn) + return; + + spin_lock_irqsave(&dev->devres_lock, flags); + list_for_each_entry_safe_reverse(node, tmp, + &dev->devres_head, entry) { + struct devres *dr = container_of(node, struct devres, node); + + if (node->release != release) + continue; + if (match && !match(dev, dr->data, match_data)) + continue; + fn(dev, dr->data, data); + } + spin_unlock_irqrestore(&dev->devres_lock, flags); +} +EXPORT_SYMBOL_GPL(devres_for_each_res); + +/** * devres_free - Free device resource data * @res: Pointer to devres data to free * diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 803cfc1597a..6e210802c37 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -21,6 +21,13 @@ #include <linux/firmware.h> #include <linux/slab.h> #include <linux/sched.h> +#include <linux/list.h> +#include <linux/async.h> +#include <linux/pm.h> +#include <linux/suspend.h> +#include <linux/syscore_ops.h> + +#include "base.h" MODULE_AUTHOR("Manuel Estrada Sainz"); MODULE_DESCRIPTION("Multi purpose firmware loading support"); @@ -85,23 +92,168 @@ static inline long firmware_loading_timeout(void) return loading_timeout > 0 ? loading_timeout * HZ : MAX_SCHEDULE_TIMEOUT; } -/* fw_lock could be moved to 'struct firmware_priv' but since it is just - * guarding for corner cases a global lock should be OK */ -static DEFINE_MUTEX(fw_lock); +struct firmware_cache { + /* firmware_buf instance will be added into the below list */ + spinlock_t lock; + struct list_head head; + int state; + +#ifdef CONFIG_PM_SLEEP + /* + * Names of firmware images which have been cached successfully + * will be added into the below list so that device uncache + * helper can trace which firmware images have been cached + * before. + */ + spinlock_t name_lock; + struct list_head fw_names; + + wait_queue_head_t wait_queue; + int cnt; + struct delayed_work work; + + struct notifier_block pm_notify; +#endif +}; -struct firmware_priv { +struct firmware_buf { + struct kref ref; + struct list_head list; struct completion completion; - struct firmware *fw; + struct firmware_cache *fwc; unsigned long status; + void *data; + size_t size; struct page **pages; int nr_pages; int page_array_size; + char fw_id[]; +}; + +struct fw_cache_entry { + struct list_head list; + char name[]; +}; + +struct firmware_priv { struct timer_list timeout; - struct device dev; bool nowait; - char fw_id[]; + struct device dev; + struct firmware_buf *buf; + struct firmware *fw; +}; + +struct fw_name_devm { + unsigned long magic; + char name[]; }; +#define to_fwbuf(d) container_of(d, struct firmware_buf, ref) + +#define FW_LOADER_NO_CACHE 0 +#define FW_LOADER_START_CACHE 1 + +static int fw_cache_piggyback_on_request(const char *name); + +/* fw_lock could be moved to 'struct firmware_priv' but since it is just + * guarding for corner cases a global lock should be OK */ +static DEFINE_MUTEX(fw_lock); + +static struct firmware_cache fw_cache; + +static struct firmware_buf *__allocate_fw_buf(const char *fw_name, + struct firmware_cache *fwc) +{ + struct firmware_buf *buf; + + buf = kzalloc(sizeof(*buf) + strlen(fw_name) + 1 , GFP_ATOMIC); + + if (!buf) + return buf; + + kref_init(&buf->ref); + strcpy(buf->fw_id, fw_name); + buf->fwc = fwc; + init_completion(&buf->completion); + + pr_debug("%s: fw-%s buf=%p\n", __func__, fw_name, buf); + + return buf; +} + +static struct firmware_buf *__fw_lookup_buf(const char *fw_name) +{ + struct firmware_buf *tmp; + struct firmware_cache *fwc = &fw_cache; + + list_for_each_entry(tmp, &fwc->head, list) + if (!strcmp(tmp->fw_id, fw_name)) + return tmp; + return NULL; +} + +static int fw_lookup_and_allocate_buf(const char *fw_name, + struct firmware_cache *fwc, + struct firmware_buf **buf) +{ + struct firmware_buf *tmp; + + spin_lock(&fwc->lock); + tmp = __fw_lookup_buf(fw_name); + if (tmp) { + kref_get(&tmp->ref); + spin_unlock(&fwc->lock); + *buf = tmp; + return 1; + } + tmp = __allocate_fw_buf(fw_name, fwc); + if (tmp) + list_add(&tmp->list, &fwc->head); + spin_unlock(&fwc->lock); + + *buf = tmp; + + return tmp ? 0 : -ENOMEM; +} + +static struct firmware_buf *fw_lookup_buf(const char *fw_name) +{ + struct firmware_buf *tmp; + struct firmware_cache *fwc = &fw_cache; + + spin_lock(&fwc->lock); + tmp = __fw_lookup_buf(fw_name); + spin_unlock(&fwc->lock); + + return tmp; +} + +static void __fw_free_buf(struct kref *ref) +{ + struct firmware_buf *buf = to_fwbuf(ref); + struct firmware_cache *fwc = buf->fwc; + int i; + + pr_debug("%s: fw-%s buf=%p data=%p size=%u\n", + __func__, buf->fw_id, buf, buf->data, + (unsigned int)buf->size); + + spin_lock(&fwc->lock); + list_del(&buf->list); + spin_unlock(&fwc->lock); + + vunmap(buf->data); + for (i = 0; i < buf->nr_pages; i++) + __free_page(buf->pages[i]); + kfree(buf->pages); + kfree(buf); +} + +static void fw_free_buf(struct firmware_buf *buf) +{ + kref_put(&buf->ref, __fw_free_buf); +} + static struct firmware_priv *to_firmware_priv(struct device *dev) { return container_of(dev, struct firmware_priv, dev); @@ -109,9 +261,10 @@ static struct firmware_priv *to_firmware_priv(struct device *dev) static void fw_load_abort(struct firmware_priv *fw_priv) { - set_bit(FW_STATUS_ABORT, &fw_priv->status); - wmb(); - complete(&fw_priv->completion); + struct firmware_buf *buf = fw_priv->buf; + + set_bit(FW_STATUS_ABORT, &buf->status); + complete_all(&buf->completion); } static ssize_t firmware_timeout_show(struct class *class, @@ -154,11 +307,7 @@ static struct class_attribute firmware_class_attrs[] = { static void fw_dev_release(struct device *dev) { struct firmware_priv *fw_priv = to_firmware_priv(dev); - int i; - for (i = 0; i < fw_priv->nr_pages; i++) - __free_page(fw_priv->pages[i]); - kfree(fw_priv->pages); kfree(fw_priv); module_put(THIS_MODULE); @@ -168,7 +317,7 @@ static int firmware_uevent(struct device *dev, struct kobj_uevent_env *env) { struct firmware_priv *fw_priv = to_firmware_priv(dev); - if (add_uevent_var(env, "FIRMWARE=%s", fw_priv->fw_id)) + if (add_uevent_var(env, "FIRMWARE=%s", fw_priv->buf->fw_id)) return -ENOMEM; if (add_uevent_var(env, "TIMEOUT=%i", loading_timeout)) return -ENOMEM; @@ -189,20 +338,16 @@ static ssize_t firmware_loading_show(struct device *dev, struct device_attribute *attr, char *buf) { struct firmware_priv *fw_priv = to_firmware_priv(dev); - int loading = test_bit(FW_STATUS_LOADING, &fw_priv->status); + int loading = test_bit(FW_STATUS_LOADING, &fw_priv->buf->status); return sprintf(buf, "%d\n", loading); } +/* firmware holds the ownership of pages */ static void firmware_free_data(const struct firmware *fw) { - int i; - vunmap(fw->data); - if (fw->pages) { - for (i = 0; i < PFN_UP(fw->size); i++) - __free_page(fw->pages[i]); - kfree(fw->pages); - } + WARN_ON(!fw->priv); + fw_free_buf(fw->priv); } /* Some architectures don't have PAGE_KERNEL_RO */ @@ -227,45 +372,33 @@ static ssize_t firmware_loading_store(struct device *dev, const char *buf, size_t count) { struct firmware_priv *fw_priv = to_firmware_priv(dev); + struct firmware_buf *fw_buf = fw_priv->buf; int loading = simple_strtol(buf, NULL, 10); int i; mutex_lock(&fw_lock); - if (!fw_priv->fw) + if (!fw_buf) goto out; switch (loading) { case 1: - firmware_free_data(fw_priv->fw); - memset(fw_priv->fw, 0, sizeof(struct firmware)); - /* If the pages are not owned by 'struct firmware' */ - for (i = 0; i < fw_priv->nr_pages; i++) - __free_page(fw_priv->pages[i]); - kfree(fw_priv->pages); - fw_priv->pages = NULL; - fw_priv->page_array_size = 0; - fw_priv->nr_pages = 0; - set_bit(FW_STATUS_LOADING, &fw_priv->status); + /* discarding any previous partial load */ + if (!test_bit(FW_STATUS_DONE, &fw_buf->status)) { + for (i = 0; i < fw_buf->nr_pages; i++) + __free_page(fw_buf->pages[i]); + kfree(fw_buf->pages); + fw_buf->pages = NULL; + fw_buf->page_array_size = 0; + fw_buf->nr_pages = 0; + set_bit(FW_STATUS_LOADING, &fw_buf->status); + } break; case 0: - if (test_bit(FW_STATUS_LOADING, &fw_priv->status)) { - vunmap(fw_priv->fw->data); - fw_priv->fw->data = vmap(fw_priv->pages, - fw_priv->nr_pages, - 0, PAGE_KERNEL_RO); - if (!fw_priv->fw->data) { - dev_err(dev, "%s: vmap() failed\n", __func__); - goto err; - } - /* Pages are now owned by 'struct firmware' */ - fw_priv->fw->pages = fw_priv->pages; - fw_priv->pages = NULL; - - fw_priv->page_array_size = 0; - fw_priv->nr_pages = 0; - complete(&fw_priv->completion); - clear_bit(FW_STATUS_LOADING, &fw_priv->status); + if (test_bit(FW_STATUS_LOADING, &fw_buf->status)) { + set_bit(FW_STATUS_DONE, &fw_buf->status); + clear_bit(FW_STATUS_LOADING, &fw_buf->status); + complete_all(&fw_buf->completion); break; } /* fallthrough */ @@ -273,7 +406,6 @@ static ssize_t firmware_loading_store(struct device *dev, dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading); /* fallthrough */ case -1: - err: fw_load_abort(fw_priv); break; } @@ -290,21 +422,21 @@ static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj, { struct device *dev = kobj_to_dev(kobj); struct firmware_priv *fw_priv = to_firmware_priv(dev); - struct firmware *fw; + struct firmware_buf *buf; ssize_t ret_count; mutex_lock(&fw_lock); - fw = fw_priv->fw; - if (!fw || test_bit(FW_STATUS_DONE, &fw_priv->status)) { + buf = fw_priv->buf; + if (!buf || test_bit(FW_STATUS_DONE, &buf->status)) { ret_count = -ENODEV; goto out; } - if (offset > fw->size) { + if (offset > buf->size) { ret_count = 0; goto out; } - if (count > fw->size - offset) - count = fw->size - offset; + if (count > buf->size - offset) + count = buf->size - offset; ret_count = count; @@ -314,11 +446,11 @@ static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj, int page_ofs = offset & (PAGE_SIZE-1); int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count); - page_data = kmap(fw_priv->pages[page_nr]); + page_data = kmap(buf->pages[page_nr]); memcpy(buffer, page_data + page_ofs, page_cnt); - kunmap(fw_priv->pages[page_nr]); + kunmap(buf->pages[page_nr]); buffer += page_cnt; offset += page_cnt; count -= page_cnt; @@ -330,12 +462,13 @@ out: static int fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size) { + struct firmware_buf *buf = fw_priv->buf; int pages_needed = ALIGN(min_size, PAGE_SIZE) >> PAGE_SHIFT; /* If the array of pages is too small, grow it... */ - if (fw_priv->page_array_size < pages_needed) { + if (buf->page_array_size < pages_needed) { int new_array_size = max(pages_needed, - fw_priv->page_array_size * 2); + buf->page_array_size * 2); struct page **new_pages; new_pages = kmalloc(new_array_size * sizeof(void *), @@ -344,24 +477,24 @@ static int fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size) fw_load_abort(fw_priv); return -ENOMEM; } - memcpy(new_pages, fw_priv->pages, - fw_priv->page_array_size * sizeof(void *)); - memset(&new_pages[fw_priv->page_array_size], 0, sizeof(void *) * - (new_array_size - fw_priv->page_array_size)); - kfree(fw_priv->pages); - fw_priv->pages = new_pages; - fw_priv->page_array_size = new_array_size; + memcpy(new_pages, buf->pages, + buf->page_array_size * sizeof(void *)); + memset(&new_pages[buf->page_array_size], 0, sizeof(void *) * + (new_array_size - buf->page_array_size)); + kfree(buf->pages); + buf->pages = new_pages; + buf->page_array_size = new_array_size; } - while (fw_priv->nr_pages < pages_needed) { - fw_priv->pages[fw_priv->nr_pages] = + while (buf->nr_pages < pages_needed) { + buf->pages[buf->nr_pages] = alloc_page(GFP_KERNEL | __GFP_HIGHMEM); - if (!fw_priv->pages[fw_priv->nr_pages]) { + if (!buf->pages[buf->nr_pages]) { fw_load_abort(fw_priv); return -ENOMEM; } - fw_priv->nr_pages++; + buf->nr_pages++; } return 0; } @@ -384,18 +517,19 @@ static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj, { struct device *dev = kobj_to_dev(kobj); struct firmware_priv *fw_priv = to_firmware_priv(dev); - struct firmware *fw; + struct firmware_buf *buf; ssize_t retval; if (!capable(CAP_SYS_RAWIO)) return -EPERM; mutex_lock(&fw_lock); - fw = fw_priv->fw; - if (!fw || test_bit(FW_STATUS_DONE, &fw_priv->status)) { + buf = fw_priv->buf; + if (!buf || test_bit(FW_STATUS_DONE, &buf->status)) { retval = -ENODEV; goto out; } + retval = fw_realloc_buffer(fw_priv, offset + count); if (retval) goto out; @@ -408,17 +542,17 @@ static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj, int page_ofs = offset & (PAGE_SIZE - 1); int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count); - page_data = kmap(fw_priv->pages[page_nr]); + page_data = kmap(buf->pages[page_nr]); memcpy(page_data + page_ofs, buffer, page_cnt); - kunmap(fw_priv->pages[page_nr]); + kunmap(buf->pages[page_nr]); buffer += page_cnt; offset += page_cnt; count -= page_cnt; } - fw->size = max_t(size_t, offset, fw->size); + buf->size = max_t(size_t, offset, buf->size); out: mutex_unlock(&fw_lock); return retval; @@ -445,35 +579,120 @@ fw_create_instance(struct firmware *firmware, const char *fw_name, struct firmware_priv *fw_priv; struct device *f_dev; - fw_priv = kzalloc(sizeof(*fw_priv) + strlen(fw_name) + 1 , GFP_KERNEL); + fw_priv = kzalloc(sizeof(*fw_priv), GFP_KERNEL); if (!fw_priv) { dev_err(device, "%s: kmalloc failed\n", __func__); - return ERR_PTR(-ENOMEM); + fw_priv = ERR_PTR(-ENOMEM); + goto exit; } - fw_priv->fw = firmware; fw_priv->nowait = nowait; - strcpy(fw_priv->fw_id, fw_name); - init_completion(&fw_priv->completion); + fw_priv->fw = firmware; setup_timer(&fw_priv->timeout, firmware_class_timeout, (u_long) fw_priv); f_dev = &fw_priv->dev; device_initialize(f_dev); - dev_set_name(f_dev, "%s", dev_name(device)); + dev_set_name(f_dev, "%s", fw_name); f_dev->parent = device; f_dev->class = &firmware_class; - +exit: return fw_priv; } +/* one pages buffer is mapped/unmapped only once */ +static int fw_map_pages_buf(struct firmware_buf *buf) +{ + buf->data = vmap(buf->pages, buf->nr_pages, 0, PAGE_KERNEL_RO); + if (!buf->data) + return -ENOMEM; + return 0; +} + +/* store the pages buffer info firmware from buf */ +static void fw_set_page_data(struct firmware_buf *buf, struct firmware *fw) +{ + fw->priv = buf; + fw->pages = buf->pages; + fw->size = buf->size; + fw->data = buf->data; + + pr_debug("%s: fw-%s buf=%p data=%p size=%u\n", + __func__, buf->fw_id, buf, buf->data, + (unsigned int)buf->size); +} + +#ifdef CONFIG_PM_SLEEP +static void fw_name_devm_release(struct device *dev, void *res) +{ + struct fw_name_devm *fwn = res; + + if (fwn->magic == (unsigned long)&fw_cache) + pr_debug("%s: fw_name-%s devm-%p released\n", + __func__, fwn->name, res); +} + +static int fw_devm_match(struct device *dev, void *res, + void *match_data) +{ + struct fw_name_devm *fwn = res; + + return (fwn->magic == (unsigned long)&fw_cache) && + !strcmp(fwn->name, match_data); +} + +static struct fw_name_devm *fw_find_devm_name(struct device *dev, + const char *name) +{ + struct fw_name_devm *fwn; + + fwn = devres_find(dev, fw_name_devm_release, + fw_devm_match, (void *)name); + return fwn; +} + +/* add firmware name into devres list */ +static int fw_add_devm_name(struct device *dev, const char *name) +{ + struct fw_name_devm *fwn; + + fwn = fw_find_devm_name(dev, name); + if (fwn) + return 1; + + fwn = devres_alloc(fw_name_devm_release, sizeof(struct fw_name_devm) + + strlen(name) + 1, GFP_KERNEL); + if (!fwn) + return -ENOMEM; + + fwn->magic = (unsigned long)&fw_cache; + strcpy(fwn->name, name); + devres_add(dev, fwn); + + return 0; +} +#else +static int fw_add_devm_name(struct device *dev, const char *name) +{ + return 0; +} +#endif + +static void _request_firmware_cleanup(const struct firmware **firmware_p) +{ + release_firmware(*firmware_p); + *firmware_p = NULL; +} + static struct firmware_priv * _request_firmware_prepare(const struct firmware **firmware_p, const char *name, struct device *device, bool uevent, bool nowait) { struct firmware *firmware; - struct firmware_priv *fw_priv; + struct firmware_priv *fw_priv = NULL; + struct firmware_buf *buf; + int ret; if (!firmware_p) return ERR_PTR(-EINVAL); @@ -490,18 +709,46 @@ _request_firmware_prepare(const struct firmware **firmware_p, const char *name, return NULL; } - fw_priv = fw_create_instance(firmware, name, device, uevent, nowait); - if (IS_ERR(fw_priv)) { - release_firmware(firmware); + ret = fw_lookup_and_allocate_buf(name, &fw_cache, &buf); + if (!ret) + fw_priv = fw_create_instance(firmware, name, device, + uevent, nowait); + + if (IS_ERR(fw_priv) || ret < 0) { + kfree(firmware); *firmware_p = NULL; + return ERR_PTR(-ENOMEM); + } else if (fw_priv) { + fw_priv->buf = buf; + + /* + * bind with 'buf' now to avoid warning in failure path + * of requesting firmware. + */ + firmware->priv = buf; + return fw_priv; } - return fw_priv; -} -static void _request_firmware_cleanup(const struct firmware **firmware_p) -{ - release_firmware(*firmware_p); - *firmware_p = NULL; + /* share the cached buf, which is inprogessing or completed */ + check_status: + mutex_lock(&fw_lock); + if (test_bit(FW_STATUS_ABORT, &buf->status)) { + fw_priv = ERR_PTR(-ENOENT); + firmware->priv = buf; + _request_firmware_cleanup(firmware_p); + goto exit; + } else if (test_bit(FW_STATUS_DONE, &buf->status)) { + fw_priv = NULL; + fw_set_page_data(buf, firmware); + goto exit; + } + mutex_unlock(&fw_lock); + wait_for_completion(&buf->completion); + goto check_status; + +exit: + mutex_unlock(&fw_lock); + return fw_priv; } static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, @@ -509,6 +756,8 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, { int retval = 0; struct device *f_dev = &fw_priv->dev; + struct firmware_buf *buf = fw_priv->buf; + struct firmware_cache *fwc = &fw_cache; dev_set_uevent_suppress(f_dev, true); @@ -535,7 +784,7 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, if (uevent) { dev_set_uevent_suppress(f_dev, false); - dev_dbg(f_dev, "firmware: requesting %s\n", fw_priv->fw_id); + dev_dbg(f_dev, "firmware: requesting %s\n", buf->fw_id); if (timeout != MAX_SCHEDULE_TIMEOUT) mod_timer(&fw_priv->timeout, round_jiffies_up(jiffies + timeout)); @@ -543,15 +792,40 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, kobject_uevent(&fw_priv->dev.kobj, KOBJ_ADD); } - wait_for_completion(&fw_priv->completion); + wait_for_completion(&buf->completion); - set_bit(FW_STATUS_DONE, &fw_priv->status); del_timer_sync(&fw_priv->timeout); mutex_lock(&fw_lock); - if (!fw_priv->fw->size || test_bit(FW_STATUS_ABORT, &fw_priv->status)) + if (!buf->size || test_bit(FW_STATUS_ABORT, &buf->status)) retval = -ENOENT; - fw_priv->fw = NULL; + + /* + * add firmware name into devres list so that we can auto cache + * and uncache firmware for device. + * + * f_dev->parent may has been deleted already, but the problem + * should be fixed in devres or driver core. + */ + if (!retval && f_dev->parent) + fw_add_devm_name(f_dev->parent, buf->fw_id); + + if (!retval) + retval = fw_map_pages_buf(buf); + + /* + * After caching firmware image is started, let it piggyback + * on request firmware. + */ + if (!retval && fwc->state == FW_LOADER_START_CACHE) { + if (fw_cache_piggyback_on_request(buf->fw_id)) + kref_get(&buf->ref); + } + + /* pass the pages buffer to driver at the last minute */ + fw_set_page_data(buf, fw_priv->fw); + + fw_priv->buf = NULL; mutex_unlock(&fw_lock); device_remove_file(f_dev, &dev_attr_loading); @@ -578,6 +852,8 @@ err_put_dev: * @name will be used as $FIRMWARE in the uevent environment and * should be distinctive enough not to be confused with any other * firmware image for this or any other device. + * + * Caller must hold the reference count of @device. **/ int request_firmware(const struct firmware **firmware_p, const char *name, @@ -659,6 +935,7 @@ static void request_firmware_work_func(struct work_struct *work) out: fw_work->cont(fw, fw_work->context); + put_device(fw_work->device); module_put(fw_work->module); kfree(fw_work); @@ -677,9 +954,15 @@ static void request_firmware_work_func(struct work_struct *work) * @cont: function will be called asynchronously when the firmware * request is over. * - * Asynchronous variant of request_firmware() for user contexts where - * it is not possible to sleep for long time. It can't be called - * in atomic contexts. + * Caller must hold the reference count of @device. + * + * Asynchronous variant of request_firmware() for user contexts: + * - sleep for as small periods as possible since it may + * increase kernel boot time of built-in device drivers + * requesting firmware in their ->probe() methods, if + * @gfp is GFP_KERNEL. + * + * - can't sleep at all if @gfp is GFP_ATOMIC. **/ int request_firmware_nowait( @@ -705,18 +988,363 @@ request_firmware_nowait( return -EFAULT; } + get_device(fw_work->device); INIT_WORK(&fw_work->work, request_firmware_work_func); schedule_work(&fw_work->work); return 0; } +/** + * cache_firmware - cache one firmware image in kernel memory space + * @fw_name: the firmware image name + * + * Cache firmware in kernel memory so that drivers can use it when + * system isn't ready for them to request firmware image from userspace. + * Once it returns successfully, driver can use request_firmware or its + * nowait version to get the cached firmware without any interacting + * with userspace + * + * Return 0 if the firmware image has been cached successfully + * Return !0 otherwise + * + */ +int cache_firmware(const char *fw_name) +{ + int ret; + const struct firmware *fw; + + pr_debug("%s: %s\n", __func__, fw_name); + + ret = request_firmware(&fw, fw_name, NULL); + if (!ret) + kfree(fw); + + pr_debug("%s: %s ret=%d\n", __func__, fw_name, ret); + + return ret; +} + +/** + * uncache_firmware - remove one cached firmware image + * @fw_name: the firmware image name + * + * Uncache one firmware image which has been cached successfully + * before. + * + * Return 0 if the firmware cache has been removed successfully + * Return !0 otherwise + * + */ +int uncache_firmware(const char *fw_name) +{ + struct firmware_buf *buf; + struct firmware fw; + + pr_debug("%s: %s\n", __func__, fw_name); + + if (fw_get_builtin_firmware(&fw, fw_name)) + return 0; + + buf = fw_lookup_buf(fw_name); + if (buf) { + fw_free_buf(buf); + return 0; + } + + return -EINVAL; +} + +#ifdef CONFIG_PM_SLEEP +static struct fw_cache_entry *alloc_fw_cache_entry(const char *name) +{ + struct fw_cache_entry *fce; + + fce = kzalloc(sizeof(*fce) + strlen(name) + 1, GFP_ATOMIC); + if (!fce) + goto exit; + + strcpy(fce->name, name); +exit: + return fce; +} + +static int fw_cache_piggyback_on_request(const char *name) +{ + struct firmware_cache *fwc = &fw_cache; + struct fw_cache_entry *fce; + int ret = 0; + + spin_lock(&fwc->name_lock); + list_for_each_entry(fce, &fwc->fw_names, list) { + if (!strcmp(fce->name, name)) + goto found; + } + + fce = alloc_fw_cache_entry(name); + if (fce) { + ret = 1; + list_add(&fce->list, &fwc->fw_names); + pr_debug("%s: fw: %s\n", __func__, name); + } +found: + spin_unlock(&fwc->name_lock); + return ret; +} + +static void free_fw_cache_entry(struct fw_cache_entry *fce) +{ + kfree(fce); +} + +static void __async_dev_cache_fw_image(void *fw_entry, + async_cookie_t cookie) +{ + struct fw_cache_entry *fce = fw_entry; + struct firmware_cache *fwc = &fw_cache; + int ret; + + ret = cache_firmware(fce->name); + if (ret) { + spin_lock(&fwc->name_lock); + list_del(&fce->list); + spin_unlock(&fwc->name_lock); + + free_fw_cache_entry(fce); + } + + spin_lock(&fwc->name_lock); + fwc->cnt--; + spin_unlock(&fwc->name_lock); + + wake_up(&fwc->wait_queue); +} + +/* called with dev->devres_lock held */ +static void dev_create_fw_entry(struct device *dev, void *res, + void *data) +{ + struct fw_name_devm *fwn = res; + const char *fw_name = fwn->name; + struct list_head *head = data; + struct fw_cache_entry *fce; + + fce = alloc_fw_cache_entry(fw_name); + if (fce) + list_add(&fce->list, head); +} + +static int devm_name_match(struct device *dev, void *res, + void *match_data) +{ + struct fw_name_devm *fwn = res; + return (fwn->magic == (unsigned long)match_data); +} + +static void dev_cache_fw_image(struct device *dev, void *data) +{ + LIST_HEAD(todo); + struct fw_cache_entry *fce; + struct fw_cache_entry *fce_next; + struct firmware_cache *fwc = &fw_cache; + + devres_for_each_res(dev, fw_name_devm_release, + devm_name_match, &fw_cache, + dev_create_fw_entry, &todo); + + list_for_each_entry_safe(fce, fce_next, &todo, list) { + list_del(&fce->list); + + spin_lock(&fwc->name_lock); + fwc->cnt++; + list_add(&fce->list, &fwc->fw_names); + spin_unlock(&fwc->name_lock); + + async_schedule(__async_dev_cache_fw_image, (void *)fce); + } +} + +static void __device_uncache_fw_images(void) +{ + struct firmware_cache *fwc = &fw_cache; + struct fw_cache_entry *fce; + + spin_lock(&fwc->name_lock); + while (!list_empty(&fwc->fw_names)) { + fce = list_entry(fwc->fw_names.next, + struct fw_cache_entry, list); + list_del(&fce->list); + spin_unlock(&fwc->name_lock); + + uncache_firmware(fce->name); + free_fw_cache_entry(fce); + + spin_lock(&fwc->name_lock); + } + spin_unlock(&fwc->name_lock); +} + +/** + * device_cache_fw_images - cache devices' firmware + * + * If one device called request_firmware or its nowait version + * successfully before, the firmware names are recored into the + * device's devres link list, so device_cache_fw_images can call + * cache_firmware() to cache these firmwares for the device, + * then the device driver can load its firmwares easily at + * time when system is not ready to complete loading firmware. + */ +static void device_cache_fw_images(void) +{ + struct firmware_cache *fwc = &fw_cache; + int old_timeout; + DEFINE_WAIT(wait); + + pr_debug("%s\n", __func__); + + /* + * use small loading timeout for caching devices' firmware + * because all these firmware images have been loaded + * successfully at lease once, also system is ready for + * completing firmware loading now. The maximum size of + * firmware in current distributions is about 2M bytes, + * so 10 secs should be enough. + */ + old_timeout = loading_timeout; + loading_timeout = 10; + + mutex_lock(&fw_lock); + fwc->state = FW_LOADER_START_CACHE; + dpm_for_each_dev(NULL, dev_cache_fw_image); + mutex_unlock(&fw_lock); + + /* wait for completion of caching firmware for all devices */ + spin_lock(&fwc->name_lock); + for (;;) { + prepare_to_wait(&fwc->wait_queue, &wait, + TASK_UNINTERRUPTIBLE); + if (!fwc->cnt) + break; + + spin_unlock(&fwc->name_lock); + + schedule(); + + spin_lock(&fwc->name_lock); + } + spin_unlock(&fwc->name_lock); + finish_wait(&fwc->wait_queue, &wait); + + loading_timeout = old_timeout; +} + +/** + * device_uncache_fw_images - uncache devices' firmware + * + * uncache all firmwares which have been cached successfully + * by device_uncache_fw_images earlier + */ +static void device_uncache_fw_images(void) +{ + pr_debug("%s\n", __func__); + __device_uncache_fw_images(); +} + +static void device_uncache_fw_images_work(struct work_struct *work) +{ + device_uncache_fw_images(); +} + +/** + * device_uncache_fw_images_delay - uncache devices firmwares + * @delay: number of milliseconds to delay uncache device firmwares + * + * uncache all devices's firmwares which has been cached successfully + * by device_cache_fw_images after @delay milliseconds. + */ +static void device_uncache_fw_images_delay(unsigned long delay) +{ + schedule_delayed_work(&fw_cache.work, + msecs_to_jiffies(delay)); +} + +static int fw_pm_notify(struct notifier_block *notify_block, + unsigned long mode, void *unused) +{ + switch (mode) { + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + device_cache_fw_images(); + break; + + case PM_POST_SUSPEND: + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + /* + * In case that system sleep failed and syscore_suspend is + * not called. + */ + mutex_lock(&fw_lock); + fw_cache.state = FW_LOADER_NO_CACHE; + mutex_unlock(&fw_lock); + + device_uncache_fw_images_delay(10 * MSEC_PER_SEC); + break; + } + + return 0; +} + +/* stop caching firmware once syscore_suspend is reached */ +static int fw_suspend(void) +{ + fw_cache.state = FW_LOADER_NO_CACHE; + return 0; +} + +static struct syscore_ops fw_syscore_ops = { + .suspend = fw_suspend, +}; +#else +static int fw_cache_piggyback_on_request(const char *name) +{ + return 0; +} +#endif + +static void __init fw_cache_init(void) +{ + spin_lock_init(&fw_cache.lock); + INIT_LIST_HEAD(&fw_cache.head); + fw_cache.state = FW_LOADER_NO_CACHE; + +#ifdef CONFIG_PM_SLEEP + spin_lock_init(&fw_cache.name_lock); + INIT_LIST_HEAD(&fw_cache.fw_names); + fw_cache.cnt = 0; + + init_waitqueue_head(&fw_cache.wait_queue); + INIT_DELAYED_WORK(&fw_cache.work, + device_uncache_fw_images_work); + + fw_cache.pm_notify.notifier_call = fw_pm_notify; + register_pm_notifier(&fw_cache.pm_notify); + + register_syscore_ops(&fw_syscore_ops); +#endif +} + static int __init firmware_class_init(void) { + fw_cache_init(); return class_register(&firmware_class); } static void __exit firmware_class_exit(void) { +#ifdef CONFIG_PM_SLEEP + unregister_syscore_ops(&fw_syscore_ops); + unregister_pm_notifier(&fw_cache.pm_notify); +#endif class_unregister(&firmware_class); } @@ -726,3 +1354,5 @@ module_exit(firmware_class_exit); EXPORT_SYMBOL(release_firmware); EXPORT_SYMBOL(request_firmware); EXPORT_SYMBOL(request_firmware_nowait); +EXPORT_SYMBOL_GPL(cache_firmware); +EXPORT_SYMBOL_GPL(uncache_firmware); diff --git a/drivers/base/platform.c b/drivers/base/platform.c index a1a72250258..ddeca142293 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -20,9 +20,13 @@ #include <linux/err.h> #include <linux/slab.h> #include <linux/pm_runtime.h> +#include <linux/idr.h> #include "base.h" +/* For automatically allocated device IDs */ +static DEFINE_IDA(platform_devid_ida); + #define to_platform_driver(drv) (container_of((drv), struct platform_driver, \ driver)) @@ -99,6 +103,9 @@ struct resource *platform_get_resource_byname(struct platform_device *dev, for (i = 0; i < dev->num_resources; i++) { struct resource *r = &dev->resource[i]; + if (unlikely(!r->name)) + continue; + if (type == resource_type(r) && !strcmp(r->name, name)) return r; } @@ -263,7 +270,7 @@ EXPORT_SYMBOL_GPL(platform_device_add_data); */ int platform_device_add(struct platform_device *pdev) { - int i, ret = 0; + int i, ret; if (!pdev) return -EINVAL; @@ -273,10 +280,27 @@ int platform_device_add(struct platform_device *pdev) pdev->dev.bus = &platform_bus_type; - if (pdev->id != -1) + switch (pdev->id) { + default: dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id); - else + break; + case PLATFORM_DEVID_NONE: dev_set_name(&pdev->dev, "%s", pdev->name); + break; + case PLATFORM_DEVID_AUTO: + /* + * Automatically allocated device ID. We mark it as such so + * that we remember it must be freed, and we append a suffix + * to avoid namespace collision with explicit IDs. + */ + ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL); + if (ret < 0) + goto err_out; + pdev->id = ret; + pdev->id_auto = true; + dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id); + break; + } for (i = 0; i < pdev->num_resources; i++) { struct resource *p, *r = &pdev->resource[i]; @@ -309,6 +333,11 @@ int platform_device_add(struct platform_device *pdev) return ret; failed: + if (pdev->id_auto) { + ida_simple_remove(&platform_devid_ida, pdev->id); + pdev->id = PLATFORM_DEVID_AUTO; + } + while (--i >= 0) { struct resource *r = &pdev->resource[i]; unsigned long type = resource_type(r); @@ -317,6 +346,7 @@ int platform_device_add(struct platform_device *pdev) release_resource(r); } + err_out: return ret; } EXPORT_SYMBOL_GPL(platform_device_add); @@ -336,6 +366,11 @@ void platform_device_del(struct platform_device *pdev) if (pdev) { device_del(&pdev->dev); + if (pdev->id_auto) { + ida_simple_remove(&platform_devid_ida, pdev->id); + pdev->id = PLATFORM_DEVID_AUTO; + } + for (i = 0; i < pdev->num_resources; i++) { struct resource *r = &pdev->resource[i]; unsigned long type = resource_type(r); diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 0113adc310d..b0b072a88f5 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -1324,3 +1324,25 @@ int device_pm_wait_for_dev(struct device *subordinate, struct device *dev) return async_error; } EXPORT_SYMBOL_GPL(device_pm_wait_for_dev); + +/** + * dpm_for_each_dev - device iterator. + * @data: data for the callback. + * @fn: function to be called for each device. + * + * Iterate over devices in dpm_list, and call @fn for each device, + * passing it @data. + */ +void dpm_for_each_dev(void *data, void (*fn)(struct device *, void *)) +{ + struct device *dev; + + if (!fn) + return; + + device_pm_lock(); + list_for_each_entry(dev, &dpm_list, power.entry) + fn(dev, data); + device_pm_unlock(); +} +EXPORT_SYMBOL_GPL(dpm_for_each_dev); diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index e175c8ed4ec..07122a9ef36 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -21,6 +21,12 @@ config EXTCON_GPIO Say Y here to enable GPIO based extcon support. Note that GPIO extcon supports single state per extcon instance. +config EXTCON_ADC_JACK + tristate "ADC Jack extcon support" + depends on IIO + help + Say Y here to enable extcon device driver based on ADC values. + config EXTCON_MAX77693 tristate "MAX77693 EXTCON Support" depends on MFD_MAX77693 @@ -41,7 +47,7 @@ config EXTCON_MAX8997 config EXTCON_ARIZONA tristate "Wolfson Arizona EXTCON support" - depends on MFD_ARIZONA + depends on MFD_ARIZONA && INPUT help Say Y here to enable support for external accessory detection with Wolfson Arizona devices. These are audio CODECs with diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 88961b33234..f98a3c4d46e 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -2,8 +2,9 @@ # Makefile for external connector class (extcon) devices # -obj-$(CONFIG_EXTCON) += extcon_class.o -obj-$(CONFIG_EXTCON_GPIO) += extcon_gpio.o +obj-$(CONFIG_EXTCON) += extcon-class.o +obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o +obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o diff --git a/drivers/extcon/extcon-adc-jack.c b/drivers/extcon/extcon-adc-jack.c new file mode 100644 index 00000000000..60ac3fbb4cd --- /dev/null +++ b/drivers/extcon/extcon-adc-jack.c @@ -0,0 +1,198 @@ +/* + * drivers/extcon/extcon-adc-jack.c + * + * Analog Jack extcon driver with ADC-based detection capability. + * + * Copyright (C) 2012 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + * + * Modified for calling to IIO to get adc by <anish.singh@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/iio/consumer.h> +#include <linux/extcon/extcon-adc-jack.h> +#include <linux/extcon.h> + +/** + * struct adc_jack_data - internal data for adc_jack device driver + * @edev - extcon device. + * @cable_names - list of supported cables. + * @num_cables - size of cable_names. + * @adc_conditions - list of adc value conditions. + * @num_conditions - size of adc_conditions. + * @irq - irq number of attach/detach event (0 if not exist). + * @handling_delay - interrupt handler will schedule extcon event + * handling at handling_delay jiffies. + * @handler - extcon event handler called by interrupt handler. + * @chan - iio channel being queried. + */ +struct adc_jack_data { + struct extcon_dev edev; + + const char **cable_names; + int num_cables; + struct adc_jack_cond *adc_conditions; + int num_conditions; + + int irq; + unsigned long handling_delay; /* in jiffies */ + struct delayed_work handler; + + struct iio_channel *chan; +}; + +static void adc_jack_handler(struct work_struct *work) +{ + struct adc_jack_data *data = container_of(to_delayed_work(work), + struct adc_jack_data, + handler); + u32 state = 0; + int ret, adc_val; + int i; + + ret = iio_read_channel_raw(data->chan, &adc_val); + if (ret < 0) { + dev_err(data->edev.dev, "read channel() error: %d\n", ret); + return; + } + + /* Get state from adc value with adc_conditions */ + for (i = 0; i < data->num_conditions; i++) { + struct adc_jack_cond *def = &data->adc_conditions[i]; + if (!def->state) + break; + if (def->min_adc <= adc_val && def->max_adc >= adc_val) { + state = def->state; + break; + } + } + /* if no def has met, it means state = 0 (no cables attached) */ + + extcon_set_state(&data->edev, state); +} + +static irqreturn_t adc_jack_irq_thread(int irq, void *_data) +{ + struct adc_jack_data *data = _data; + + schedule_delayed_work(&data->handler, data->handling_delay); + return IRQ_HANDLED; +} + +static int __devinit adc_jack_probe(struct platform_device *pdev) +{ + struct adc_jack_data *data; + struct adc_jack_pdata *pdata = pdev->dev.platform_data; + int i, err = 0; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->edev.name = pdata->name; + + if (!pdata->cable_names) { + err = -EINVAL; + dev_err(&pdev->dev, "error: cable_names not defined.\n"); + goto out; + } + + data->edev.supported_cable = pdata->cable_names; + + /* Check the length of array and set num_cables */ + for (i = 0; data->edev.supported_cable[i]; i++) + ; + if (i == 0 || i > SUPPORTED_CABLE_MAX) { + err = -EINVAL; + dev_err(&pdev->dev, "error: pdata->cable_names size = %d\n", + i - 1); + goto out; + } + data->num_cables = i; + + if (!pdata->adc_conditions || + !pdata->adc_conditions[0].state) { + err = -EINVAL; + dev_err(&pdev->dev, "error: adc_conditions not defined.\n"); + goto out; + } + data->adc_conditions = pdata->adc_conditions; + + /* Check the length of array and set num_conditions */ + for (i = 0; data->adc_conditions[i].state; i++) + ; + data->num_conditions = i; + + data->chan = iio_channel_get(dev_name(&pdev->dev), + pdata->consumer_channel); + if (IS_ERR(data->chan)) { + err = PTR_ERR(data->chan); + goto out; + } + + data->handling_delay = msecs_to_jiffies(pdata->handling_delay_ms); + + INIT_DELAYED_WORK_DEFERRABLE(&data->handler, adc_jack_handler); + + platform_set_drvdata(pdev, data); + + err = extcon_dev_register(&data->edev, &pdev->dev); + if (err) + goto out; + + data->irq = platform_get_irq(pdev, 0); + if (!data->irq) { + dev_err(&pdev->dev, "platform_get_irq failed\n"); + err = -ENODEV; + goto err_irq; + } + + err = request_any_context_irq(data->irq, adc_jack_irq_thread, + pdata->irq_flags, pdata->name, data); + + if (err) { + dev_err(&pdev->dev, "error: irq %d\n", data->irq); + err = -EINVAL; + goto err_irq; + } + + goto out; + +err_irq: + extcon_dev_unregister(&data->edev); +out: + return err; +} + +static int __devexit adc_jack_remove(struct platform_device *pdev) +{ + struct adc_jack_data *data = platform_get_drvdata(pdev); + + free_irq(data->irq, data); + cancel_work_sync(&data->handler.work); + extcon_dev_unregister(&data->edev); + + return 0; +} + +static struct platform_driver adc_jack_driver = { + .probe = adc_jack_probe, + .remove = __devexit_p(adc_jack_remove), + .driver = { + .name = "adc-jack", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(adc_jack_driver); diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 6c19833ed2d..cdab9e59829 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -21,6 +21,7 @@ #include <linux/interrupt.h> #include <linux/err.h> #include <linux/gpio.h> +#include <linux/input.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> @@ -30,11 +31,14 @@ #include <linux/mfd/arizona/pdata.h> #include <linux/mfd/arizona/registers.h> +#define ARIZONA_NUM_BUTTONS 6 + struct arizona_extcon_info { struct device *dev; struct arizona *arizona; struct mutex lock; struct regulator *micvdd; + struct input_dev *input; int micd_mode; const struct arizona_micd_config *micd_modes; @@ -54,6 +58,18 @@ static const struct arizona_micd_config micd_default_modes[] = { { 0, 2 << ARIZONA_MICD_BIAS_SRC_SHIFT, 1 }, }; +static struct { + u16 status; + int report; +} arizona_lvl_to_key[ARIZONA_NUM_BUTTONS] = { + { 0x1, BTN_0 }, + { 0x2, BTN_1 }, + { 0x4, BTN_2 }, + { 0x8, BTN_3 }, + { 0x10, BTN_4 }, + { 0x20, BTN_5 }, +}; + #define ARIZONA_CABLE_MECHANICAL 0 #define ARIZONA_CABLE_MICROPHONE 1 #define ARIZONA_CABLE_HEADPHONE 2 @@ -133,6 +149,7 @@ static void arizona_stop_mic(struct arizona_extcon_info *info) if (change) { regulator_disable(info->micvdd); + pm_runtime_mark_last_busy(info->dev); pm_runtime_put_autosuspend(info->dev); } } @@ -141,8 +158,8 @@ static irqreturn_t arizona_micdet(int irq, void *data) { struct arizona_extcon_info *info = data; struct arizona *arizona = info->arizona; - unsigned int val; - int ret; + unsigned int val, lvl; + int ret, i; mutex_lock(&info->lock); @@ -219,13 +236,22 @@ static irqreturn_t arizona_micdet(int irq, void *data) /* * If we're still detecting and we detect a short then we've - * got a headphone. Otherwise it's a button press, the - * button reporting is stubbed out for now. + * got a headphone. Otherwise it's a button press. */ if (val & 0x3fc) { if (info->mic) { dev_dbg(arizona->dev, "Mic button detected\n"); + lvl = val & ARIZONA_MICD_LVL_MASK; + lvl >>= ARIZONA_MICD_LVL_SHIFT; + + for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) + if (lvl & arizona_lvl_to_key[i].status) + input_report_key(info->input, + arizona_lvl_to_key[i].report, + 1); + input_sync(info->input); + } else if (info->detecting) { dev_dbg(arizona->dev, "Headphone detected\n"); info->detecting = false; @@ -244,6 +270,10 @@ static irqreturn_t arizona_micdet(int irq, void *data) } } else { dev_dbg(arizona->dev, "Mic button released\n"); + for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) + input_report_key(info->input, + arizona_lvl_to_key[i].report, 0); + input_sync(info->input); } handled: @@ -258,7 +288,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data) struct arizona_extcon_info *info = data; struct arizona *arizona = info->arizona; unsigned int val; - int ret; + int ret, i; pm_runtime_get_sync(info->dev); @@ -288,6 +318,11 @@ static irqreturn_t arizona_jackdet(int irq, void *data) arizona_stop_mic(info); + for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) + input_report_key(info->input, + arizona_lvl_to_key[i].report, 0); + input_sync(info->input); + ret = extcon_update_state(&info->edev, 0xffffffff, 0); if (ret != 0) dev_err(arizona->dev, "Removal report failed: %d\n", @@ -307,13 +342,13 @@ static int __devinit arizona_extcon_probe(struct platform_device *pdev) struct arizona *arizona = dev_get_drvdata(pdev->dev.parent); struct arizona_pdata *pdata; struct arizona_extcon_info *info; - int ret, mode; + int ret, mode, i; pdata = dev_get_platdata(arizona->dev); info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); if (!info) { - dev_err(&pdev->dev, "failed to allocate memory\n"); + dev_err(&pdev->dev, "Failed to allocate memory\n"); ret = -ENOMEM; goto err; } @@ -350,7 +385,7 @@ static int __devinit arizona_extcon_probe(struct platform_device *pdev) ret = extcon_dev_register(&info->edev, arizona->dev); if (ret < 0) { - dev_err(arizona->dev, "extcon_dev_regster() failed: %d\n", + dev_err(arizona->dev, "extcon_dev_register() failed: %d\n", ret); goto err; } @@ -382,6 +417,20 @@ static int __devinit arizona_extcon_probe(struct platform_device *pdev) arizona_extcon_set_mode(info, 0); + info->input = input_allocate_device(); + if (!info->input) { + dev_err(arizona->dev, "Can't allocate input dev\n"); + ret = -ENOMEM; + goto err_register; + } + + for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) + input_set_capability(info->input, EV_KEY, + arizona_lvl_to_key[i].report); + info->input->name = "Headset"; + info->input->phys = "arizona/extcon"; + info->input->dev.parent = &pdev->dev; + pm_runtime_enable(&pdev->dev); pm_runtime_idle(&pdev->dev); pm_runtime_get_sync(&pdev->dev); @@ -391,7 +440,7 @@ static int __devinit arizona_extcon_probe(struct platform_device *pdev) if (ret != 0) { dev_err(&pdev->dev, "Failed to get JACKDET rise IRQ: %d\n", ret); - goto err_register; + goto err_input; } ret = arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 1); @@ -441,8 +490,16 @@ static int __devinit arizona_extcon_probe(struct platform_device *pdev) pm_runtime_put(&pdev->dev); + ret = input_register_device(info->input); + if (ret) { + dev_err(&pdev->dev, "Can't register input device: %d\n", ret); + goto err_micdet; + } + return 0; +err_micdet: + arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info); err_fall_wake: arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 0); err_fall: @@ -451,6 +508,8 @@ err_rise_wake: arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 0); err_rise: arizona_free_irq(arizona, ARIZONA_IRQ_JD_RISE, info); +err_input: + input_free_device(info->input); err_register: pm_runtime_disable(&pdev->dev); extcon_dev_unregister(&info->edev); @@ -473,6 +532,7 @@ static int __devexit arizona_extcon_remove(struct platform_device *pdev) regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE, ARIZONA_JD1_ENA, 0); arizona_clk32k_disable(arizona); + input_unregister_device(info->input); extcon_dev_unregister(&info->edev); return 0; diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon-class.c index f6419f9db76..946a3188b2b 100644 --- a/drivers/extcon/extcon_class.c +++ b/drivers/extcon/extcon-class.c @@ -30,6 +30,7 @@ #include <linux/err.h> #include <linux/extcon.h> #include <linux/slab.h> +#include <linux/sysfs.h> /* * extcon_cable_name suggests the standard cable names for commonly used @@ -442,7 +443,7 @@ static int _call_per_cable(struct notifier_block *nb, unsigned long val, /** * extcon_register_interest() - Register a notifier for a state change of a - * specific cable, not a entier set of cables of a + * specific cable, not an entier set of cables of a * extcon device. * @obj: an empty extcon_specific_cable_nb object to be returned. * @extcon_name: the name of extcon device. @@ -498,7 +499,7 @@ int extcon_unregister_interest(struct extcon_specific_cable_nb *obj) } /** - * extcon_register_notifier() - Register a notifee to get notified by + * extcon_register_notifier() - Register a notifiee to get notified by * any attach status changes from the extcon. * @edev: the extcon device. * @nb: a notifier block to be registered. @@ -515,7 +516,7 @@ int extcon_register_notifier(struct extcon_dev *edev, EXPORT_SYMBOL_GPL(extcon_register_notifier); /** - * extcon_unregister_notifier() - Unregister a notifee from the extcon device. + * extcon_unregister_notifier() - Unregister a notifiee from the extcon device. * @edev: the extcon device. * @nb: a registered notifier block to be unregistered. */ @@ -673,10 +674,12 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev) cable->attr_g.name = str; cable->attr_g.attrs = cable->attrs; + sysfs_attr_init(&cable->attr_name.attr); cable->attr_name.attr.name = "name"; cable->attr_name.attr.mode = 0444; cable->attr_name.show = cable_name_show; + sysfs_attr_init(&cable->attr_state.attr); cable->attr_state.attr.name = "state"; cable->attr_state.attr.mode = 0644; cable->attr_state.show = cable_state_show; @@ -722,6 +725,7 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev) goto err_muex; } strcpy(name, buf); + sysfs_attr_init(&edev->d_attrs_muex[index].attr); edev->d_attrs_muex[index].attr.name = name; edev->d_attrs_muex[index].attr.mode = 0000; edev->attrs_muex[index] = &edev->d_attrs_muex[index] @@ -802,7 +806,7 @@ EXPORT_SYMBOL_GPL(extcon_dev_register); /** * extcon_dev_unregister() - Unregister the extcon device. - * @edev: the extcon device instance to be unregitered. + * @edev: the extcon device instance to be unregistered. * * Note that this does not call kfree(edev) because edev was not allocated * by this class. diff --git a/drivers/extcon/extcon_gpio.c b/drivers/extcon/extcon-gpio.c index 3cc152e690b..3cc152e690b 100644 --- a/drivers/extcon/extcon_gpio.c +++ b/drivers/extcon/extcon-gpio.c diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 38f9e52f358..e21387e2da5 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -356,7 +356,7 @@ static int max77693_muic_adc_ground_handler(struct max77693_muic_info *info, extcon_set_cable_state(info->edev, "MHL", attached); break; default: - dev_err(info->dev, "faild to detect %s accessory\n", + dev_err(info->dev, "failed to detect %s accessory\n", attached ? "attached" : "detached"); dev_err(info->dev, "- adc:0x%x, adclow:0x%x, adc1k:0x%x\n", adc, adclow, adc1k); @@ -548,7 +548,7 @@ static void max77693_muic_irq_work(struct work_struct *work) curr_adc = info->status[0] & STATUS1_ADC_MASK; curr_adc >>= STATUS1_ADC_SHIFT; - /* Check accossory state which is either detached or attached */ + /* Check accessory state which is either detached or attached */ if (curr_adc == MAX77693_MUIC_ADC_OPEN) attached = false; @@ -564,7 +564,7 @@ static void max77693_muic_irq_work(struct work_struct *work) curr_chg_type = info->status[1] & STATUS2_CHGTYP_MASK; curr_chg_type >>= STATUS2_CHGTYP_SHIFT; - /* Check charger accossory state which + /* Check charger accessory state which is either detached or attached */ if (curr_chg_type == MAX77693_CHARGER_TYPE_NONE) attached = false; @@ -699,7 +699,7 @@ static int __devinit max77693_muic_probe(struct platform_device *pdev) ret = request_threaded_irq(virq, NULL, max77693_muic_irq_handler, - 0, muic_irq->name, info); + IRQF_ONESHOT, muic_irq->name, info); if (ret) { dev_err(&pdev->dev, "failed: irq request (IRQ: %d," diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index 47408e802ab..d10c9873dd9 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -435,12 +435,23 @@ efivar_attr_read(struct efivar_entry *entry, char *buf) if (status != EFI_SUCCESS) return -EIO; - if (var->Attributes & 0x1) + if (var->Attributes & EFI_VARIABLE_NON_VOLATILE) str += sprintf(str, "EFI_VARIABLE_NON_VOLATILE\n"); - if (var->Attributes & 0x2) + if (var->Attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS) str += sprintf(str, "EFI_VARIABLE_BOOTSERVICE_ACCESS\n"); - if (var->Attributes & 0x4) + if (var->Attributes & EFI_VARIABLE_RUNTIME_ACCESS) str += sprintf(str, "EFI_VARIABLE_RUNTIME_ACCESS\n"); + if (var->Attributes & EFI_VARIABLE_HARDWARE_ERROR_RECORD) + str += sprintf(str, "EFI_VARIABLE_HARDWARE_ERROR_RECORD\n"); + if (var->Attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) + str += sprintf(str, + "EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS\n"); + if (var->Attributes & + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) + str += sprintf(str, + "EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS\n"); + if (var->Attributes & EFI_VARIABLE_APPEND_WRITE) + str += sprintf(str, "EFI_VARIABLE_APPEND_WRITE\n"); return str - buf; } diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index 86f8885aeb4..3648f8f0f36 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -26,6 +26,7 @@ #include <linux/slab.h> #include <linux/vmalloc.h> #include <linux/hyperv.h> +#include <linux/version.h> #include <asm/hyperv.h> #include "hyperv_vmbus.h" @@ -38,28 +39,6 @@ struct hv_context hv_context = { }; /* - * query_hypervisor_presence - * - Query the cpuid for presence of windows hypervisor - */ -static int query_hypervisor_presence(void) -{ - unsigned int eax; - unsigned int ebx; - unsigned int ecx; - unsigned int edx; - unsigned int op; - - eax = 0; - ebx = 0; - ecx = 0; - edx = 0; - op = HVCPUID_VERSION_FEATURES; - cpuid(op, &eax, &ebx, &ecx, &edx); - - return ecx & HV_PRESENT_BIT; -} - -/* * query_hypervisor_info - Get version info of the windows hypervisor */ static int query_hypervisor_info(void) @@ -159,14 +138,13 @@ int hv_init(void) memset(hv_context.synic_message_page, 0, sizeof(void *) * NR_CPUS); - if (!query_hypervisor_presence()) - goto cleanup; - max_leaf = query_hypervisor_info(); - /* Write our OS info */ - wrmsrl(HV_X64_MSR_GUEST_OS_ID, HV_LINUX_GUEST_ID); - hv_context.guestid = HV_LINUX_GUEST_ID; + /* + * Write our OS ID. + */ + hv_context.guestid = generate_guest_id(0, LINUX_VERSION_CODE, 0); + wrmsrl(HV_X64_MSR_GUEST_OS_ID, hv_context.guestid); /* See if the hypercall page is already set */ rdmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64); diff --git a/drivers/hv/hv_kvp.c b/drivers/hv/hv_kvp.c index 0012eed6d87..ed50e9e83c6 100644 --- a/drivers/hv/hv_kvp.c +++ b/drivers/hv/hv_kvp.c @@ -48,13 +48,24 @@ static struct { void *kvp_context; /* for the channel callback */ } kvp_transaction; +/* + * Before we can accept KVP messages from the host, we need + * to handshake with the user level daemon. This state tracks + * if we are in the handshake phase. + */ +static bool in_hand_shake = true; + +/* + * This state maintains the version number registered by the daemon. + */ +static int dm_reg_value; + static void kvp_send_key(struct work_struct *dummy); -#define TIMEOUT_FIRED 1 -static void kvp_respond_to_host(char *key, char *value, int error); +static void kvp_respond_to_host(struct hv_kvp_msg *msg, int error); static void kvp_work_func(struct work_struct *dummy); -static void kvp_register(void); +static void kvp_register(int); static DECLARE_DELAYED_WORK(kvp_work, kvp_work_func); static DECLARE_WORK(kvp_sendkey_work, kvp_send_key); @@ -68,7 +79,7 @@ static u8 *recv_buffer; */ static void -kvp_register(void) +kvp_register(int reg_value) { struct cn_msg *msg; @@ -83,7 +94,7 @@ kvp_register(void) msg->id.idx = CN_KVP_IDX; msg->id.val = CN_KVP_VAL; - kvp_msg->kvp_hdr.operation = KVP_OP_REGISTER; + kvp_msg->kvp_hdr.operation = reg_value; strcpy(version, HV_DRV_VERSION); msg->len = sizeof(struct hv_kvp_msg); cn_netlink_send(msg, 0, GFP_ATOMIC); @@ -97,9 +108,43 @@ kvp_work_func(struct work_struct *dummy) * If the timer fires, the user-mode component has not responded; * process the pending transaction. */ - kvp_respond_to_host("Unknown key", "Guest timed out", TIMEOUT_FIRED); + kvp_respond_to_host(NULL, HV_E_FAIL); +} + +static int kvp_handle_handshake(struct hv_kvp_msg *msg) +{ + int ret = 1; + + switch (msg->kvp_hdr.operation) { + case KVP_OP_REGISTER: + dm_reg_value = KVP_OP_REGISTER; + pr_info("KVP: IP injection functionality not available\n"); + pr_info("KVP: Upgrade the KVP daemon\n"); + break; + case KVP_OP_REGISTER1: + dm_reg_value = KVP_OP_REGISTER1; + break; + default: + pr_info("KVP: incompatible daemon\n"); + pr_info("KVP: KVP version: %d, Daemon version: %d\n", + KVP_OP_REGISTER1, msg->kvp_hdr.operation); + ret = 0; + } + + if (ret) { + /* + * We have a compatible daemon; complete the handshake. + */ + pr_info("KVP: user-mode registering done.\n"); + kvp_register(dm_reg_value); + kvp_transaction.active = false; + if (kvp_transaction.kvp_context) + hv_kvp_onchannelcallback(kvp_transaction.kvp_context); + } + return ret; } + /* * Callback when data is received from user mode. */ @@ -109,29 +154,165 @@ kvp_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) { struct hv_kvp_msg *message; struct hv_kvp_msg_enumerate *data; + int error = 0; message = (struct hv_kvp_msg *)msg->data; - switch (message->kvp_hdr.operation) { + + /* + * If we are negotiating the version information + * with the daemon; handle that first. + */ + + if (in_hand_shake) { + if (kvp_handle_handshake(message)) + in_hand_shake = false; + return; + } + + /* + * Based on the version of the daemon, we propagate errors from the + * daemon differently. + */ + + data = &message->body.kvp_enum_data; + + switch (dm_reg_value) { case KVP_OP_REGISTER: - pr_info("KVP: user-mode registering done.\n"); - kvp_register(); - kvp_transaction.active = false; - hv_kvp_onchannelcallback(kvp_transaction.kvp_context); + /* + * Null string is used to pass back error condition. + */ + if (data->data.key[0] == 0) + error = HV_S_CONT; break; - default: - data = &message->body.kvp_enum_data; + case KVP_OP_REGISTER1: /* - * Complete the transaction by forwarding the key value - * to the host. But first, cancel the timeout. + * We use the message header information from + * the user level daemon to transmit errors. */ - if (cancel_delayed_work_sync(&kvp_work)) - kvp_respond_to_host(data->data.key, - data->data.value, - !strlen(data->data.key)); + error = message->error; + break; + } + + /* + * Complete the transaction by forwarding the key value + * to the host. But first, cancel the timeout. + */ + if (cancel_delayed_work_sync(&kvp_work)) + kvp_respond_to_host(message, error); +} + + +static int process_ob_ipinfo(void *in_msg, void *out_msg, int op) +{ + struct hv_kvp_msg *in = in_msg; + struct hv_kvp_ip_msg *out = out_msg; + int len; + + switch (op) { + case KVP_OP_GET_IP_INFO: + /* + * Transform all parameters into utf16 encoding. + */ + len = utf8s_to_utf16s((char *)in->body.kvp_ip_val.ip_addr, + strlen((char *)in->body.kvp_ip_val.ip_addr), + UTF16_HOST_ENDIAN, + (wchar_t *)out->kvp_ip_val.ip_addr, + MAX_IP_ADDR_SIZE); + if (len < 0) + return len; + + len = utf8s_to_utf16s((char *)in->body.kvp_ip_val.sub_net, + strlen((char *)in->body.kvp_ip_val.sub_net), + UTF16_HOST_ENDIAN, + (wchar_t *)out->kvp_ip_val.sub_net, + MAX_IP_ADDR_SIZE); + if (len < 0) + return len; + + len = utf8s_to_utf16s((char *)in->body.kvp_ip_val.gate_way, + strlen((char *)in->body.kvp_ip_val.gate_way), + UTF16_HOST_ENDIAN, + (wchar_t *)out->kvp_ip_val.gate_way, + MAX_GATEWAY_SIZE); + if (len < 0) + return len; + + len = utf8s_to_utf16s((char *)in->body.kvp_ip_val.dns_addr, + strlen((char *)in->body.kvp_ip_val.dns_addr), + UTF16_HOST_ENDIAN, + (wchar_t *)out->kvp_ip_val.dns_addr, + MAX_IP_ADDR_SIZE); + if (len < 0) + return len; + + len = utf8s_to_utf16s((char *)in->body.kvp_ip_val.adapter_id, + strlen((char *)in->body.kvp_ip_val.adapter_id), + UTF16_HOST_ENDIAN, + (wchar_t *)out->kvp_ip_val.adapter_id, + MAX_IP_ADDR_SIZE); + if (len < 0) + return len; + + out->kvp_ip_val.dhcp_enabled = + in->body.kvp_ip_val.dhcp_enabled; + out->kvp_ip_val.addr_family = + in->body.kvp_ip_val.addr_family; + } + + return 0; +} + +static void process_ib_ipinfo(void *in_msg, void *out_msg, int op) +{ + struct hv_kvp_ip_msg *in = in_msg; + struct hv_kvp_msg *out = out_msg; + + switch (op) { + case KVP_OP_SET_IP_INFO: + /* + * Transform all parameters into utf8 encoding. + */ + utf16s_to_utf8s((wchar_t *)in->kvp_ip_val.ip_addr, + MAX_IP_ADDR_SIZE, + UTF16_LITTLE_ENDIAN, + (__u8 *)out->body.kvp_ip_val.ip_addr, + MAX_IP_ADDR_SIZE); + + utf16s_to_utf8s((wchar_t *)in->kvp_ip_val.sub_net, + MAX_IP_ADDR_SIZE, + UTF16_LITTLE_ENDIAN, + (__u8 *)out->body.kvp_ip_val.sub_net, + MAX_IP_ADDR_SIZE); + + utf16s_to_utf8s((wchar_t *)in->kvp_ip_val.gate_way, + MAX_GATEWAY_SIZE, + UTF16_LITTLE_ENDIAN, + (__u8 *)out->body.kvp_ip_val.gate_way, + MAX_GATEWAY_SIZE); + + utf16s_to_utf8s((wchar_t *)in->kvp_ip_val.dns_addr, + MAX_IP_ADDR_SIZE, + UTF16_LITTLE_ENDIAN, + (__u8 *)out->body.kvp_ip_val.dns_addr, + MAX_IP_ADDR_SIZE); + + out->body.kvp_ip_val.dhcp_enabled = in->kvp_ip_val.dhcp_enabled; + + default: + utf16s_to_utf8s((wchar_t *)in->kvp_ip_val.adapter_id, + MAX_ADAPTER_ID_SIZE, + UTF16_LITTLE_ENDIAN, + (__u8 *)out->body.kvp_ip_val.adapter_id, + MAX_ADAPTER_ID_SIZE); + + out->body.kvp_ip_val.addr_family = in->kvp_ip_val.addr_family; } } + + + static void kvp_send_key(struct work_struct *dummy) { @@ -167,6 +348,12 @@ kvp_send_key(struct work_struct *dummy) */ switch (message->kvp_hdr.operation) { + case KVP_OP_SET_IP_INFO: + process_ib_ipinfo(in_msg, message, KVP_OP_SET_IP_INFO); + break; + case KVP_OP_GET_IP_INFO: + process_ib_ipinfo(in_msg, message, KVP_OP_GET_IP_INFO); + break; case KVP_OP_SET: switch (in_msg->body.kvp_set.data.value_type) { case REG_SZ: @@ -243,17 +430,19 @@ kvp_send_key(struct work_struct *dummy) */ static void -kvp_respond_to_host(char *key, char *value, int error) +kvp_respond_to_host(struct hv_kvp_msg *msg_to_host, int error) { struct hv_kvp_msg *kvp_msg; struct hv_kvp_exchg_msg_value *kvp_data; char *key_name; + char *value; struct icmsg_hdr *icmsghdrp; int keylen = 0; int valuelen = 0; u32 buf_len; struct vmbus_channel *channel; u64 req_id; + int ret; /* * If a transaction is not active; log and return. @@ -287,6 +476,7 @@ kvp_respond_to_host(char *key, char *value, int error) */ return; + icmsghdrp->status = error; /* * If the error parameter is set, terminate the host's enumeration @@ -294,20 +484,27 @@ kvp_respond_to_host(char *key, char *value, int error) */ if (error) { /* - * Something failed or the we have timedout; - * terminate the current host-side iteration. + * Something failed or we have timedout; + * terminate the current host-side iteration. */ - icmsghdrp->status = HV_S_CONT; goto response_done; } - icmsghdrp->status = HV_S_OK; - kvp_msg = (struct hv_kvp_msg *) &recv_buffer[sizeof(struct vmbuspipe_hdr) + sizeof(struct icmsg_hdr)]; switch (kvp_transaction.kvp_msg->kvp_hdr.operation) { + case KVP_OP_GET_IP_INFO: + ret = process_ob_ipinfo(msg_to_host, + (struct hv_kvp_ip_msg *)kvp_msg, + KVP_OP_GET_IP_INFO); + if (ret < 0) + icmsghdrp->status = HV_E_FAIL; + + goto response_done; + case KVP_OP_SET_IP_INFO: + goto response_done; case KVP_OP_GET: kvp_data = &kvp_msg->body.kvp_get.data; goto copy_value; @@ -321,7 +518,7 @@ kvp_respond_to_host(char *key, char *value, int error) } kvp_data = &kvp_msg->body.kvp_enum_data.data; - key_name = key; + key_name = msg_to_host->body.kvp_enum_data.data.key; /* * The windows host expects the key/value pair to be encoded @@ -335,6 +532,7 @@ kvp_respond_to_host(char *key, char *value, int error) kvp_data->key_size = 2*(keylen + 1); /* utf16 encoding */ copy_value: + value = msg_to_host->body.kvp_enum_data.data.value; valuelen = utf8s_to_utf16s(value, strlen(value), UTF16_HOST_ENDIAN, (wchar_t *) kvp_data->value, (HV_KVP_EXCHANGE_MAX_VALUE_SIZE / 2) - 2); @@ -387,7 +585,8 @@ void hv_kvp_onchannelcallback(void *context) return; } - vmbus_recvpacket(channel, recv_buffer, PAGE_SIZE, &recvlen, &requestid); + vmbus_recvpacket(channel, recv_buffer, PAGE_SIZE * 2, &recvlen, + &requestid); if (recvlen > 0) { icmsghdrp = (struct icmsg_hdr *)&recv_buffer[ diff --git a/drivers/hv/hv_util.c b/drivers/hv/hv_util.c index d3ac6a40118..a0667de7a04 100644 --- a/drivers/hv/hv_util.c +++ b/drivers/hv/hv_util.c @@ -263,7 +263,7 @@ static int util_probe(struct hv_device *dev, (struct hv_util_service *)dev_id->driver_data; int ret; - srv->recv_buffer = kmalloc(PAGE_SIZE, GFP_KERNEL); + srv->recv_buffer = kmalloc(PAGE_SIZE * 2, GFP_KERNEL); if (!srv->recv_buffer) return -ENOMEM; if (srv->util_init) { @@ -274,7 +274,7 @@ static int util_probe(struct hv_device *dev, } } - ret = vmbus_open(dev->channel, 2 * PAGE_SIZE, 2 * PAGE_SIZE, NULL, 0, + ret = vmbus_open(dev->channel, 4 * PAGE_SIZE, 4 * PAGE_SIZE, NULL, 0, srv->util_cb, dev->channel); if (ret) goto error; diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 0614ff3a7d7..d8d1fadb398 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -410,10 +410,49 @@ enum { #define HV_PRESENT_BIT 0x80000000 -#define HV_LINUX_GUEST_ID_LO 0x00000000 -#define HV_LINUX_GUEST_ID_HI 2976579765 -#define HV_LINUX_GUEST_ID (((u64)HV_LINUX_GUEST_ID_HI << 32) | \ - HV_LINUX_GUEST_ID_LO) +/* + * The guest OS needs to register the guest ID with the hypervisor. + * The guest ID is a 64 bit entity and the structure of this ID is + * specified in the Hyper-V specification: + * + * http://msdn.microsoft.com/en-us/library/windows/hardware/ff542653%28v=vs.85%29.aspx + * + * While the current guideline does not specify how Linux guest ID(s) + * need to be generated, our plan is to publish the guidelines for + * Linux and other guest operating systems that currently are hosted + * on Hyper-V. The implementation here conforms to this yet + * unpublished guidelines. + * + * + * Bit(s) + * 63 - Indicates if the OS is Open Source or not; 1 is Open Source + * 62:56 - Os Type; Linux is 0x100 + * 55:48 - Distro specific identification + * 47:16 - Linux kernel version number + * 15:0 - Distro specific identification + * + * + */ + +#define HV_LINUX_VENDOR_ID 0x8100 + +/* + * Generate the guest ID based on the guideline described above. + */ + +static inline __u64 generate_guest_id(__u8 d_info1, __u32 kernel_version, + __u16 d_info2) +{ + __u64 guest_id = 0; + + guest_id = (((__u64)HV_LINUX_VENDOR_ID) << 48); + guest_id |= (((__u64)(d_info1)) << 48); + guest_id |= (((__u64)(kernel_version)) << 16); + guest_id |= ((__u64)(d_info2)); + + return guest_id; +} + #define HV_CPU_POWER_MANAGEMENT (1 << 0) #define HV_RECOMMENDATIONS_MAX 4 diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 4748086eaaf..8e1a9ec5300 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -34,6 +34,7 @@ #include <linux/completion.h> #include <linux/hyperv.h> #include <asm/hyperv.h> +#include <asm/hypervisor.h> #include "hyperv_vmbus.h" @@ -146,43 +147,9 @@ static ssize_t vmbus_show_device_attr(struct device *dev, get_channel_info(hv_dev, device_info); if (!strcmp(dev_attr->attr.name, "class_id")) { - ret = sprintf(buf, "{%02x%02x%02x%02x-%02x%02x-%02x%02x-" - "%02x%02x%02x%02x%02x%02x%02x%02x}\n", - device_info->chn_type.b[3], - device_info->chn_type.b[2], - device_info->chn_type.b[1], - device_info->chn_type.b[0], - device_info->chn_type.b[5], - device_info->chn_type.b[4], - device_info->chn_type.b[7], - device_info->chn_type.b[6], - device_info->chn_type.b[8], - device_info->chn_type.b[9], - device_info->chn_type.b[10], - device_info->chn_type.b[11], - device_info->chn_type.b[12], - device_info->chn_type.b[13], - device_info->chn_type.b[14], - device_info->chn_type.b[15]); + ret = sprintf(buf, "{%pUl}\n", device_info->chn_type.b); } else if (!strcmp(dev_attr->attr.name, "device_id")) { - ret = sprintf(buf, "{%02x%02x%02x%02x-%02x%02x-%02x%02x-" - "%02x%02x%02x%02x%02x%02x%02x%02x}\n", - device_info->chn_instance.b[3], - device_info->chn_instance.b[2], - device_info->chn_instance.b[1], - device_info->chn_instance.b[0], - device_info->chn_instance.b[5], - device_info->chn_instance.b[4], - device_info->chn_instance.b[7], - device_info->chn_instance.b[6], - device_info->chn_instance.b[8], - device_info->chn_instance.b[9], - device_info->chn_instance.b[10], - device_info->chn_instance.b[11], - device_info->chn_instance.b[12], - device_info->chn_instance.b[13], - device_info->chn_instance.b[14], - device_info->chn_instance.b[15]); + ret = sprintf(buf, "{%pUl}\n", device_info->chn_instance.b); } else if (!strcmp(dev_attr->attr.name, "modalias")) { print_alias_name(hv_dev, alias_name); ret = sprintf(buf, "vmbus:%s\n", alias_name); @@ -757,6 +724,9 @@ static int __init hv_acpi_init(void) { int ret, t; + if (x86_hyper != &x86_hyper_ms_hyperv) + return -ENODEV; + init_completion(&probe_event); /* diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile index 42b3ce9d80f..9cce5d70ed5 100644 --- a/drivers/memory/Makefile +++ b/drivers/memory/Makefile @@ -2,6 +2,9 @@ # Makefile for memory devices # +ifeq ($(CONFIG_DDR),y) +obj-$(CONFIG_OF) += of_memory.o +endif obj-$(CONFIG_TI_EMIF) += emif.o obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o obj-$(CONFIG_TEGRA30_MC) += tegra30-mc.o diff --git a/drivers/memory/emif.c b/drivers/memory/emif.c index 33a4396b24c..06d31c99e6a 100644 --- a/drivers/memory/emif.c +++ b/drivers/memory/emif.c @@ -18,6 +18,7 @@ #include <linux/platform_device.h> #include <linux/interrupt.h> #include <linux/slab.h> +#include <linux/of.h> #include <linux/debugfs.h> #include <linux/seq_file.h> #include <linux/module.h> @@ -25,6 +26,7 @@ #include <linux/spinlock.h> #include <memory/jedec_ddr.h> #include "emif.h" +#include "of_memory.h" /** * struct emif_data - Per device static data for driver's use @@ -49,6 +51,7 @@ * frequency in effect at the moment) * @plat_data: Pointer to saved platform data. * @debugfs_root: dentry to the root folder for EMIF in debugfs + * @np_ddr: Pointer to ddr device tree node */ struct emif_data { u8 duplicate; @@ -63,6 +66,7 @@ struct emif_data { struct emif_regs *curr_regs; struct emif_platform_data *plat_data; struct dentry *debugfs_root; + struct device_node *np_ddr; }; static struct emif_data *emif1; @@ -71,6 +75,7 @@ static unsigned long irq_state; static u32 t_ck; /* DDR clock period in ps */ static LIST_HEAD(device_list); +#ifdef CONFIG_DEBUG_FS static void do_emif_regdump_show(struct seq_file *s, struct emif_data *emif, struct emif_regs *regs) { @@ -162,23 +167,23 @@ static int __init_or_module emif_debugfs_init(struct emif_data *emif) int ret; dentry = debugfs_create_dir(dev_name(emif->dev), NULL); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); + if (!dentry) { + ret = -ENOMEM; goto err0; } emif->debugfs_root = dentry; dentry = debugfs_create_file("regcache_dump", S_IRUGO, emif->debugfs_root, emif, &emif_regdump_fops); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); + if (!dentry) { + ret = -ENOMEM; goto err1; } dentry = debugfs_create_file("mr4", S_IRUGO, emif->debugfs_root, emif, &emif_mr4_fops); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); + if (!dentry) { + ret = -ENOMEM; goto err1; } @@ -194,6 +199,16 @@ static void __exit emif_debugfs_exit(struct emif_data *emif) debugfs_remove_recursive(emif->debugfs_root); emif->debugfs_root = NULL; } +#else +static inline int __init_or_module emif_debugfs_init(struct emif_data *emif) +{ + return 0; +} + +static inline void __exit emif_debugfs_exit(struct emif_data *emif) +{ +} +#endif /* * Calculate the period of DDR clock from frequency value @@ -1148,6 +1163,168 @@ static int is_custom_config_valid(struct emif_custom_configs *cust_cfgs, return valid; } +#if defined(CONFIG_OF) +static void __init_or_module of_get_custom_configs(struct device_node *np_emif, + struct emif_data *emif) +{ + struct emif_custom_configs *cust_cfgs = NULL; + int len; + const int *lpmode, *poll_intvl; + + lpmode = of_get_property(np_emif, "low-power-mode", &len); + poll_intvl = of_get_property(np_emif, "temp-alert-poll-interval", &len); + + if (lpmode || poll_intvl) + cust_cfgs = devm_kzalloc(emif->dev, sizeof(*cust_cfgs), + GFP_KERNEL); + + if (!cust_cfgs) + return; + + if (lpmode) { + cust_cfgs->mask |= EMIF_CUSTOM_CONFIG_LPMODE; + cust_cfgs->lpmode = *lpmode; + of_property_read_u32(np_emif, + "low-power-mode-timeout-performance", + &cust_cfgs->lpmode_timeout_performance); + of_property_read_u32(np_emif, + "low-power-mode-timeout-power", + &cust_cfgs->lpmode_timeout_power); + of_property_read_u32(np_emif, + "low-power-mode-freq-threshold", + &cust_cfgs->lpmode_freq_threshold); + } + + if (poll_intvl) { + cust_cfgs->mask |= + EMIF_CUSTOM_CONFIG_TEMP_ALERT_POLL_INTERVAL; + cust_cfgs->temp_alert_poll_interval_ms = *poll_intvl; + } + + if (!is_custom_config_valid(cust_cfgs, emif->dev)) { + devm_kfree(emif->dev, cust_cfgs); + return; + } + + emif->plat_data->custom_configs = cust_cfgs; +} + +static void __init_or_module of_get_ddr_info(struct device_node *np_emif, + struct device_node *np_ddr, + struct ddr_device_info *dev_info) +{ + u32 density = 0, io_width = 0; + int len; + + if (of_find_property(np_emif, "cs1-used", &len)) + dev_info->cs1_used = true; + + if (of_find_property(np_emif, "cal-resistor-per-cs", &len)) + dev_info->cal_resistors_per_cs = true; + + if (of_device_is_compatible(np_ddr , "jedec,lpddr2-s4")) + dev_info->type = DDR_TYPE_LPDDR2_S4; + else if (of_device_is_compatible(np_ddr , "jedec,lpddr2-s2")) + dev_info->type = DDR_TYPE_LPDDR2_S2; + + of_property_read_u32(np_ddr, "density", &density); + of_property_read_u32(np_ddr, "io-width", &io_width); + + /* Convert from density in Mb to the density encoding in jedc_ddr.h */ + if (density & (density - 1)) + dev_info->density = 0; + else + dev_info->density = __fls(density) - 5; + + /* Convert from io_width in bits to io_width encoding in jedc_ddr.h */ + if (io_width & (io_width - 1)) + dev_info->io_width = 0; + else + dev_info->io_width = __fls(io_width) - 1; +} + +static struct emif_data * __init_or_module of_get_memory_device_details( + struct device_node *np_emif, struct device *dev) +{ + struct emif_data *emif = NULL; + struct ddr_device_info *dev_info = NULL; + struct emif_platform_data *pd = NULL; + struct device_node *np_ddr; + int len; + + np_ddr = of_parse_phandle(np_emif, "device-handle", 0); + if (!np_ddr) + goto error; + emif = devm_kzalloc(dev, sizeof(struct emif_data), GFP_KERNEL); + pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL); + dev_info = devm_kzalloc(dev, sizeof(*dev_info), GFP_KERNEL); + + if (!emif || !pd || !dev_info) { + dev_err(dev, "%s: Out of memory!!\n", + __func__); + goto error; + } + + emif->plat_data = pd; + pd->device_info = dev_info; + emif->dev = dev; + emif->np_ddr = np_ddr; + emif->temperature_level = SDRAM_TEMP_NOMINAL; + + if (of_device_is_compatible(np_emif, "ti,emif-4d")) + emif->plat_data->ip_rev = EMIF_4D; + else if (of_device_is_compatible(np_emif, "ti,emif-4d5")) + emif->plat_data->ip_rev = EMIF_4D5; + + of_property_read_u32(np_emif, "phy-type", &pd->phy_type); + + if (of_find_property(np_emif, "hw-caps-ll-interface", &len)) + pd->hw_caps |= EMIF_HW_CAPS_LL_INTERFACE; + + of_get_ddr_info(np_emif, np_ddr, dev_info); + if (!is_dev_data_valid(pd->device_info->type, pd->device_info->density, + pd->device_info->io_width, pd->phy_type, pd->ip_rev, + emif->dev)) { + dev_err(dev, "%s: invalid device data!!\n", __func__); + goto error; + } + /* + * For EMIF instances other than EMIF1 see if the devices connected + * are exactly same as on EMIF1(which is typically the case). If so, + * mark it as a duplicate of EMIF1. This will save some memory and + * computation. + */ + if (emif1 && emif1->np_ddr == np_ddr) { + emif->duplicate = true; + goto out; + } else if (emif1) { + dev_warn(emif->dev, "%s: Non-symmetric DDR geometry\n", + __func__); + } + + of_get_custom_configs(np_emif, emif); + emif->plat_data->timings = of_get_ddr_timings(np_ddr, emif->dev, + emif->plat_data->device_info->type, + &emif->plat_data->timings_arr_size); + + emif->plat_data->min_tck = of_get_min_tck(np_ddr, emif->dev); + goto out; + +error: + return NULL; +out: + return emif; +} + +#else + +static struct emif_data * __init_or_module of_get_memory_device_details( + struct device_node *np_emif, struct device *dev) +{ + return NULL; +} +#endif + static struct emif_data *__init_or_module get_device_details( struct platform_device *pdev) { @@ -1267,7 +1444,11 @@ static int __init_or_module emif_probe(struct platform_device *pdev) struct resource *res; int irq; - emif = get_device_details(pdev); + if (pdev->dev.of_node) + emif = of_get_memory_device_details(pdev->dev.of_node, &pdev->dev); + else + emif = get_device_details(pdev); + if (!emif) { pr_err("%s: error getting device data\n", __func__); goto error; @@ -1644,11 +1825,21 @@ static void __attribute__((unused)) freq_post_notify_handling(void) spin_unlock_irqrestore(&emif_lock, irq_state); } +#if defined(CONFIG_OF) +static const struct of_device_id emif_of_match[] = { + { .compatible = "ti,emif-4d" }, + { .compatible = "ti,emif-4d5" }, + {}, +}; +MODULE_DEVICE_TABLE(of, emif_of_match); +#endif + static struct platform_driver emif_driver = { .remove = __exit_p(emif_remove), .shutdown = emif_shutdown, .driver = { .name = "emif", + .of_match_table = of_match_ptr(emif_of_match), }, }; diff --git a/drivers/memory/of_memory.c b/drivers/memory/of_memory.c new file mode 100644 index 00000000000..60074351f17 --- /dev/null +++ b/drivers/memory/of_memory.c @@ -0,0 +1,153 @@ +/* + * OpenFirmware helpers for memory drivers + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/list.h> +#include <linux/of.h> +#include <linux/gfp.h> +#include <memory/jedec_ddr.h> +#include <linux/export.h> + +/** + * of_get_min_tck() - extract min timing values for ddr + * @np: pointer to ddr device tree node + * @device: device requesting for min timing values + * + * Populates the lpddr2_min_tck structure by extracting data + * from device tree node. Returns a pointer to the populated + * structure. If any error in populating the structure, returns + * default min timings provided by JEDEC. + */ +const struct lpddr2_min_tck *of_get_min_tck(struct device_node *np, + struct device *dev) +{ + int ret = 0; + struct lpddr2_min_tck *min; + + min = devm_kzalloc(dev, sizeof(*min), GFP_KERNEL); + if (!min) + goto default_min_tck; + + ret |= of_property_read_u32(np, "tRPab-min-tck", &min->tRPab); + ret |= of_property_read_u32(np, "tRCD-min-tck", &min->tRCD); + ret |= of_property_read_u32(np, "tWR-min-tck", &min->tWR); + ret |= of_property_read_u32(np, "tRASmin-min-tck", &min->tRASmin); + ret |= of_property_read_u32(np, "tRRD-min-tck", &min->tRRD); + ret |= of_property_read_u32(np, "tWTR-min-tck", &min->tWTR); + ret |= of_property_read_u32(np, "tXP-min-tck", &min->tXP); + ret |= of_property_read_u32(np, "tRTP-min-tck", &min->tRTP); + ret |= of_property_read_u32(np, "tCKE-min-tck", &min->tCKE); + ret |= of_property_read_u32(np, "tCKESR-min-tck", &min->tCKESR); + ret |= of_property_read_u32(np, "tFAW-min-tck", &min->tFAW); + + if (ret) { + devm_kfree(dev, min); + goto default_min_tck; + } + + return min; + +default_min_tck: + dev_warn(dev, "%s: using default min-tck values\n", __func__); + return &lpddr2_jedec_min_tck; +} +EXPORT_SYMBOL(of_get_min_tck); + +static int of_do_get_timings(struct device_node *np, + struct lpddr2_timings *tim) +{ + int ret; + + ret = of_property_read_u32(np, "max-freq", &tim->max_freq); + ret |= of_property_read_u32(np, "min-freq", &tim->min_freq); + ret |= of_property_read_u32(np, "tRPab", &tim->tRPab); + ret |= of_property_read_u32(np, "tRCD", &tim->tRCD); + ret |= of_property_read_u32(np, "tWR", &tim->tWR); + ret |= of_property_read_u32(np, "tRAS-min", &tim->tRAS_min); + ret |= of_property_read_u32(np, "tRRD", &tim->tRRD); + ret |= of_property_read_u32(np, "tWTR", &tim->tWTR); + ret |= of_property_read_u32(np, "tXP", &tim->tXP); + ret |= of_property_read_u32(np, "tRTP", &tim->tRTP); + ret |= of_property_read_u32(np, "tCKESR", &tim->tCKESR); + ret |= of_property_read_u32(np, "tDQSCK-max", &tim->tDQSCK_max); + ret |= of_property_read_u32(np, "tFAW", &tim->tFAW); + ret |= of_property_read_u32(np, "tZQCS", &tim->tZQCS); + ret |= of_property_read_u32(np, "tZQCL", &tim->tZQCL); + ret |= of_property_read_u32(np, "tZQinit", &tim->tZQinit); + ret |= of_property_read_u32(np, "tRAS-max-ns", &tim->tRAS_max_ns); + ret |= of_property_read_u32(np, "tDQSCK-max-derated", + &tim->tDQSCK_max_derated); + + return ret; +} + +/** + * of_get_ddr_timings() - extracts the ddr timings and updates no of + * frequencies available. + * @np_ddr: Pointer to ddr device tree node + * @dev: Device requesting for ddr timings + * @device_type: Type of ddr(LPDDR2 S2/S4) + * @nr_frequencies: No of frequencies available for ddr + * (updated by this function) + * + * Populates lpddr2_timings structure by extracting data from device + * tree node. Returns pointer to populated structure. If any error + * while populating, returns default timings provided by JEDEC. + */ +const struct lpddr2_timings *of_get_ddr_timings(struct device_node *np_ddr, + struct device *dev, u32 device_type, u32 *nr_frequencies) +{ + struct lpddr2_timings *timings = NULL; + u32 arr_sz = 0, i = 0; + struct device_node *np_tim; + char *tim_compat; + + switch (device_type) { + case DDR_TYPE_LPDDR2_S2: + case DDR_TYPE_LPDDR2_S4: + tim_compat = "jedec,lpddr2-timings"; + break; + default: + dev_warn(dev, "%s: un-supported memory type\n", __func__); + } + + for_each_child_of_node(np_ddr, np_tim) + if (of_device_is_compatible(np_tim, tim_compat)) + arr_sz++; + + if (arr_sz) + timings = devm_kzalloc(dev, sizeof(*timings) * arr_sz, + GFP_KERNEL); + + if (!timings) + goto default_timings; + + for_each_child_of_node(np_ddr, np_tim) { + if (of_device_is_compatible(np_tim, tim_compat)) { + if (of_do_get_timings(np_tim, &timings[i])) { + devm_kfree(dev, timings); + goto default_timings; + } + i++; + } + } + + *nr_frequencies = arr_sz; + + return timings; + +default_timings: + dev_warn(dev, "%s: using default timings\n", __func__); + *nr_frequencies = ARRAY_SIZE(lpddr2_jedec_timings); + return lpddr2_jedec_timings; +} +EXPORT_SYMBOL(of_get_ddr_timings); diff --git a/drivers/memory/of_memory.h b/drivers/memory/of_memory.h new file mode 100644 index 00000000000..ef2514f553d --- /dev/null +++ b/drivers/memory/of_memory.h @@ -0,0 +1,36 @@ +/* + * OpenFirmware helpers for memory drivers + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __LINUX_MEMORY_OF_REG_H +#define __LINUX_MEMORY_OF_REG_H + +#if defined(CONFIG_OF) && defined(CONFIG_DDR) +extern const struct lpddr2_min_tck *of_get_min_tck(struct device_node *np, + struct device *dev); +extern const struct lpddr2_timings + *of_get_ddr_timings(struct device_node *np_ddr, struct device *dev, + u32 device_type, u32 *nr_frequencies); +#else +static inline const struct lpddr2_min_tck + *of_get_min_tck(struct device_node *np, struct device *dev) +{ + return NULL; +} + +static inline const struct lpddr2_timings + *of_get_ddr_timings(struct device_node *np_ddr, struct device *dev, + u32 device_type, u32 *nr_frequencies) +{ + return NULL; +} +#endif /* CONFIG_OF && CONFIG_DDR */ + +#endif /* __LINUX_MEMORY_OF_REG_ */ diff --git a/drivers/memory/tegra20-mc.c b/drivers/memory/tegra20-mc.c index 3ed49c1c2b9..e6764bb41cb 100644 --- a/drivers/memory/tegra20-mc.c +++ b/drivers/memory/tegra20-mc.c @@ -57,7 +57,7 @@ static inline u32 mc_readl(struct tegra20_mc *mc, u32 offs) if (offs < 0x24) val = readl(mc->regs[0] + offs); - if (offs < 0x400) + else if (offs < 0x400) val = readl(mc->regs[1] + offs - 0x3c); return val; @@ -65,14 +65,10 @@ static inline u32 mc_readl(struct tegra20_mc *mc, u32 offs) static inline void mc_writel(struct tegra20_mc *mc, u32 val, u32 offs) { - if (offs < 0x24) { + if (offs < 0x24) writel(val, mc->regs[0] + offs); - return; - } - if (offs < 0x400) { + else if (offs < 0x400) writel(val, mc->regs[1] + offs - 0x3c); - return; - } } static const char * const tegra20_mc_client[] = { diff --git a/drivers/memory/tegra30-mc.c b/drivers/memory/tegra30-mc.c index e56ff04eb5c..802b9ea431f 100644 --- a/drivers/memory/tegra30-mc.c +++ b/drivers/memory/tegra30-mc.c @@ -95,11 +95,11 @@ static inline u32 mc_readl(struct tegra30_mc *mc, u32 offs) if (offs < 0x10) val = readl(mc->regs[0] + offs); - if (offs < 0x1f0) + else if (offs < 0x1f0) val = readl(mc->regs[1] + offs - 0x3c); - if (offs < 0x228) + else if (offs < 0x228) val = readl(mc->regs[2] + offs - 0x200); - if (offs < 0x400) + else if (offs < 0x400) val = readl(mc->regs[3] + offs - 0x284); return val; @@ -107,22 +107,14 @@ static inline u32 mc_readl(struct tegra30_mc *mc, u32 offs) static inline void mc_writel(struct tegra30_mc *mc, u32 val, u32 offs) { - if (offs < 0x10) { + if (offs < 0x10) writel(val, mc->regs[0] + offs); - return; - } - if (offs < 0x1f0) { + else if (offs < 0x1f0) writel(val, mc->regs[1] + offs - 0x3c); - return; - } - if (offs < 0x228) { + else if (offs < 0x228) writel(val, mc->regs[2] + offs - 0x200); - return; - } - if (offs < 0x400) { + else if (offs < 0x400) writel(val, mc->regs[3] + offs - 0x284); - return; - } } static const char * const tegra30_mc_client[] = { diff --git a/drivers/vme/bridges/vme_ca91cx42.c b/drivers/vme/bridges/vme_ca91cx42.c index e0df92ec44b..1425d22cf95 100644 --- a/drivers/vme/bridges/vme_ca91cx42.c +++ b/drivers/vme/bridges/vme_ca91cx42.c @@ -1603,7 +1603,7 @@ static int ca91cx42_probe(struct pci_dev *pdev, const struct pci_device_id *id) { int retval, i; u32 data; - struct list_head *pos = NULL; + struct list_head *pos = NULL, *n; struct vme_bridge *ca91cx42_bridge; struct ca91cx42_driver *ca91cx42_device; struct vme_master_resource *master_image; @@ -1821,28 +1821,28 @@ err_reg: ca91cx42_crcsr_exit(ca91cx42_bridge, pdev); err_lm: /* resources are stored in link list */ - list_for_each(pos, &ca91cx42_bridge->lm_resources) { + list_for_each_safe(pos, n, &ca91cx42_bridge->lm_resources) { lm = list_entry(pos, struct vme_lm_resource, list); list_del(pos); kfree(lm); } err_dma: /* resources are stored in link list */ - list_for_each(pos, &ca91cx42_bridge->dma_resources) { + list_for_each_safe(pos, n, &ca91cx42_bridge->dma_resources) { dma_ctrlr = list_entry(pos, struct vme_dma_resource, list); list_del(pos); kfree(dma_ctrlr); } err_slave: /* resources are stored in link list */ - list_for_each(pos, &ca91cx42_bridge->slave_resources) { + list_for_each_safe(pos, n, &ca91cx42_bridge->slave_resources) { slave_image = list_entry(pos, struct vme_slave_resource, list); list_del(pos); kfree(slave_image); } err_master: /* resources are stored in link list */ - list_for_each(pos, &ca91cx42_bridge->master_resources) { + list_for_each_safe(pos, n, &ca91cx42_bridge->master_resources) { master_image = list_entry(pos, struct vme_master_resource, list); list_del(pos); @@ -1868,7 +1868,7 @@ err_struct: static void ca91cx42_remove(struct pci_dev *pdev) { - struct list_head *pos = NULL; + struct list_head *pos = NULL, *n; struct vme_master_resource *master_image; struct vme_slave_resource *slave_image; struct vme_dma_resource *dma_ctrlr; @@ -1905,28 +1905,28 @@ static void ca91cx42_remove(struct pci_dev *pdev) ca91cx42_crcsr_exit(ca91cx42_bridge, pdev); /* resources are stored in link list */ - list_for_each(pos, &ca91cx42_bridge->lm_resources) { + list_for_each_safe(pos, n, &ca91cx42_bridge->lm_resources) { lm = list_entry(pos, struct vme_lm_resource, list); list_del(pos); kfree(lm); } /* resources are stored in link list */ - list_for_each(pos, &ca91cx42_bridge->dma_resources) { + list_for_each_safe(pos, n, &ca91cx42_bridge->dma_resources) { dma_ctrlr = list_entry(pos, struct vme_dma_resource, list); list_del(pos); kfree(dma_ctrlr); } /* resources are stored in link list */ - list_for_each(pos, &ca91cx42_bridge->slave_resources) { + list_for_each_safe(pos, n, &ca91cx42_bridge->slave_resources) { slave_image = list_entry(pos, struct vme_slave_resource, list); list_del(pos); kfree(slave_image); } /* resources are stored in link list */ - list_for_each(pos, &ca91cx42_bridge->master_resources) { + list_for_each_safe(pos, n, &ca91cx42_bridge->master_resources) { master_image = list_entry(pos, struct vme_master_resource, list); list_del(pos); diff --git a/drivers/vme/bridges/vme_tsi148.c b/drivers/vme/bridges/vme_tsi148.c index 880d9242e34..5fbd08ffb9c 100644 --- a/drivers/vme/bridges/vme_tsi148.c +++ b/drivers/vme/bridges/vme_tsi148.c @@ -2350,7 +2350,7 @@ static int tsi148_probe(struct pci_dev *pdev, const struct pci_device_id *id) { int retval, i, master_num; u32 data; - struct list_head *pos = NULL; + struct list_head *pos = NULL, *n; struct vme_bridge *tsi148_bridge; struct tsi148_driver *tsi148_device; struct vme_master_resource *master_image; @@ -2615,28 +2615,28 @@ err_reg: err_crcsr: err_lm: /* resources are stored in link list */ - list_for_each(pos, &tsi148_bridge->lm_resources) { + list_for_each_safe(pos, n, &tsi148_bridge->lm_resources) { lm = list_entry(pos, struct vme_lm_resource, list); list_del(pos); kfree(lm); } err_dma: /* resources are stored in link list */ - list_for_each(pos, &tsi148_bridge->dma_resources) { + list_for_each_safe(pos, n, &tsi148_bridge->dma_resources) { dma_ctrlr = list_entry(pos, struct vme_dma_resource, list); list_del(pos); kfree(dma_ctrlr); } err_slave: /* resources are stored in link list */ - list_for_each(pos, &tsi148_bridge->slave_resources) { + list_for_each_safe(pos, n, &tsi148_bridge->slave_resources) { slave_image = list_entry(pos, struct vme_slave_resource, list); list_del(pos); kfree(slave_image); } err_master: /* resources are stored in link list */ - list_for_each(pos, &tsi148_bridge->master_resources) { + list_for_each_safe(pos, n, &tsi148_bridge->master_resources) { master_image = list_entry(pos, struct vme_master_resource, list); list_del(pos); |