diff options
47 files changed, 2159 insertions, 227 deletions
diff --git a/Documentation/ABI/stable/sysfs-driver-w1_ds28e04 b/Documentation/ABI/stable/sysfs-driver-w1_ds28e04 new file mode 100644 index 0000000..26579ee --- /dev/null +++ b/Documentation/ABI/stable/sysfs-driver-w1_ds28e04 @@ -0,0 +1,15 @@ +What: /sys/bus/w1/devices/.../pio +Date: May 2012 +Contact: Markus Franke <franm@hrz.tu-chemnitz.de> +Description: read/write the contents of the two PIO's of the DS28E04-100 + see Documentation/w1/slaves/w1_ds28e04 for detailed information +Users: any user space application which wants to communicate with DS28E04-100 + + + +What: /sys/bus/w1/devices/.../eeprom +Date: May 2012 +Contact: Markus Franke <franm@hrz.tu-chemnitz.de> +Description: read/write the contents of the EEPROM memory of the DS28E04-100 + see Documentation/w1/slaves/w1_ds28e04 for detailed information +Users: any user space application which wants to communicate with DS28E04-100 diff --git a/Documentation/ABI/testing/dev-kmsg b/Documentation/ABI/testing/dev-kmsg index 281ecc5..7e7e07a 100644 --- a/Documentation/ABI/testing/dev-kmsg +++ b/Documentation/ABI/testing/dev-kmsg @@ -58,16 +58,18 @@ Description: The /dev/kmsg character device node provides userspace access The output format consists of a prefix carrying the syslog prefix including priority and facility, the 64 bit message - sequence number and the monotonic timestamp in microseconds. - The values are separated by a ','. Future extensions might - add more comma separated values before the terminating ';'. - Unknown values should be gracefully ignored. + sequence number and the monotonic timestamp in microseconds, + and a flag field. All fields are separated by a ','. + + Future extensions might add more comma separated values before + the terminating ';'. Unknown fields and values should be + gracefully ignored. The human readable text string starts directly after the ';' and is terminated by a '\n'. Untrusted values derived from hardware or other facilities are printed, therefore - all non-printable characters in the log message are escaped - by "\x00" C-style hex encoding. + all non-printable characters and '\' itself in the log message + are escaped by "\x00" C-style hex encoding. A line starting with ' ', is a continuation line, adding key/value pairs to the log message, which provide the machine @@ -75,11 +77,11 @@ Description: The /dev/kmsg character device node provides userspace access userspace. Example: - 7,160,424069;pci_root PNP0A03:00: host bridge window [io 0x0000-0x0cf7] (ignored) + 7,160,424069,-;pci_root PNP0A03:00: host bridge window [io 0x0000-0x0cf7] (ignored) SUBSYSTEM=acpi DEVICE=+acpi:PNP0A03:00 - 6,339,5140900;NET: Registered protocol family 10 - 30,340,5690716;udevd[80]: starting version 181 + 6,339,5140900,-;NET: Registered protocol family 10 + 30,340,5690716,-;udevd[80]: starting version 181 The DEVICE= key uniquely identifies devices the following way: b12:8 - block dev_t @@ -87,4 +89,13 @@ Description: The /dev/kmsg character device node provides userspace access n8 - netdev ifindex +sound:card0 - subsystem:devname + The flags field carries '-' by default. A 'c' indicates a + fragment of a line. All following fragments are flagged with + '+'. Note, that these hints about continuation lines are not + neccessarily correct, and the stream could be interleaved with + unrelated messages, but merging the lines in the output + usually produces better human readable results. A similar + logic is used internally when messages are printed to the + console, /proc/kmsg or the syslog() syscall. + Users: dmesg(1), userspace kernel log consumers diff --git a/Documentation/stable_kernel_rules.txt b/Documentation/stable_kernel_rules.txt index 4a7b54b..b0714d8 100644 --- a/Documentation/stable_kernel_rules.txt +++ b/Documentation/stable_kernel_rules.txt @@ -1,4 +1,4 @@ -Everything you ever wanted to know about Linux 2.6 -stable releases. +Everything you ever wanted to know about Linux -stable releases. Rules on what kind of patches are accepted, and which ones are not, into the "-stable" tree: @@ -42,10 +42,10 @@ Procedure for submitting patches to the -stable tree: cherry-picked than this can be specified in the following format in the sign-off area: - Cc: <stable@vger.kernel.org> # .32.x: a1f84a3: sched: Check for idle - Cc: <stable@vger.kernel.org> # .32.x: 1b9508f: sched: Rate-limit newidle - Cc: <stable@vger.kernel.org> # .32.x: fd21073: sched: Fix affinity logic - Cc: <stable@vger.kernel.org> # .32.x + Cc: <stable@vger.kernel.org> # 3.3.x: a1f84a3: sched: Check for idle + Cc: <stable@vger.kernel.org> # 3.3.x: 1b9508f: sched: Rate-limit newidle + Cc: <stable@vger.kernel.org> # 3.3.x: fd21073: sched: Fix affinity logic + Cc: <stable@vger.kernel.org> # 3.3.x Signed-off-by: Ingo Molnar <mingo@elte.hu> The tag sequence has the meaning of: @@ -79,6 +79,15 @@ Review cycle: security kernel team, and not go through the normal review cycle. Contact the kernel security team for more details on this procedure. +Trees: + + - The queues of patches, for both completed versions and in progress + versions can be found at: + http://git.kernel.org/?p=linux/kernel/git/stable/stable-queue.git + - The finalized and tagged releases of all stable kernels can be found + in separate branches per version at: + http://git.kernel.org/?p=linux/kernel/git/stable/linux-stable.git + Review committee: diff --git a/Documentation/w1/slaves/w1_ds28e04 b/Documentation/w1/slaves/w1_ds28e04 new file mode 100644 index 0000000..85bc9a7 --- /dev/null +++ b/Documentation/w1/slaves/w1_ds28e04 @@ -0,0 +1,36 @@ +Kernel driver w1_ds28e04 +======================== + +Supported chips: + * Maxim DS28E04-100 4096-Bit Addressable 1-Wire EEPROM with PIO + +supported family codes: + W1_FAMILY_DS28E04 0x1C + +Author: Markus Franke, <franke.m@sebakmt.com> <franm@hrz.tu-chemnitz.de> + +Description +----------- + +Support is provided through the sysfs files "eeprom" and "pio". CRC checking +during memory accesses can optionally be enabled/disabled via the device +attribute "crccheck". The strong pull-up can optionally be enabled/disabled +via the module parameter "w1_strong_pullup". + +Memory Access + + A read operation on the "eeprom" file reads the given amount of bytes + from the EEPROM of the DS28E04. + + A write operation on the "eeprom" file writes the given byte sequence + to the EEPROM of the DS28E04. If CRC checking mode is enabled only + fully alligned blocks of 32 bytes with valid CRC16 values (in bytes 30 + and 31) are allowed to be written. + +PIO Access + + The 2 PIOs of the DS28E04-100 are accessible via the "pio" sysfs file. + + The current status of the PIO's is returned as an 8 bit value. Bit 0/1 + represent the state of PIO_0/PIO_1. Bits 2..7 do not care. The PIO's are + driven low-active, i.e. the driver delivers/expects low-active values. diff --git a/MAINTAINERS b/MAINTAINERS index 9b8a5da..429e72c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2728,6 +2728,14 @@ M: Mimi Zohar <zohar@us.ibm.com> S: Supported F: security/integrity/evm/ +EXTERNAL CONNECTOR SUBSYSTEM (EXTCON) +M: MyungJoo Ham <myungjoo.ham@samsung.com> +M: Chanwoo Choi <cw00.choi@samsung.com> +L: linux-kernel@vger.kernel.org +S: Maintained +F: drivers/extcon/ +F: Documentation/extcon/ + EXYNOS DP DRIVER M: Jingoo Han <jg1.han@samsung.com> L: linux-fbdev@vger.kernel.org diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 2bcef65..181ed26 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -743,7 +743,6 @@ int bus_add_driver(struct device_driver *drv) } } - kobject_uevent(&priv->kobj, KOBJ_ADD); return 0; out_unregister: diff --git a/drivers/base/core.c b/drivers/base/core.c index 346be8b..f338037 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -85,14 +85,13 @@ const char *dev_driver_string(const struct device *dev) } EXPORT_SYMBOL(dev_driver_string); -#define to_dev(obj) container_of(obj, struct device, kobj) #define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr) static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct device_attribute *dev_attr = to_dev_attr(attr); - struct device *dev = to_dev(kobj); + struct device *dev = kobj_to_dev(kobj); ssize_t ret = -EIO; if (dev_attr->show) @@ -108,7 +107,7 @@ static ssize_t dev_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count) { struct device_attribute *dev_attr = to_dev_attr(attr); - struct device *dev = to_dev(kobj); + struct device *dev = kobj_to_dev(kobj); ssize_t ret = -EIO; if (dev_attr->store) @@ -182,7 +181,7 @@ EXPORT_SYMBOL_GPL(device_show_int); */ static void device_release(struct kobject *kobj) { - struct device *dev = to_dev(kobj); + struct device *dev = kobj_to_dev(kobj); struct device_private *p = dev->p; if (dev->release) @@ -200,7 +199,7 @@ static void device_release(struct kobject *kobj) static const void *device_namespace(struct kobject *kobj) { - struct device *dev = to_dev(kobj); + struct device *dev = kobj_to_dev(kobj); const void *ns = NULL; if (dev->class && dev->class->ns_type) @@ -221,7 +220,7 @@ static int dev_uevent_filter(struct kset *kset, struct kobject *kobj) struct kobj_type *ktype = get_ktype(kobj); if (ktype == &device_ktype) { - struct device *dev = to_dev(kobj); + struct device *dev = kobj_to_dev(kobj); if (dev->bus) return 1; if (dev->class) @@ -232,7 +231,7 @@ static int dev_uevent_filter(struct kset *kset, struct kobject *kobj) static const char *dev_uevent_name(struct kset *kset, struct kobject *kobj) { - struct device *dev = to_dev(kobj); + struct device *dev = kobj_to_dev(kobj); if (dev->bus) return dev->bus->name; @@ -244,7 +243,7 @@ static const char *dev_uevent_name(struct kset *kset, struct kobject *kobj) static int dev_uevent(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env) { - struct device *dev = to_dev(kobj); + struct device *dev = kobj_to_dev(kobj); int retval = 0; /* add device node properties if present */ @@ -1132,7 +1131,7 @@ int device_register(struct device *dev) */ struct device *get_device(struct device *dev) { - return dev ? to_dev(kobject_get(&dev->kobj)) : NULL; + return dev ? kobj_to_dev(kobject_get(&dev->kobj)) : NULL; } /** @@ -1754,25 +1753,25 @@ int device_move(struct device *dev, struct device *new_parent, set_dev_node(dev, dev_to_node(new_parent)); } - if (!dev->class) - goto out_put; - error = device_move_class_links(dev, old_parent, new_parent); - if (error) { - /* We ignore errors on cleanup since we're hosed anyway... */ - device_move_class_links(dev, new_parent, old_parent); - if (!kobject_move(&dev->kobj, &old_parent->kobj)) { - if (new_parent) - klist_remove(&dev->p->knode_parent); - dev->parent = old_parent; - if (old_parent) { - klist_add_tail(&dev->p->knode_parent, - &old_parent->p->klist_children); - set_dev_node(dev, dev_to_node(old_parent)); + if (dev->class) { + error = device_move_class_links(dev, old_parent, new_parent); + if (error) { + /* We ignore errors on cleanup since we're hosed anyway... */ + device_move_class_links(dev, new_parent, old_parent); + if (!kobject_move(&dev->kobj, &old_parent->kobj)) { + if (new_parent) + klist_remove(&dev->p->knode_parent); + dev->parent = old_parent; + if (old_parent) { + klist_add_tail(&dev->p->knode_parent, + &old_parent->p->klist_children); + set_dev_node(dev, dev_to_node(old_parent)); + } } + cleanup_glue_dir(dev, new_parent_kobj); + put_device(new_parent); + goto out; } - cleanup_glue_dir(dev, new_parent_kobj); - put_device(new_parent); - goto out; } switch (dpm_order) { case DPM_ORDER_NONE: @@ -1787,7 +1786,7 @@ int device_move(struct device *dev, struct device *new_parent, device_pm_move_last(dev); break; } -out_put: + put_device(old_parent); out: device_pm_unlock(); @@ -1812,6 +1811,13 @@ void device_shutdown(void) while (!list_empty(&devices_kset->list)) { dev = list_entry(devices_kset->list.prev, struct device, kobj.entry); + + /* + * hold reference count of device's parent to + * prevent it from being freed because parent's + * lock is to be held + */ + get_device(dev->parent); get_device(dev); /* * Make sure the device is off the kset list, in the @@ -1820,6 +1826,11 @@ void device_shutdown(void) list_del_init(&dev->kobj.entry); spin_unlock(&devices_kset->list_lock); + /* hold lock to avoid race with probe/release */ + if (dev->parent) + device_lock(dev->parent); + device_lock(dev); + /* Don't allow any more runtime suspends */ pm_runtime_get_noresume(dev); pm_runtime_barrier(dev); @@ -1831,7 +1842,13 @@ void device_shutdown(void) dev_dbg(dev, "shutdown\n"); dev->driver->shutdown(dev); } + + device_unlock(dev); + if (dev->parent) + device_unlock(dev->parent); + put_device(dev); + put_device(dev->parent); spin_lock(&devices_kset->list_lock); } diff --git a/drivers/base/dd.c b/drivers/base/dd.c index dcb8a6e..e3bbed8 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -85,8 +85,20 @@ static void deferred_probe_work_func(struct work_struct *work) * manipulate the deferred list */ mutex_unlock(&deferred_probe_mutex); + + /* + * Force the device to the end of the dpm_list since + * the PM code assumes that the order we add things to + * the list is a good order for suspend but deferred + * probe makes that very unsafe. + */ + device_pm_lock(); + device_pm_move_last(dev); + device_pm_unlock(); + dev_dbg(dev, "Retrying from deferred list\n"); bus_probe_device(dev); + mutex_lock(&deferred_probe_mutex); put_device(dev); @@ -283,6 +295,7 @@ probe_failed: devres_release_all(dev); driver_sysfs_remove(dev); dev->driver = NULL; + dev_set_drvdata(dev, NULL); if (ret == -EPROBE_DEFER) { /* Driver requested deferred probing */ @@ -356,10 +369,9 @@ int driver_probe_device(struct device_driver *drv, struct device *dev) pr_debug("bus: '%s': %s: matched device %s with driver %s\n", drv->bus->name, __func__, dev_name(dev), drv->name); - pm_runtime_get_noresume(dev); pm_runtime_barrier(dev); ret = really_probe(dev, drv); - pm_runtime_put_sync(dev); + pm_runtime_idle(dev); return ret; } @@ -406,9 +418,8 @@ int device_attach(struct device *dev) ret = 0; } } else { - pm_runtime_get_noresume(dev); ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach); - pm_runtime_put_sync(dev); + pm_runtime_idle(dev); } out_unlock: device_unlock(dev); @@ -487,6 +498,7 @@ static void __device_release_driver(struct device *dev) drv->remove(dev); devres_release_all(dev); dev->driver = NULL; + dev_set_drvdata(dev, NULL); klist_remove(&dev->p->knode_driver); if (dev->bus) blocking_notifier_call_chain(&dev->bus->p->bus_notifier, diff --git a/drivers/base/dma-buf.c b/drivers/base/dma-buf.c index 24e88fe..c30f3e1 100644 --- a/drivers/base/dma-buf.c +++ b/drivers/base/dma-buf.c @@ -493,6 +493,7 @@ EXPORT_SYMBOL_GPL(dma_buf_vmap); /** * dma_buf_vunmap - Unmap a vmap obtained by dma_buf_vmap. * @dmabuf: [in] buffer to vunmap + * @vaddr: [in] vmap to vunmap */ void dma_buf_vunmap(struct dma_buf *dmabuf, void *vaddr) { diff --git a/drivers/base/dma-coherent.c b/drivers/base/dma-coherent.c index 1b85949..560a717 100644 --- a/drivers/base/dma-coherent.c +++ b/drivers/base/dma-coherent.c @@ -186,6 +186,7 @@ EXPORT_SYMBOL(dma_release_from_coherent); * @vma: vm_area for the userspace memory * @vaddr: cpu address returned by dma_alloc_from_coherent * @size: size of the memory buffer allocated by dma_alloc_from_coherent + * @ret: result from remap_pfn_range() * * This checks whether the memory was allocated from the per-device * coherent memory pool and if so, maps that memory to the provided vma. diff --git a/drivers/base/driver.c b/drivers/base/driver.c index 207c27d..974e301 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -185,8 +185,12 @@ int driver_register(struct device_driver *drv) if (ret) return ret; ret = driver_add_groups(drv, drv->groups); - if (ret) + if (ret) { bus_remove_driver(drv); + return ret; + } + kobject_uevent(&drv->p->kobj, KOBJ_ADD); + return ret; } EXPORT_SYMBOL_GPL(driver_register); diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 5401814..803cfc1 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -22,8 +22,6 @@ #include <linux/slab.h> #include <linux/sched.h> -#define to_dev(obj) container_of(obj, struct device, kobj) - MODULE_AUTHOR("Manuel Estrada Sainz"); MODULE_DESCRIPTION("Multi purpose firmware loading support"); MODULE_LICENSE("GPL"); @@ -290,7 +288,7 @@ static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj, struct bin_attribute *bin_attr, char *buffer, loff_t offset, size_t count) { - struct device *dev = to_dev(kobj); + struct device *dev = kobj_to_dev(kobj); struct firmware_priv *fw_priv = to_firmware_priv(dev); struct firmware *fw; ssize_t ret_count; @@ -384,7 +382,7 @@ static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj, struct bin_attribute *bin_attr, char *buffer, loff_t offset, size_t count) { - struct device *dev = to_dev(kobj); + struct device *dev = kobj_to_dev(kobj); struct firmware_priv *fw_priv = to_firmware_priv(dev); struct firmware *fw; ssize_t retval; diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 29c5cf8..1671635 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -21,6 +21,16 @@ config EXTCON_GPIO Say Y here to enable GPIO based extcon support. Note that GPIO extcon supports single state per extcon instance. +config EXTCON_MAX77693 + tristate "MAX77693 EXTCON Support" + depends on MFD_MAX77693 + select IRQ_DOMAIN + select REGMAP_I2C + help + If you say yes here you get support for the MUIC device of + Maxim MAX77693 PMIC. The MAX77693 MUIC is a USB port accessory + detector and switch. + config EXTCON_MAX8997 tristate "MAX8997 EXTCON Support" depends on MFD_MAX8997 @@ -29,4 +39,12 @@ config EXTCON_MAX8997 Maxim MAX8997 PMIC. The MAX8997 MUIC is a USB port accessory detector and switch. +config EXTCON_ARIZONA + tristate "Wolfson Arizona EXTCON support" + depends on MFD_ARIZONA + help + Say Y here to enable support for external accessory detection + with Wolfson Arizona devices. These are audio CODECs with + advanced audio accessory detection support. + endif # MULTISTATE_SWITCH diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 86020bd..88961b3 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -4,4 +4,6 @@ obj-$(CONFIG_EXTCON) += extcon_class.o obj-$(CONFIG_EXTCON_GPIO) += extcon_gpio.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-arizona.c b/drivers/extcon/extcon-arizona.c new file mode 100644 index 0000000..427a289 --- /dev/null +++ b/drivers/extcon/extcon-arizona.c @@ -0,0 +1,490 @@ +/* + * extcon-arizona.c - Extcon driver Wolfson Arizona devices + * + * Copyright (C) 2012 Wolfson Microelectronics plc + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/extcon.h> + +#include <linux/mfd/arizona/core.h> +#include <linux/mfd/arizona/pdata.h> +#include <linux/mfd/arizona/registers.h> + +struct arizona_extcon_info { + struct device *dev; + struct arizona *arizona; + struct mutex lock; + struct regulator *micvdd; + + int micd_mode; + const struct arizona_micd_config *micd_modes; + int micd_num_modes; + + bool micd_reva; + + bool mic; + bool detecting; + int jack_flips; + + struct extcon_dev edev; +}; + +static const struct arizona_micd_config micd_default_modes[] = { + { ARIZONA_ACCDET_SRC, 1 << ARIZONA_MICD_BIAS_SRC_SHIFT, 0 }, + { 0, 2 << ARIZONA_MICD_BIAS_SRC_SHIFT, 1 }, +}; + +#define ARIZONA_CABLE_MECHANICAL 0 +#define ARIZONA_CABLE_MICROPHONE 1 +#define ARIZONA_CABLE_HEADPHONE 2 + +static const char *arizona_cable[] = { + "Mechanical", + "Microphone", + "Headphone", + NULL, +}; + +static void arizona_extcon_set_mode(struct arizona_extcon_info *info, int mode) +{ + struct arizona *arizona = info->arizona; + + gpio_set_value_cansleep(arizona->pdata.micd_pol_gpio, + info->micd_modes[mode].gpio); + regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1, + ARIZONA_MICD_BIAS_SRC_MASK, + info->micd_modes[mode].bias); + regmap_update_bits(arizona->regmap, ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_SRC, info->micd_modes[mode].src); + + info->micd_mode = mode; + + dev_dbg(arizona->dev, "Set jack polarity to %d\n", mode); +} + +static void arizona_start_mic(struct arizona_extcon_info *info) +{ + struct arizona *arizona = info->arizona; + bool change; + int ret; + + info->detecting = true; + info->mic = false; + info->jack_flips = 0; + + /* Microphone detection can't use idle mode */ + pm_runtime_get(info->dev); + + ret = regulator_enable(info->micvdd); + if (ret != 0) { + dev_err(arizona->dev, "Failed to enable MICVDD: %d\n", + ret); + } + + if (info->micd_reva) { + regmap_write(arizona->regmap, 0x80, 0x3); + regmap_write(arizona->regmap, 0x294, 0); + regmap_write(arizona->regmap, 0x80, 0x0); + } + + regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1, + ARIZONA_MICD_ENA, ARIZONA_MICD_ENA, + &change); + if (!change) { + regulator_disable(info->micvdd); + pm_runtime_put_autosuspend(info->dev); + } +} + +static void arizona_stop_mic(struct arizona_extcon_info *info) +{ + struct arizona *arizona = info->arizona; + bool change; + + regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1, + ARIZONA_MICD_ENA, 0, + &change); + + if (info->micd_reva) { + regmap_write(arizona->regmap, 0x80, 0x3); + regmap_write(arizona->regmap, 0x294, 2); + regmap_write(arizona->regmap, 0x80, 0x0); + } + + if (change) { + regulator_disable(info->micvdd); + pm_runtime_put_autosuspend(info->dev); + } +} + +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; + + mutex_lock(&info->lock); + + ret = regmap_read(arizona->regmap, ARIZONA_MIC_DETECT_3, &val); + if (ret != 0) { + dev_err(arizona->dev, "Failed to read MICDET: %d\n", ret); + return IRQ_NONE; + } + + dev_dbg(arizona->dev, "MICDET: %x\n", val); + + if (!(val & ARIZONA_MICD_VALID)) { + dev_warn(arizona->dev, "Microphone detection state invalid\n"); + mutex_unlock(&info->lock); + return IRQ_NONE; + } + + /* Due to jack detect this should never happen */ + if (!(val & ARIZONA_MICD_STS)) { + dev_warn(arizona->dev, "Detected open circuit\n"); + info->detecting = false; + goto handled; + } + + /* If we got a high impedence we should have a headset, report it. */ + if (info->detecting && (val & 0x400)) { + ret = extcon_update_state(&info->edev, + 1 << ARIZONA_CABLE_MICROPHONE | + 1 << ARIZONA_CABLE_HEADPHONE, + 1 << ARIZONA_CABLE_MICROPHONE | + 1 << ARIZONA_CABLE_HEADPHONE); + + if (ret != 0) + dev_err(arizona->dev, "Headset report failed: %d\n", + ret); + + info->mic = true; + info->detecting = false; + goto handled; + } + + /* If we detected a lower impedence during initial startup + * then we probably have the wrong polarity, flip it. Don't + * do this for the lowest impedences to speed up detection of + * plain headphones. If both polarities report a low + * impedence then give up and report headphones. + */ + if (info->detecting && (val & 0x3f8)) { + info->jack_flips++; + + if (info->jack_flips >= info->micd_num_modes) { + dev_dbg(arizona->dev, "Detected headphone\n"); + info->detecting = false; + arizona_stop_mic(info); + + ret = extcon_set_cable_state_(&info->edev, + ARIZONA_CABLE_HEADPHONE, + true); + if (ret != 0) + dev_err(arizona->dev, + "Headphone report failed: %d\n", + ret); + } else { + info->micd_mode++; + if (info->micd_mode == info->micd_num_modes) + info->micd_mode = 0; + arizona_extcon_set_mode(info, info->micd_mode); + + info->jack_flips++; + } + + goto handled; + } + + /* + * 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. + */ + if (val & 0x3fc) { + if (info->mic) { + dev_dbg(arizona->dev, "Mic button detected\n"); + + } else if (info->detecting) { + dev_dbg(arizona->dev, "Headphone detected\n"); + info->detecting = false; + arizona_stop_mic(info); + + ret = extcon_set_cable_state_(&info->edev, + ARIZONA_CABLE_HEADPHONE, + true); + if (ret != 0) + dev_err(arizona->dev, + "Headphone report failed: %d\n", + ret); + } else { + dev_warn(arizona->dev, "Button with no mic: %x\n", + val); + } + } else { + dev_dbg(arizona->dev, "Mic button released\n"); + } + +handled: + pm_runtime_mark_last_busy(info->dev); + mutex_unlock(&info->lock); + + return IRQ_HANDLED; +} + +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; + + pm_runtime_get_sync(info->dev); + + mutex_lock(&info->lock); + + ret = regmap_read(arizona->regmap, ARIZONA_AOD_IRQ_RAW_STATUS, &val); + if (ret != 0) { + dev_err(arizona->dev, "Failed to read jackdet status: %d\n", + ret); + mutex_unlock(&info->lock); + pm_runtime_put_autosuspend(info->dev); + return IRQ_NONE; + } + + if (val & ARIZONA_JD1_STS) { + dev_dbg(arizona->dev, "Detected jack\n"); + ret = extcon_set_cable_state_(&info->edev, + ARIZONA_CABLE_MECHANICAL, true); + + if (ret != 0) + dev_err(arizona->dev, "Mechanical report failed: %d\n", + ret); + + arizona_start_mic(info); + } else { + dev_dbg(arizona->dev, "Detected jack removal\n"); + + arizona_stop_mic(info); + + ret = extcon_update_state(&info->edev, 0xffffffff, 0); + if (ret != 0) + dev_err(arizona->dev, "Removal report failed: %d\n", + ret); + } + + mutex_unlock(&info->lock); + + pm_runtime_mark_last_busy(info->dev); + pm_runtime_put_autosuspend(info->dev); + + return IRQ_HANDLED; +} + +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; + + 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"); + ret = -ENOMEM; + goto err; + } + + info->micvdd = devm_regulator_get(arizona->dev, "MICVDD"); + if (IS_ERR(info->micvdd)) { + ret = PTR_ERR(info->micvdd); + dev_err(arizona->dev, "Failed to get MICVDD: %d\n", ret); + goto err; + } + + mutex_init(&info->lock); + info->arizona = arizona; + info->dev = &pdev->dev; + info->detecting = true; + platform_set_drvdata(pdev, info); + + switch (arizona->type) { + case WM5102: + switch (arizona->rev) { + case 0: + info->micd_reva = true; + break; + default: + break; + } + break; + default: + break; + } + + info->edev.name = "Headset Jack"; + info->edev.supported_cable = arizona_cable; + + ret = extcon_dev_register(&info->edev, arizona->dev); + if (ret < 0) { + dev_err(arizona->dev, "extcon_dev_regster() failed: %d\n", + ret); + goto err; + } + + if (pdata->num_micd_configs) { + info->micd_modes = pdata->micd_configs; + info->micd_num_modes = pdata->num_micd_configs; + } else { + info->micd_modes = micd_default_modes; + info->micd_num_modes = ARRAY_SIZE(micd_default_modes); + } + + if (arizona->pdata.micd_pol_gpio > 0) { + if (info->micd_modes[0].gpio) + mode = GPIOF_OUT_INIT_HIGH; + else + mode = GPIOF_OUT_INIT_LOW; + + ret = devm_gpio_request_one(&pdev->dev, + arizona->pdata.micd_pol_gpio, + mode, + "MICD polarity"); + if (ret != 0) { + dev_err(arizona->dev, "Failed to request GPIO%d: %d\n", + arizona->pdata.micd_pol_gpio, ret); + goto err_register; + } + } + + arizona_extcon_set_mode(info, 0); + + pm_runtime_enable(&pdev->dev); + pm_runtime_idle(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + + ret = arizona_request_irq(arizona, ARIZONA_IRQ_JD_RISE, + "JACKDET rise", arizona_jackdet, info); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to get JACKDET rise IRQ: %d\n", + ret); + goto err_register; + } + + ret = arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 1); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to set JD rise IRQ wake: %d\n", + ret); + goto err_rise; + } + + ret = arizona_request_irq(arizona, ARIZONA_IRQ_JD_FALL, + "JACKDET fall", arizona_jackdet, info); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to get JD fall IRQ: %d\n", ret); + goto err_rise_wake; + } + + ret = arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 1); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to set JD fall IRQ wake: %d\n", + ret); + goto err_fall; + } + + ret = arizona_request_irq(arizona, ARIZONA_IRQ_MICDET, + "MICDET", arizona_micdet, info); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to get MICDET IRQ: %d\n", ret); + goto err_fall_wake; + } + + regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1, + ARIZONA_MICD_BIAS_STARTTIME_MASK | + ARIZONA_MICD_RATE_MASK, + 7 << ARIZONA_MICD_BIAS_STARTTIME_SHIFT | + 8 << ARIZONA_MICD_RATE_SHIFT); + + arizona_clk32k_enable(arizona); + regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_DEBOUNCE, + ARIZONA_JD1_DB, ARIZONA_JD1_DB); + regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE, + ARIZONA_JD1_ENA, ARIZONA_JD1_ENA); + + pm_runtime_put(&pdev->dev); + + return 0; + +err_fall_wake: + arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 0); +err_fall: + arizona_free_irq(arizona, ARIZONA_IRQ_JD_FALL, info); +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_register: + pm_runtime_disable(&pdev->dev); + extcon_dev_unregister(&info->edev); +err: + return ret; +} + +static int __devexit arizona_extcon_remove(struct platform_device *pdev) +{ + struct arizona_extcon_info *info = platform_get_drvdata(pdev); + struct arizona *arizona = info->arizona; + + pm_runtime_disable(&pdev->dev); + + arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 0); + arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 0); + arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info); + arizona_free_irq(arizona, ARIZONA_IRQ_JD_RISE, info); + arizona_free_irq(arizona, ARIZONA_IRQ_JD_FALL, info); + regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE, + ARIZONA_JD1_ENA, 0); + arizona_clk32k_disable(arizona); + extcon_dev_unregister(&info->edev); + + return 0; +} + +static struct platform_driver arizona_extcon_driver = { + .driver = { + .name = "arizona-extcon", + .owner = THIS_MODULE, + }, + .probe = arizona_extcon_probe, + .remove = __devexit_p(arizona_extcon_remove), +}; + +module_platform_driver(arizona_extcon_driver); + +MODULE_DESCRIPTION("Arizona Extcon driver"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:extcon-arizona"); diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c new file mode 100644 index 0000000..920a609 --- /dev/null +++ b/drivers/extcon/extcon-max77693.c @@ -0,0 +1,779 @@ +/* + * extcon-max77693.c - MAX77693 extcon driver to support MAX77693 MUIC + * + * Copyright (C) 2012 Samsung Electrnoics + * Chanwoo Choi <cw00.choi@samsung.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/mfd/max77693.h> +#include <linux/mfd/max77693-private.h> +#include <linux/extcon.h> +#include <linux/regmap.h> +#include <linux/irqdomain.h> + +#define DEV_NAME "max77693-muic" + +/* MAX77693 MUIC - STATUS1~3 Register */ +#define STATUS1_ADC_SHIFT (0) +#define STATUS1_ADCLOW_SHIFT (5) +#define STATUS1_ADCERR_SHIFT (6) +#define STATUS1_ADC1K_SHIFT (7) +#define STATUS1_ADC_MASK (0x1f << STATUS1_ADC_SHIFT) +#define STATUS1_ADCLOW_MASK (0x1 << STATUS1_ADCLOW_SHIFT) +#define STATUS1_ADCERR_MASK (0x1 << STATUS1_ADCERR_SHIFT) +#define STATUS1_ADC1K_MASK (0x1 << STATUS1_ADC1K_SHIFT) + +#define STATUS2_CHGTYP_SHIFT (0) +#define STATUS2_CHGDETRUN_SHIFT (3) +#define STATUS2_DCDTMR_SHIFT (4) +#define STATUS2_DXOVP_SHIFT (5) +#define STATUS2_VBVOLT_SHIFT (6) +#define STATUS2_VIDRM_SHIFT (7) +#define STATUS2_CHGTYP_MASK (0x7 << STATUS2_CHGTYP_SHIFT) +#define STATUS2_CHGDETRUN_MASK (0x1 << STATUS2_CHGDETRUN_SHIFT) +#define STATUS2_DCDTMR_MASK (0x1 << STATUS2_DCDTMR_SHIFT) +#define STATUS2_DXOVP_MASK (0x1 << STATUS2_DXOVP_SHIFT) +#define STATUS2_VBVOLT_MASK (0x1 << STATUS2_VBVOLT_SHIFT) +#define STATUS2_VIDRM_MASK (0x1 << STATUS2_VIDRM_SHIFT) + +#define STATUS3_OVP_SHIFT (2) +#define STATUS3_OVP_MASK (0x1 << STATUS3_OVP_SHIFT) + +/* MAX77693 CDETCTRL1~2 register */ +#define CDETCTRL1_CHGDETEN_SHIFT (0) +#define CDETCTRL1_CHGTYPMAN_SHIFT (1) +#define CDETCTRL1_DCDEN_SHIFT (2) +#define CDETCTRL1_DCD2SCT_SHIFT (3) +#define CDETCTRL1_CDDELAY_SHIFT (4) +#define CDETCTRL1_DCDCPL_SHIFT (5) +#define CDETCTRL1_CDPDET_SHIFT (7) +#define CDETCTRL1_CHGDETEN_MASK (0x1 << CDETCTRL1_CHGDETEN_SHIFT) +#define CDETCTRL1_CHGTYPMAN_MASK (0x1 << CDETCTRL1_CHGTYPMAN_SHIFT) +#define CDETCTRL1_DCDEN_MASK (0x1 << CDETCTRL1_DCDEN_SHIFT) +#define CDETCTRL1_DCD2SCT_MASK (0x1 << CDETCTRL1_DCD2SCT_SHIFT) +#define CDETCTRL1_CDDELAY_MASK (0x1 << CDETCTRL1_CDDELAY_SHIFT) +#define CDETCTRL1_DCDCPL_MASK (0x1 << CDETCTRL1_DCDCPL_SHIFT) +#define CDETCTRL1_CDPDET_MASK (0x1 << CDETCTRL1_CDPDET_SHIFT) + +#define CDETCTRL2_VIDRMEN_SHIFT (1) +#define CDETCTRL2_DXOVPEN_SHIFT (3) +#define CDETCTRL2_VIDRMEN_MASK (0x1 << CDETCTRL2_VIDRMEN_SHIFT) +#define CDETCTRL2_DXOVPEN_MASK (0x1 << CDETCTRL2_DXOVPEN_SHIFT) + +/* MAX77693 MUIC - CONTROL1~3 register */ +#define COMN1SW_SHIFT (0) +#define COMP2SW_SHIFT (3) +#define COMN1SW_MASK (0x7 << COMN1SW_SHIFT) +#define COMP2SW_MASK (0x7 << COMP2SW_SHIFT) +#define COMP_SW_MASK (COMP2SW_MASK | COMN1SW_MASK) +#define CONTROL1_SW_USB ((1 << COMP2SW_SHIFT) \ + | (1 << COMN1SW_SHIFT)) +#define CONTROL1_SW_AUDIO ((2 << COMP2SW_SHIFT) \ + | (2 << COMN1SW_SHIFT)) +#define CONTROL1_SW_UART ((3 << COMP2SW_SHIFT) \ + | (3 << COMN1SW_SHIFT)) +#define CONTROL1_SW_OPEN ((0 << COMP2SW_SHIFT) \ + | (0 << COMN1SW_SHIFT)) + +#define CONTROL2_LOWPWR_SHIFT (0) +#define CONTROL2_ADCEN_SHIFT (1) +#define CONTROL2_CPEN_SHIFT (2) +#define CONTROL2_SFOUTASRT_SHIFT (3) +#define CONTROL2_SFOUTORD_SHIFT (4) +#define CONTROL2_ACCDET_SHIFT (5) +#define CONTROL2_USBCPINT_SHIFT (6) +#define CONTROL2_RCPS_SHIFT (7) +#define CONTROL2_LOWPWR_MASK (0x1 << CONTROL2_LOWPWR_SHIFT) +#define CONTROL2_ADCEN_MASK (0x1 << CONTROL2_ADCEN_SHIFT) +#define CONTROL2_CPEN_MASK (0x1 << CONTROL2_CPEN_SHIFT) +#define CONTROL2_SFOUTASRT_MASK (0x1 << CONTROL2_SFOUTASRT_SHIFT) +#define CONTROL2_SFOUTORD_MASK (0x1 << CONTROL2_SFOUTORD_SHIFT) +#define CONTROL2_ACCDET_MASK (0x1 << CONTROL2_ACCDET_SHIFT) +#define CONTROL2_USBCPINT_MASK (0x1 << CONTROL2_USBCPINT_SHIFT) +#define CONTROL2_RCPS_MASK (0x1 << CONTROL2_RCPS_SHIFT) + +#define CONTROL3_JIGSET_SHIFT (0) +#define CONTROL3_BTLDSET_SHIFT (2) +#define CONTROL3_ADCDBSET_SHIFT (4) +#define CONTROL3_JIGSET_MASK (0x3 << CONTROL3_JIGSET_SHIFT) +#define CONTROL3_BTLDSET_MASK (0x3 << CONTROL3_BTLDSET_SHIFT) +#define CONTROL3_ADCDBSET_MASK (0x3 << CONTROL3_ADCDBSET_SHIFT) + +enum max77693_muic_adc_debounce_time { + ADC_DEBOUNCE_TIME_5MS = 0, + ADC_DEBOUNCE_TIME_10MS, + ADC_DEBOUNCE_TIME_25MS, + ADC_DEBOUNCE_TIME_38_62MS, +}; + +struct max77693_muic_info { + struct device *dev; + struct max77693_dev *max77693; + struct extcon_dev *edev; + int prev_adc; + int prev_adc_gnd; + int prev_chg_type; + u8 status[2]; + + int irq; + struct work_struct irq_work; + struct mutex mutex; +}; + +enum max77693_muic_charger_type { + MAX77693_CHARGER_TYPE_NONE = 0, + MAX77693_CHARGER_TYPE_USB, + MAX77693_CHARGER_TYPE_DOWNSTREAM_PORT, + MAX77693_CHARGER_TYPE_DEDICATED_CHG, + MAX77693_CHARGER_TYPE_APPLE_500MA, + MAX77693_CHARGER_TYPE_APPLE_1A_2A, + MAX77693_CHARGER_TYPE_DEAD_BATTERY = 7, +}; + +/** + * struct max77693_muic_irq + * @irq: the index of irq list of MUIC device. + * @name: the name of irq. + * @virq: the virtual irq to use irq domain + */ +struct max77693_muic_irq { + unsigned int irq; + const char *name; + unsigned int virq; +}; + +static struct max77693_muic_irq muic_irqs[] = { + { MAX77693_MUIC_IRQ_INT1_ADC, "muic-ADC" }, + { MAX77693_MUIC_IRQ_INT1_ADC_LOW, "muic-ADCLOW" }, + { MAX77693_MUIC_IRQ_INT1_ADC_ERR, "muic-ADCError" }, + { MAX77693_MUIC_IRQ_INT1_ADC1K, "muic-ADC1K" }, + { MAX77693_MUIC_IRQ_INT2_CHGTYP, "muic-CHGTYP" }, + { MAX77693_MUIC_IRQ_INT2_CHGDETREUN, "muic-CHGDETREUN" }, + { MAX77693_MUIC_IRQ_INT2_DCDTMR, "muic-DCDTMR" }, + { MAX77693_MUIC_IRQ_INT2_DXOVP, "muic-DXOVP" }, + { MAX77693_MUIC_IRQ_INT2_VBVOLT, "muic-VBVOLT" }, + { MAX77693_MUIC_IRQ_INT2_VIDRM, "muic-VIDRM" }, + { MAX77693_MUIC_IRQ_INT3_EOC, "muic-EOC" }, + { MAX77693_MUIC_IRQ_INT3_CGMBC, "muic-CGMBC" }, + { MAX77693_MUIC_IRQ_INT3_OVP, "muic-OVP" }, + { MAX77693_MUIC_IRQ_INT3_MBCCHG_ERR, "muic-MBCCHG_ERR" }, + { MAX77693_MUIC_IRQ_INT3_CHG_ENABLED, "muic-CHG_ENABLED" }, + { MAX77693_MUIC_IRQ_INT3_BAT_DET, "muic-BAT_DET" }, +}; + +/* Define supported accessory type */ +enum max77693_muic_acc_type { + MAX77693_MUIC_ADC_GROUND = 0x0, + MAX77693_MUIC_ADC_SEND_END_BUTTON, + MAX77693_MUIC_ADC_REMOTE_S1_BUTTON, + MAX77693_MUIC_ADC_REMOTE_S2_BUTTON, + MAX77693_MUIC_ADC_REMOTE_S3_BUTTON, + MAX77693_MUIC_ADC_REMOTE_S4_BUTTON, + MAX77693_MUIC_ADC_REMOTE_S5_BUTTON, + MAX77693_MUIC_ADC_REMOTE_S6_BUTTON, + MAX77693_MUIC_ADC_REMOTE_S7_BUTTON, + MAX77693_MUIC_ADC_REMOTE_S8_BUTTON, + MAX77693_MUIC_ADC_REMOTE_S9_BUTTON, + MAX77693_MUIC_ADC_REMOTE_S10_BUTTON, + MAX77693_MUIC_ADC_REMOTE_S11_BUTTON, + MAX77693_MUIC_ADC_REMOTE_S12_BUTTON, + MAX77693_MUIC_ADC_RESERVED_ACC_1, + MAX77693_MUIC_ADC_RESERVED_ACC_2, + MAX77693_MUIC_ADC_RESERVED_ACC_3, + MAX77693_MUIC_ADC_RESERVED_ACC_4, + MAX77693_MUIC_ADC_RESERVED_ACC_5, + MAX77693_MUIC_ADC_CEA936_AUDIO, + MAX77693_MUIC_ADC_PHONE_POWERED_DEV, + MAX77693_MUIC_ADC_TTY_CONVERTER, + MAX77693_MUIC_ADC_UART_CABLE, + MAX77693_MUIC_ADC_CEA936A_TYPE1_CHG, + MAX77693_MUIC_ADC_FACTORY_MODE_USB_OFF, + MAX77693_MUIC_ADC_FACTORY_MODE_USB_ON, + MAX77693_MUIC_ADC_AV_CABLE_NOLOAD, + MAX77693_MUIC_ADC_CEA936A_TYPE2_CHG, + MAX77693_MUIC_ADC_FACTORY_MODE_UART_OFF, + MAX77693_MUIC_ADC_FACTORY_MODE_UART_ON, + MAX77693_MUIC_ADC_AUDIO_MODE_REMOTE, + MAX77693_MUIC_ADC_OPEN, + + /* The below accessories have same ADC value so ADCLow and + ADC1K bit is used to separate specific accessory */ + MAX77693_MUIC_GND_USB_OTG = 0x100, /* ADC:0x0, ADCLow:0, ADC1K:0 */ + MAX77693_MUIC_GND_AV_CABLE_LOAD = 0x102,/* ADC:0x0, ADCLow:1, ADC1K:0 */ + MAX77693_MUIC_GND_MHL_CABLE = 0x103, /* ADC:0x0, ADCLow:1, ADC1K:1 */ +}; + +/* MAX77693 MUIC device support below list of accessories(external connector) */ +const char *max77693_extcon_cable[] = { + [0] = "USB", + [1] = "USB-Host", + [2] = "TA", + [3] = "Fast-charger", + [4] = "Slow-charger", + [5] = "Charge-downstream", + [6] = "MHL", + [7] = "Audio-video-load", + [8] = "Audio-video-noload", + [9] = "JIG", + + NULL, +}; + +static int max77693_muic_set_debounce_time(struct max77693_muic_info *info, + enum max77693_muic_adc_debounce_time time) +{ + int ret = 0; + u8 ctrl3; + + switch (time) { + case ADC_DEBOUNCE_TIME_5MS: + case ADC_DEBOUNCE_TIME_10MS: + case ADC_DEBOUNCE_TIME_25MS: + case ADC_DEBOUNCE_TIME_38_62MS: + ret = max77693_read_reg(info->max77693->regmap_muic, + MAX77693_MUIC_REG_CTRL3, &ctrl3); + ctrl3 &= ~CONTROL3_ADCDBSET_MASK; + ctrl3 |= (time << CONTROL3_ADCDBSET_SHIFT); + + ret = max77693_write_reg(info->max77693->regmap_muic, + MAX77693_MUIC_REG_CTRL3, ctrl3); + if (ret) { + dev_err(info->dev, "failed to set ADC debounce time\n"); + ret = -EINVAL; + } + break; + default: + dev_err(info->dev, "invalid ADC debounce time\n"); + ret = -EINVAL; + break; + } + + return ret; +}; + +static int max77693_muic_set_path(struct max77693_muic_info *info, + u8 val, bool attached) +{ + int ret = 0; + u8 ctrl1, ctrl2 = 0; + + if (attached) + ctrl1 = val; + else + ctrl1 = CONTROL1_SW_OPEN; + + ret = max77693_update_reg(info->max77693->regmap_muic, + MAX77693_MUIC_REG_CTRL1, ctrl1, COMP_SW_MASK); + if (ret < 0) { + dev_err(info->dev, "failed to update MUIC register\n"); + goto out; + } + + if (attached) + ctrl2 |= CONTROL2_CPEN_MASK; /* LowPwr=0, CPEn=1 */ + else + ctrl2 |= CONTROL2_LOWPWR_MASK; /* LowPwr=1, CPEn=0 */ + + ret = max77693_update_reg(info->max77693->regmap_muic, + MAX77693_MUIC_REG_CTRL2, ctrl2, + CONTROL2_LOWPWR_MASK | CONTROL2_CPEN_MASK); + if (ret < 0) { + dev_err(info->dev, "failed to update MUIC register\n"); + goto out; + } + + dev_info(info->dev, + "CONTROL1 : 0x%02x, CONTROL2 : 0x%02x, state : %s\n", + ctrl1, ctrl2, attached ? "attached" : "detached"); +out: + return ret; +} + +static int max77693_muic_adc_ground_handler(struct max77693_muic_info *info, + bool attached) +{ + int ret = 0; + int type; + int adc, adc1k, adclow; + + if (attached) { + adc = info->status[0] & STATUS1_ADC_MASK; + adclow = info->status[0] & STATUS1_ADCLOW_MASK; + adclow >>= STATUS1_ADCLOW_SHIFT; + adc1k = info->status[0] & STATUS1_ADC1K_MASK; + adc1k >>= STATUS1_ADC1K_SHIFT; + + /** + * [0x1][ADCLow][ADC1K] + * [0x1 0 0 ] : USB_OTG + * [0x1 1 0 ] : Audio Video Cable with load + * [0x1 1 1 ] : MHL + */ + type = ((0x1 << 8) | (adclow << 1) | adc1k); + + /* Store previous ADC value to handle accessory + when accessory will be detached */ + info->prev_adc = adc; + info->prev_adc_gnd = type; + } else + type = info->prev_adc_gnd; + + switch (type) { + case MAX77693_MUIC_GND_USB_OTG: + /* USB_OTG */ + ret = max77693_muic_set_path(info, CONTROL1_SW_USB, attached); + if (ret < 0) + goto out; + extcon_set_cable_state(info->edev, "USB-Host", attached); + break; + case MAX77693_MUIC_GND_AV_CABLE_LOAD: + /* Audio Video Cable with load */ + ret = max77693_muic_set_path(info, CONTROL1_SW_AUDIO, attached); + if (ret < 0) + goto out; + extcon_set_cable_state(info->edev, + "Audio-video-load", attached); + break; + case MAX77693_MUIC_GND_MHL_CABLE: + /* MHL */ + extcon_set_cable_state(info->edev, "MHL", attached); + break; + default: + dev_err(info->dev, "faild 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); + ret = -EINVAL; + break; + } + +out: + return ret; +} + +static int max77693_muic_adc_handler(struct max77693_muic_info *info, + int curr_adc, bool attached) +{ + int ret = 0; + int adc; + + if (attached) { + /* Store ADC value to handle accessory + when accessory will be detached */ + info->prev_adc = curr_adc; + adc = curr_adc; + } else + adc = info->prev_adc; + + dev_info(info->dev, + "external connector is %s (adc:0x%02x, prev_adc:0x%x)\n", + attached ? "attached" : "detached", curr_adc, info->prev_adc); + + switch (adc) { + case MAX77693_MUIC_ADC_GROUND: + /* USB_OTG/MHL/Audio */ + max77693_muic_adc_ground_handler(info, attached); + break; + case MAX77693_MUIC_ADC_FACTORY_MODE_USB_OFF: + case MAX77693_MUIC_ADC_FACTORY_MODE_USB_ON: + /* USB */ + ret = max77693_muic_set_path(info, CONTROL1_SW_USB, attached); + if (ret < 0) + goto out; + extcon_set_cable_state(info->edev, "USB", attached); + break; + case MAX77693_MUIC_ADC_FACTORY_MODE_UART_OFF: + case MAX77693_MUIC_ADC_FACTORY_MODE_UART_ON: + /* JIG */ + ret = max77693_muic_set_path(info, CONTROL1_SW_UART, attached); + if (ret < 0) + goto out; + extcon_set_cable_state(info->edev, "JIG", attached); + break; + case MAX77693_MUIC_ADC_AUDIO_MODE_REMOTE: + /* Audio Video cable with no-load */ + ret = max77693_muic_set_path(info, CONTROL1_SW_AUDIO, attached); + if (ret < 0) + goto out; + extcon_set_cable_state(info->edev, + "Audio-video-noload", attached); + break; + case MAX77693_MUIC_ADC_SEND_END_BUTTON: + case MAX77693_MUIC_ADC_REMOTE_S1_BUTTON: + case MAX77693_MUIC_ADC_REMOTE_S2_BUTTON: + case MAX77693_MUIC_ADC_REMOTE_S3_BUTTON: + case MAX77693_MUIC_ADC_REMOTE_S4_BUTTON: + case MAX77693_MUIC_ADC_REMOTE_S5_BUTTON: + case MAX77693_MUIC_ADC_REMOTE_S6_BUTTON: + case MAX77693_MUIC_ADC_REMOTE_S7_BUTTON: + case MAX77693_MUIC_ADC_REMOTE_S8_BUTTON: + case MAX77693_MUIC_ADC_REMOTE_S9_BUTTON: + case MAX77693_MUIC_ADC_REMOTE_S10_BUTTON: + case MAX77693_MUIC_ADC_REMOTE_S11_BUTTON: + case MAX77693_MUIC_ADC_REMOTE_S12_BUTTON: + case MAX77693_MUIC_ADC_RESERVED_ACC_1: + case MAX77693_MUIC_ADC_RESERVED_ACC_2: + case MAX77693_MUIC_ADC_RESERVED_ACC_3: + case MAX77693_MUIC_ADC_RESERVED_ACC_4: + case MAX77693_MUIC_ADC_RESERVED_ACC_5: + case MAX77693_MUIC_ADC_CEA936_AUDIO: + case MAX77693_MUIC_ADC_PHONE_POWERED_DEV: + case MAX77693_MUIC_ADC_TTY_CONVERTER: + case MAX77693_MUIC_ADC_UART_CABLE: + case MAX77693_MUIC_ADC_CEA936A_TYPE1_CHG: + case MAX77693_MUIC_ADC_AV_CABLE_NOLOAD: + case MAX77693_MUIC_ADC_CEA936A_TYPE2_CHG: + /* This accessory isn't used in general case if it is specially + needed to detect additional accessory, should implement + proper operation when this accessory is attached/detached. */ + dev_info(info->dev, + "accessory is %s but it isn't used (adc:0x%x)\n", + attached ? "attached" : "detached", adc); + goto out; + default: + dev_err(info->dev, + "failed to detect %s accessory (adc:0x%x)\n", + attached ? "attached" : "detached", adc); + ret = -EINVAL; + goto out; + } + +out: + return ret; +} + +static int max77693_muic_chg_handler(struct max77693_muic_info *info, + int curr_chg_type, bool attached) +{ + int ret = 0; + int chg_type; + + if (attached) { + /* Store previous charger type to control + when charger accessory will be detached */ + info->prev_chg_type = curr_chg_type; + chg_type = curr_chg_type; + } else + chg_type = info->prev_chg_type; + + dev_info(info->dev, + "external connector is %s(chg_type:0x%x, prev_chg_type:0x%x)\n", + attached ? "attached" : "detached", + curr_chg_type, info->prev_chg_type); + + switch (chg_type) { + case MAX77693_CHARGER_TYPE_USB: + ret = max77693_muic_set_path(info, CONTROL1_SW_USB, attached); + if (ret < 0) + goto out; + extcon_set_cable_state(info->edev, "USB", attached); + break; + case MAX77693_CHARGER_TYPE_DOWNSTREAM_PORT: + extcon_set_cable_state(info->edev, + "Charge-downstream", attached); + break; + case MAX77693_CHARGER_TYPE_DEDICATED_CHG: + extcon_set_cable_state(info->edev, "TA", attached); + break; + case MAX77693_CHARGER_TYPE_APPLE_500MA: + extcon_set_cable_state(info->edev, "Slow-charger", attached); + break; + case MAX77693_CHARGER_TYPE_APPLE_1A_2A: + extcon_set_cable_state(info->edev, "Fast-charger", attached); + break; + case MAX77693_CHARGER_TYPE_DEAD_BATTERY: + break; + default: + dev_err(info->dev, + "failed to detect %s accessory (chg_type:0x%x)\n", + attached ? "attached" : "detached", chg_type); + ret = -EINVAL; + goto out; + } + +out: + return ret; +} + +static void max77693_muic_irq_work(struct work_struct *work) +{ + struct max77693_muic_info *info = container_of(work, + struct max77693_muic_info, irq_work); + int curr_adc, curr_chg_type; + int irq_type = -1; + int i, ret = 0; + bool attached = true; + + if (!info->edev) + return; + + mutex_lock(&info->mutex); + + for (i = 0 ; i < ARRAY_SIZE(muic_irqs) ; i++) + if (info->irq == muic_irqs[i].virq) + irq_type = muic_irqs[i].irq; + + ret = max77693_bulk_read(info->max77693->regmap_muic, + MAX77693_MUIC_REG_STATUS1, 2, info->status); + if (ret) { + dev_err(info->dev, "failed to read MUIC register\n"); + mutex_unlock(&info->mutex); + return; + } + + switch (irq_type) { + case MAX77693_MUIC_IRQ_INT1_ADC: + case MAX77693_MUIC_IRQ_INT1_ADC_LOW: + case MAX77693_MUIC_IRQ_INT1_ADC_ERR: + case MAX77693_MUIC_IRQ_INT1_ADC1K: + /* Handle all of accessory except for + type of charger accessory */ + curr_adc = info->status[0] & STATUS1_ADC_MASK; + curr_adc >>= STATUS1_ADC_SHIFT; + + /* Check accossory state which is either detached or attached */ + if (curr_adc == MAX77693_MUIC_ADC_OPEN) + attached = false; + + ret = max77693_muic_adc_handler(info, curr_adc, attached); + break; + case MAX77693_MUIC_IRQ_INT2_CHGTYP: + case MAX77693_MUIC_IRQ_INT2_CHGDETREUN: + case MAX77693_MUIC_IRQ_INT2_DCDTMR: + case MAX77693_MUIC_IRQ_INT2_DXOVP: + case MAX77693_MUIC_IRQ_INT2_VBVOLT: + case MAX77693_MUIC_IRQ_INT2_VIDRM: + /* Handle charger accessory */ + curr_chg_type = info->status[1] & STATUS2_CHGTYP_MASK; + curr_chg_type >>= STATUS2_CHGTYP_SHIFT; + + /* Check charger accossory state which + is either detached or attached */ + if (curr_chg_type == MAX77693_CHARGER_TYPE_NONE) + attached = false; + + ret = max77693_muic_chg_handler(info, curr_chg_type, attached); + break; + case MAX77693_MUIC_IRQ_INT3_EOC: + case MAX77693_MUIC_IRQ_INT3_CGMBC: + case MAX77693_MUIC_IRQ_INT3_OVP: + case MAX77693_MUIC_IRQ_INT3_MBCCHG_ERR: + case MAX77693_MUIC_IRQ_INT3_CHG_ENABLED: + case MAX77693_MUIC_IRQ_INT3_BAT_DET: + break; + default: + dev_err(info->dev, "muic interrupt: irq %d occurred\n", + irq_type); + break; + } + + if (ret < 0) + dev_err(info->dev, "failed to handle MUIC interrupt\n"); + + mutex_unlock(&info->mutex); + + return; +} + +static irqreturn_t max77693_muic_irq_handler(int irq, void *data) +{ + struct max77693_muic_info *info = data; + + info->irq = irq; + schedule_work(&info->irq_work); + + return IRQ_HANDLED; +} + +static struct regmap_config max77693_muic_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int max77693_muic_detect_accessory(struct max77693_muic_info *info) +{ + int ret = 0; + int adc, chg_type; + + mutex_lock(&info->mutex); + + /* Read STATUSx register to detect accessory */ + ret = max77693_bulk_read(info->max77693->regmap_muic, + MAX77693_MUIC_REG_STATUS1, 2, info->status); + if (ret) { + dev_err(info->dev, "failed to read MUIC register\n"); + mutex_unlock(&info->mutex); + return -EINVAL; + } + + adc = info->status[0] & STATUS1_ADC_MASK; + adc >>= STATUS1_ADC_SHIFT; + + if (adc != MAX77693_MUIC_ADC_OPEN) { + dev_info(info->dev, + "external connector is attached (adc:0x%02x)\n", adc); + + ret = max77693_muic_adc_handler(info, adc, true); + if (ret < 0) + dev_err(info->dev, "failed to detect accessory\n"); + goto out; + } + + chg_type = info->status[1] & STATUS2_CHGTYP_MASK; + chg_type >>= STATUS2_CHGTYP_SHIFT; + + if (chg_type != MAX77693_CHARGER_TYPE_NONE) { + dev_info(info->dev, + "external connector is attached (chg_type:0x%x)\n", + chg_type); + + max77693_muic_chg_handler(info, chg_type, true); + if (ret < 0) + dev_err(info->dev, "failed to detect charger accessory\n"); + } + +out: + mutex_unlock(&info->mutex); + return ret; +} + +static int __devinit max77693_muic_probe(struct platform_device *pdev) +{ + struct max77693_dev *max77693 = dev_get_drvdata(pdev->dev.parent); + struct max77693_muic_info *info; + int ret, i; + u8 id; + + info = kzalloc(sizeof(struct max77693_muic_info), GFP_KERNEL); + if (!info) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + ret = -ENOMEM; + goto err_kfree; + } + info->dev = &pdev->dev; + info->max77693 = max77693; + info->max77693->regmap_muic = regmap_init_i2c(info->max77693->muic, + &max77693_muic_regmap_config); + if (IS_ERR(info->max77693->regmap_muic)) { + ret = PTR_ERR(info->max77693->regmap_muic); + dev_err(max77693->dev, + "failed to allocate register map: %d\n", ret); + goto err_regmap; + } + platform_set_drvdata(pdev, info); + mutex_init(&info->mutex); + + INIT_WORK(&info->irq_work, max77693_muic_irq_work); + + /* Support irq domain for MAX77693 MUIC device */ + for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) { + struct max77693_muic_irq *muic_irq = &muic_irqs[i]; + int virq = 0; + + virq = irq_create_mapping(max77693->irq_domain, muic_irq->irq); + if (!virq) + goto err_irq; + muic_irq->virq = virq; + + ret = request_threaded_irq(virq, NULL, + max77693_muic_irq_handler, + 0, muic_irq->name, info); + if (ret) { + dev_err(&pdev->dev, + "failed: irq request (IRQ: %d," + " error :%d)\n", + muic_irq->irq, ret); + + for (i = i - 1; i >= 0; i--) + free_irq(muic_irq->virq, info); + goto err_irq; + } + } + + /* Initialize extcon device */ + info->edev = kzalloc(sizeof(struct extcon_dev), GFP_KERNEL); + if (!info->edev) { + dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); + ret = -ENOMEM; + goto err_irq; + } + info->edev->name = DEV_NAME; + info->edev->supported_cable = max77693_extcon_cable; + ret = extcon_dev_register(info->edev, NULL); + if (ret) { + dev_err(&pdev->dev, "failed to register extcon device\n"); + goto err_extcon; + } + + /* Check revision number of MUIC device*/ + ret = max77693_read_reg(info->max77693->regmap_muic, + MAX77693_MUIC_REG_ID, &id); + if (ret < 0) { + dev_err(&pdev->dev, "failed to read revision number\n"); + goto err_extcon; + } + dev_info(info->dev, "device ID : 0x%x\n", id); + + /* Set ADC debounce time */ + max77693_muic_set_debounce_time(info, ADC_DEBOUNCE_TIME_25MS); + + /* Detect accessory on boot */ + max77693_muic_detect_accessory(info); + + return ret; + +err_extcon: + kfree(info->edev); +err_irq: +err_regmap: + kfree(info); +err_kfree: + return ret; +} + +static int __devexit max77693_muic_remove(struct platform_device *pdev) +{ + struct max77693_muic_info *info = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) + free_irq(muic_irqs[i].virq, info); + cancel_work_sync(&info->irq_work); + extcon_dev_unregister(info->edev); + kfree(info); + + return 0; +} + +static struct platform_driver max77693_muic_driver = { + .driver = { + .name = DEV_NAME, + .owner = THIS_MODULE, + }, + .probe = max77693_muic_probe, + .remove = __devexit_p(max77693_muic_remove), +}; + +module_platform_driver(max77693_muic_driver); + +MODULE_DESCRIPTION("Maxim MAX77693 Extcon driver"); +MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:extcon-max77693"); diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon_class.c index 159aeb0..f6419f9 100644 --- a/drivers/extcon/extcon_class.c +++ b/drivers/extcon/extcon_class.c @@ -65,7 +65,7 @@ const char *extcon_cable_name[] = { NULL, }; -struct class *extcon_class; +static struct class *extcon_class; #if defined(CONFIG_ANDROID) static struct class_compat *switch_class; #endif /* CONFIG_ANDROID */ diff --git a/drivers/extcon/extcon_gpio.c b/drivers/extcon/extcon_gpio.c index 8a0dcc1..fe3db45 100644 --- a/drivers/extcon/extcon_gpio.c +++ b/drivers/extcon/extcon_gpio.c @@ -105,25 +105,25 @@ static int __devinit gpio_extcon_probe(struct platform_device *pdev) ret = extcon_dev_register(&extcon_data->edev, &pdev->dev); if (ret < 0) - goto err_extcon_dev_register; + return ret; ret = gpio_request_one(extcon_data->gpio, GPIOF_DIR_IN, pdev->name); if (ret < 0) - goto err_request_gpio; + goto err; INIT_DELAYED_WORK(&extcon_data->work, gpio_extcon_work); extcon_data->irq = gpio_to_irq(extcon_data->gpio); if (extcon_data->irq < 0) { ret = extcon_data->irq; - goto err_detect_irq_num_failed; + goto err; } ret = request_any_context_irq(extcon_data->irq, gpio_irq_handler, pdata->irq_flags, pdev->name, extcon_data); if (ret < 0) - goto err_request_irq; + goto err; platform_set_drvdata(pdev, extcon_data); /* Perform initial detection */ @@ -131,13 +131,8 @@ static int __devinit gpio_extcon_probe(struct platform_device *pdev) return 0; -err_request_irq: -err_detect_irq_num_failed: - gpio_free(extcon_data->gpio); -err_request_gpio: +err: extcon_dev_unregister(&extcon_data->edev); -err_extcon_dev_register: - devm_kfree(&pdev->dev, extcon_data); return ret; } @@ -148,9 +143,7 @@ static int __devexit gpio_extcon_remove(struct platform_device *pdev) cancel_delayed_work_sync(&extcon_data->work); free_irq(extcon_data->irq, extcon_data); - gpio_free(extcon_data->gpio); extcon_dev_unregister(&extcon_data->edev); - devm_kfree(&pdev->dev, extcon_data); return 0; } diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index b9426a6..0614ff3 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -411,7 +411,7 @@ enum { #define HV_PRESENT_BIT 0x80000000 #define HV_LINUX_GUEST_ID_LO 0x00000000 -#define HV_LINUX_GUEST_ID_HI 0xB16B00B5 +#define HV_LINUX_GUEST_ID_HI 2976579765 #define HV_LINUX_GUEST_ID (((u64)HV_LINUX_GUEST_ID_HI << 32) | \ HV_LINUX_GUEST_ID_LO) diff --git a/drivers/power/ds2780_battery.c b/drivers/power/ds2780_battery.c index de31cae..74fad94 100644 --- a/drivers/power/ds2780_battery.c +++ b/drivers/power/ds2780_battery.c @@ -39,7 +39,6 @@ struct ds2780_device_info { struct device *dev; struct power_supply bat; struct device *w1_dev; - struct task_struct *mutex_holder; }; enum current_types { @@ -64,10 +63,7 @@ static inline struct power_supply *to_power_supply(struct device *dev) static inline int ds2780_battery_io(struct ds2780_device_info *dev_info, char *buf, int addr, size_t count, int io) { - if (dev_info->mutex_holder == current) - return w1_ds2780_io_nolock(dev_info->w1_dev, buf, addr, count, io); - else - return w1_ds2780_io(dev_info->w1_dev, buf, addr, count, io); + return w1_ds2780_io(dev_info->w1_dev, buf, addr, count, io); } static inline int ds2780_read8(struct ds2780_device_info *dev_info, u8 *val, @@ -779,7 +775,6 @@ static int __devinit ds2780_battery_probe(struct platform_device *pdev) dev_info->bat.properties = ds2780_battery_props; dev_info->bat.num_properties = ARRAY_SIZE(ds2780_battery_props); dev_info->bat.get_property = ds2780_battery_get_property; - dev_info->mutex_holder = current; ret = power_supply_register(&pdev->dev, &dev_info->bat); if (ret) { @@ -809,8 +804,6 @@ static int __devinit ds2780_battery_probe(struct platform_device *pdev) goto fail_remove_bin_file; } - dev_info->mutex_holder = NULL; - return 0; fail_remove_bin_file: @@ -830,8 +823,6 @@ static int __devexit ds2780_battery_remove(struct platform_device *pdev) { struct ds2780_device_info *dev_info = platform_get_drvdata(pdev); - dev_info->mutex_holder = current; - /* remove attributes */ sysfs_remove_group(&dev_info->bat.dev->kobj, &ds2780_attr_group); diff --git a/drivers/power/ds2781_battery.c b/drivers/power/ds2781_battery.c index 975684a..5f92a4b 100644 --- a/drivers/power/ds2781_battery.c +++ b/drivers/power/ds2781_battery.c @@ -37,7 +37,6 @@ struct ds2781_device_info { struct device *dev; struct power_supply bat; struct device *w1_dev; - struct task_struct *mutex_holder; }; enum current_types { @@ -62,11 +61,7 @@ static inline struct power_supply *to_power_supply(struct device *dev) static inline int ds2781_battery_io(struct ds2781_device_info *dev_info, char *buf, int addr, size_t count, int io) { - if (dev_info->mutex_holder == current) - return w1_ds2781_io_nolock(dev_info->w1_dev, buf, addr, - count, io); - else - return w1_ds2781_io(dev_info->w1_dev, buf, addr, count, io); + return w1_ds2781_io(dev_info->w1_dev, buf, addr, count, io); } int w1_ds2781_read(struct ds2781_device_info *dev_info, char *buf, @@ -775,7 +770,6 @@ static int __devinit ds2781_battery_probe(struct platform_device *pdev) dev_info->bat.properties = ds2781_battery_props; dev_info->bat.num_properties = ARRAY_SIZE(ds2781_battery_props); dev_info->bat.get_property = ds2781_battery_get_property; - dev_info->mutex_holder = current; ret = power_supply_register(&pdev->dev, &dev_info->bat); if (ret) { @@ -805,8 +799,6 @@ static int __devinit ds2781_battery_probe(struct platform_device *pdev) goto fail_remove_bin_file; } - dev_info->mutex_holder = NULL; - return 0; fail_remove_bin_file: @@ -826,8 +818,6 @@ static int __devexit ds2781_battery_remove(struct platform_device *pdev) { struct ds2781_device_info *dev_info = platform_get_drvdata(pdev); - dev_info->mutex_holder = current; - /* remove attributes */ sysfs_remove_group(&dev_info->bat.dev->kobj, &ds2781_attr_group); diff --git a/drivers/w1/masters/ds1wm.c b/drivers/w1/masters/ds1wm.c index a0c8965..530a2d3 100644 --- a/drivers/w1/masters/ds1wm.c +++ b/drivers/w1/masters/ds1wm.c @@ -334,7 +334,9 @@ static void ds1wm_search(void *data, struct w1_master *master_dev, return; } + mutex_lock(&master_dev->bus_mutex); if (ds1wm_reset(ds1wm_data)) { + mutex_unlock(&master_dev->bus_mutex); dev_dbg(&ds1wm_data->pdev->dev, "pass: %d reset error (or no slaves)\n", pass); break; @@ -387,6 +389,7 @@ static void ds1wm_search(void *data, struct w1_master *master_dev, } if (ds1wm_data->read_error) { + mutex_unlock(&master_dev->bus_mutex); dev_err(&ds1wm_data->pdev->dev, "pass: %d read error, retrying\n", pass); break; @@ -400,6 +403,7 @@ static void ds1wm_search(void *data, struct w1_master *master_dev, dev_dbg(&ds1wm_data->pdev->dev, "pass: %d resetting bus\n", pass); ds1wm_reset(ds1wm_data); + mutex_unlock(&master_dev->bus_mutex); if ((r_prime & ((u64)1 << 63)) && (d & ((u64)1 << 63))) { dev_err(&ds1wm_data->pdev->dev, "pass: %d bus error, retrying\n", pass); diff --git a/drivers/w1/masters/omap_hdq.c b/drivers/w1/masters/omap_hdq.c index 291897c..4b0fcf3 100644 --- a/drivers/w1/masters/omap_hdq.c +++ b/drivers/w1/masters/omap_hdq.c @@ -178,6 +178,7 @@ static int hdq_write_byte(struct hdq_data *hdq_data, u8 val, u8 *status) hdq_data->hdq_irqstatus, OMAP_HDQ_TIMEOUT); if (ret == 0) { dev_dbg(hdq_data->dev, "TX wait elapsed\n"); + ret = -ETIMEDOUT; goto out; } @@ -185,7 +186,7 @@ static int hdq_write_byte(struct hdq_data *hdq_data, u8 val, u8 *status) /* check irqstatus */ if (!(*status & OMAP_HDQ_INT_STATUS_TXCOMPLETE)) { dev_dbg(hdq_data->dev, "timeout waiting for" - "TXCOMPLETE/RXCOMPLETE, %x", *status); + " TXCOMPLETE/RXCOMPLETE, %x", *status); ret = -ETIMEDOUT; goto out; } @@ -196,7 +197,7 @@ static int hdq_write_byte(struct hdq_data *hdq_data, u8 val, u8 *status) OMAP_HDQ_FLAG_CLEAR, &tmp_status); if (ret) { dev_dbg(hdq_data->dev, "timeout waiting GO bit" - "return to zero, %x", tmp_status); + " return to zero, %x", tmp_status); } out: @@ -339,7 +340,7 @@ static int omap_hdq_break(struct hdq_data *hdq_data) &tmp_status); if (ret) dev_dbg(hdq_data->dev, "timeout waiting INIT&GO bits" - "return to zero, %x", tmp_status); + " return to zero, %x", tmp_status); out: mutex_unlock(&hdq_data->hdq_mutex); @@ -351,7 +352,6 @@ static int hdq_read_byte(struct hdq_data *hdq_data, u8 *val) { int ret = 0; u8 status; - unsigned long timeout = jiffies + OMAP_HDQ_TIMEOUT; ret = mutex_lock_interruptible(&hdq_data->hdq_mutex); if (ret < 0) { @@ -369,22 +369,20 @@ static int hdq_read_byte(struct hdq_data *hdq_data, u8 *val) OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO, OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO); /* - * The RX comes immediately after TX. It - * triggers another interrupt before we - * sleep. So we have to wait for RXCOMPLETE bit. + * The RX comes immediately after TX. */ - while (!(hdq_data->hdq_irqstatus - & OMAP_HDQ_INT_STATUS_RXCOMPLETE) - && time_before(jiffies, timeout)) { - schedule_timeout_uninterruptible(1); - } + wait_event_timeout(hdq_wait_queue, + (hdq_data->hdq_irqstatus + & OMAP_HDQ_INT_STATUS_RXCOMPLETE), + OMAP_HDQ_TIMEOUT); + hdq_reg_merge(hdq_data, OMAP_HDQ_CTRL_STATUS, 0, OMAP_HDQ_CTRL_STATUS_DIR); status = hdq_data->hdq_irqstatus; /* check irqstatus */ if (!(status & OMAP_HDQ_INT_STATUS_RXCOMPLETE)) { dev_dbg(hdq_data->dev, "timeout waiting for" - "RXCOMPLETE, %x", status); + " RXCOMPLETE, %x", status); ret = -ETIMEDOUT; goto out; } @@ -394,7 +392,7 @@ static int hdq_read_byte(struct hdq_data *hdq_data, u8 *val) out: mutex_unlock(&hdq_data->hdq_mutex); rtn: - return 0; + return ret; } @@ -456,7 +454,7 @@ static int omap_hdq_put(struct hdq_data *hdq_data) if (0 == hdq_data->hdq_usecount) { dev_dbg(hdq_data->dev, "attempt to decrement use count" - "when it is zero"); + " when it is zero"); ret = -EINVAL; } else { hdq_data->hdq_usecount--; @@ -524,7 +522,7 @@ static void omap_w1_write_byte(void *_hdq, u8 byte) mutex_unlock(&hdq_data->hdq_mutex); ret = hdq_write_byte(hdq_data, byte, &status); - if (ret == 0) { + if (ret < 0) { dev_dbg(hdq_data->dev, "TX failure:Ctrl status %x\n", status); return; } diff --git a/drivers/w1/slaves/Kconfig b/drivers/w1/slaves/Kconfig index eb9e376..6752669 100644 --- a/drivers/w1/slaves/Kconfig +++ b/drivers/w1/slaves/Kconfig @@ -94,6 +94,19 @@ config W1_SLAVE_DS2781 If you are unsure, say N. +config W1_SLAVE_DS28E04 + tristate "4096-Bit Addressable 1-Wire EEPROM with PIO (DS28E04-100)" + depends on W1 + select CRC16 + help + If you enable this you will have the DS28E04-100 + chip support. + + Say Y here if you want to use a 1-wire + 4kb EEPROM with PIO family device (DS28E04). + + If you are unsure, say N. + config W1_SLAVE_BQ27000 tristate "BQ27000 slave support" depends on W1 diff --git a/drivers/w1/slaves/Makefile b/drivers/w1/slaves/Makefile index c4f1859..05188f6 100644 --- a/drivers/w1/slaves/Makefile +++ b/drivers/w1/slaves/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_W1_SLAVE_DS2760) += w1_ds2760.o obj-$(CONFIG_W1_SLAVE_DS2780) += w1_ds2780.o obj-$(CONFIG_W1_SLAVE_DS2781) += w1_ds2781.o obj-$(CONFIG_W1_SLAVE_BQ27000) += w1_bq27000.o +obj-$(CONFIG_W1_SLAVE_DS28E04) += w1_ds28e04.o diff --git a/drivers/w1/slaves/w1_bq27000.c b/drivers/w1/slaves/w1_bq27000.c index 52ad812..773dca5 100644 --- a/drivers/w1/slaves/w1_bq27000.c +++ b/drivers/w1/slaves/w1_bq27000.c @@ -31,10 +31,10 @@ static int w1_bq27000_read(struct device *dev, unsigned int reg) u8 val; struct w1_slave *sl = container_of(dev->parent, struct w1_slave, dev); - mutex_lock(&sl->master->mutex); + mutex_lock(&sl->master->bus_mutex); w1_write_8(sl->master, HDQ_CMD_READ | reg); val = w1_read_8(sl->master); - mutex_unlock(&sl->master->mutex); + mutex_unlock(&sl->master->bus_mutex); return val; } diff --git a/drivers/w1/slaves/w1_ds2408.c b/drivers/w1/slaves/w1_ds2408.c index 8e813ee..441ad3a 100644 --- a/drivers/w1/slaves/w1_ds2408.c +++ b/drivers/w1/slaves/w1_ds2408.c @@ -52,11 +52,11 @@ static int _read_reg(struct w1_slave *sl, u8 address, unsigned char* buf) if (!buf) return -EINVAL; - mutex_lock(&sl->master->mutex); + mutex_lock(&sl->master->bus_mutex); dev_dbg(&sl->dev, "mutex locked"); if (w1_reset_select_slave(sl)) { - mutex_unlock(&sl->master->mutex); + mutex_unlock(&sl->master->bus_mutex); return -EIO; } @@ -66,7 +66,7 @@ static int _read_reg(struct w1_slave *sl, u8 address, unsigned char* buf) w1_write_block(sl->master, wrbuf, 3); *buf = w1_read_8(sl->master); - mutex_unlock(&sl->master->mutex); + mutex_unlock(&sl->master->bus_mutex); dev_dbg(&sl->dev, "mutex unlocked"); return 1; } @@ -165,7 +165,7 @@ static ssize_t w1_f29_write_output( return -EFAULT; dev_dbg(&sl->dev, "locking mutex for write_output"); - mutex_lock(&sl->master->mutex); + mutex_lock(&sl->master->bus_mutex); dev_dbg(&sl->dev, "mutex locked"); if (w1_reset_select_slave(sl)) @@ -200,14 +200,14 @@ static ssize_t w1_f29_write_output( /* read the result of the READ_PIO_REGS command */ if (w1_read_8(sl->master) == *buf) { /* success! */ - mutex_unlock(&sl->master->mutex); + mutex_unlock(&sl->master->bus_mutex); dev_dbg(&sl->dev, "mutex unlocked, retries:%d", retries); return 1; } } error: - mutex_unlock(&sl->master->mutex); + mutex_unlock(&sl->master->bus_mutex); dev_dbg(&sl->dev, "mutex unlocked in error, retries:%d", retries); return -EIO; @@ -228,7 +228,7 @@ static ssize_t w1_f29_write_activity( if (count != 1 || off != 0) return -EFAULT; - mutex_lock(&sl->master->mutex); + mutex_lock(&sl->master->bus_mutex); if (w1_reset_select_slave(sl)) goto error; @@ -236,7 +236,7 @@ static ssize_t w1_f29_write_activity( while (retries--) { w1_write_8(sl->master, W1_F29_FUNC_RESET_ACTIVITY_LATCHES); if (w1_read_8(sl->master) == W1_F29_SUCCESS_CONFIRM_BYTE) { - mutex_unlock(&sl->master->mutex); + mutex_unlock(&sl->master->bus_mutex); return 1; } if (w1_reset_resume_command(sl->master)) @@ -244,7 +244,7 @@ static ssize_t w1_f29_write_activity( } error: - mutex_unlock(&sl->master->mutex); + mutex_unlock(&sl->master->bus_mutex); return -EIO; } @@ -263,7 +263,7 @@ static ssize_t w1_f29_write_status_control( if (count != 1 || off != 0) return -EFAULT; - mutex_lock(&sl->master->mutex); + mutex_lock(&sl->master->bus_mutex); if (w1_reset_select_slave(sl)) goto error; @@ -285,12 +285,12 @@ static ssize_t w1_f29_write_status_control( w1_write_block(sl->master, w1_buf, 3); if (w1_read_8(sl->master) == *buf) { /* success! */ - mutex_unlock(&sl->master->mutex); + mutex_unlock(&sl->master->bus_mutex); return 1; } } error: - mutex_unlock(&sl->master->mutex); + mutex_unlock(&sl->master->bus_mutex); return -EIO; } diff --git a/drivers/w1/slaves/w1_ds2423.c b/drivers/w1/slaves/w1_ds2423.c index 7a7dbe50..40a10b5 100644 --- a/drivers/w1/slaves/w1_ds2423.c +++ b/drivers/w1/slaves/w1_ds2423.c @@ -66,7 +66,7 @@ static ssize_t w1_counter_read(struct device *device, wrbuf[0] = 0xA5; wrbuf[1] = rom_addr & 0xFF; wrbuf[2] = rom_addr >> 8; - mutex_lock(&dev->mutex); + mutex_lock(&dev->bus_mutex); if (!w1_reset_select_slave(sl)) { w1_write_block(dev, wrbuf, 3); read_byte_count = 0; @@ -124,7 +124,7 @@ static ssize_t w1_counter_read(struct device *device, } else { c -= snprintf(out_buf + PAGE_SIZE - c, c, "Connection error"); } - mutex_unlock(&dev->mutex); + mutex_unlock(&dev->bus_mutex); return PAGE_SIZE - c; } diff --git a/drivers/w1/slaves/w1_ds2431.c b/drivers/w1/slaves/w1_ds2431.c index 84e2410..984b303 100644 --- a/drivers/w1/slaves/w1_ds2431.c +++ b/drivers/w1/slaves/w1_ds2431.c @@ -107,7 +107,7 @@ static ssize_t w1_f2d_read_bin(struct file *filp, struct kobject *kobj, if (count == 0) return 0; - mutex_lock(&sl->master->mutex); + mutex_lock(&sl->master->bus_mutex); /* read directly from the EEPROM in chunks of W1_F2D_READ_MAXLEN */ while (todo > 0) { @@ -126,7 +126,7 @@ static ssize_t w1_f2d_read_bin(struct file *filp, struct kobject *kobj, off += W1_F2D_READ_MAXLEN; } - mutex_unlock(&sl->master->mutex); + mutex_unlock(&sl->master->bus_mutex); return count; } @@ -214,7 +214,7 @@ static ssize_t w1_f2d_write_bin(struct file *filp, struct kobject *kobj, if (count == 0) return 0; - mutex_lock(&sl->master->mutex); + mutex_lock(&sl->master->bus_mutex); /* Can only write data in blocks of the size of the scratchpad */ addr = off; @@ -259,7 +259,7 @@ static ssize_t w1_f2d_write_bin(struct file *filp, struct kobject *kobj, } out_up: - mutex_unlock(&sl->master->mutex); + mutex_unlock(&sl->master->bus_mutex); return count; } diff --git a/drivers/w1/slaves/w1_ds2433.c b/drivers/w1/slaves/w1_ds2433.c index 0f7b8f9..85f2cdb 100644 --- a/drivers/w1/slaves/w1_ds2433.c +++ b/drivers/w1/slaves/w1_ds2433.c @@ -107,7 +107,7 @@ static ssize_t w1_f23_read_bin(struct file *filp, struct kobject *kobj, if ((count = w1_f23_fix_count(off, count, W1_EEPROM_SIZE)) == 0) return 0; - mutex_lock(&sl->master->mutex); + mutex_lock(&sl->master->bus_mutex); #ifdef CONFIG_W1_SLAVE_DS2433_CRC @@ -138,7 +138,7 @@ static ssize_t w1_f23_read_bin(struct file *filp, struct kobject *kobj, #endif /* CONFIG_W1_SLAVE_DS2433_CRC */ out_up: - mutex_unlock(&sl->master->mutex); + mutex_unlock(&sl->master->bus_mutex); return count; } @@ -233,7 +233,7 @@ static ssize_t w1_f23_write_bin(struct file *filp, struct kobject *kobj, } #endif /* CONFIG_W1_SLAVE_DS2433_CRC */ - mutex_lock(&sl->master->mutex); + mutex_lock(&sl->master->bus_mutex); /* Can only write data to one page at a time */ idx = 0; @@ -251,7 +251,7 @@ static ssize_t w1_f23_write_bin(struct file *filp, struct kobject *kobj, } out_up: - mutex_unlock(&sl->master->mutex); + mutex_unlock(&sl->master->bus_mutex); return count; } diff --git a/drivers/w1/slaves/w1_ds2760.c b/drivers/w1/slaves/w1_ds2760.c index 5754c9a..aa7bd5f 100644 --- a/drivers/w1/slaves/w1_ds2760.c +++ b/drivers/w1/slaves/w1_ds2760.c @@ -31,7 +31,7 @@ static int w1_ds2760_io(struct device *dev, char *buf, int addr, size_t count, if (!dev) return 0; - mutex_lock(&sl->master->mutex); + mutex_lock(&sl->master->bus_mutex); if (addr > DS2760_DATA_SIZE || addr < 0) { count = 0; @@ -54,7 +54,7 @@ static int w1_ds2760_io(struct device *dev, char *buf, int addr, size_t count, } out: - mutex_unlock(&sl->master->mutex); + mutex_unlock(&sl->master->bus_mutex); return count; } @@ -76,14 +76,14 @@ static int w1_ds2760_eeprom_cmd(struct device *dev, int addr, int cmd) if (!dev) return -EINVAL; - mutex_lock(&sl->master->mutex); + mutex_lock(&sl->master->bus_mutex); if (w1_reset_select_slave(sl) == 0) { w1_write_8(sl->master, cmd); w1_write_8(sl->master, addr); } - mutex_unlock(&sl->master->mutex); + mutex_unlock(&sl->master->bus_mutex); return 0; } diff --git a/drivers/w1/slaves/w1_ds2780.c b/drivers/w1/slaves/w1_ds2780.c index 39f78c0..7b09307 100644 --- a/drivers/w1/slaves/w1_ds2780.c +++ b/drivers/w1/slaves/w1_ds2780.c @@ -60,30 +60,16 @@ int w1_ds2780_io(struct device *dev, char *buf, int addr, size_t count, if (!dev) return -ENODEV; - mutex_lock(&sl->master->mutex); + mutex_lock(&sl->master->bus_mutex); ret = w1_ds2780_do_io(dev, buf, addr, count, io); - mutex_unlock(&sl->master->mutex); + mutex_unlock(&sl->master->bus_mutex); return ret; } EXPORT_SYMBOL(w1_ds2780_io); -int w1_ds2780_io_nolock(struct device *dev, char *buf, int addr, size_t count, - int io) -{ - int ret; - - if (!dev) - return -ENODEV; - - ret = w1_ds2780_do_io(dev, buf, addr, count, io); - - return ret; -} -EXPORT_SYMBOL(w1_ds2780_io_nolock); - int w1_ds2780_eeprom_cmd(struct device *dev, int addr, int cmd) { struct w1_slave *sl = container_of(dev, struct w1_slave, dev); @@ -91,14 +77,14 @@ int w1_ds2780_eeprom_cmd(struct device *dev, int addr, int cmd) if (!dev) return -EINVAL; - mutex_lock(&sl->master->mutex); + mutex_lock(&sl->master->bus_mutex); if (w1_reset_select_slave(sl) == 0) { w1_write_8(sl->master, cmd); w1_write_8(sl->master, addr); } - mutex_unlock(&sl->master->mutex); + mutex_unlock(&sl->master->bus_mutex); return 0; } EXPORT_SYMBOL(w1_ds2780_eeprom_cmd); diff --git a/drivers/w1/slaves/w1_ds2780.h b/drivers/w1/slaves/w1_ds2780.h index 7373793..a1fba79 100644 --- a/drivers/w1/slaves/w1_ds2780.h +++ b/drivers/w1/slaves/w1_ds2780.h @@ -124,8 +124,6 @@ extern int w1_ds2780_io(struct device *dev, char *buf, int addr, size_t count, int io); -extern int w1_ds2780_io_nolock(struct device *dev, char *buf, int addr, - size_t count, int io); extern int w1_ds2780_eeprom_cmd(struct device *dev, int addr, int cmd); #endif /* !_W1_DS2780_H */ diff --git a/drivers/w1/slaves/w1_ds2781.c b/drivers/w1/slaves/w1_ds2781.c index 0d0c7985..877daf7 100644 --- a/drivers/w1/slaves/w1_ds2781.c +++ b/drivers/w1/slaves/w1_ds2781.c @@ -58,30 +58,16 @@ int w1_ds2781_io(struct device *dev, char *buf, int addr, size_t count, if (!dev) return -ENODEV; - mutex_lock(&sl->master->mutex); + mutex_lock(&sl->master->bus_mutex); ret = w1_ds2781_do_io(dev, buf, addr, count, io); - mutex_unlock(&sl->master->mutex); + mutex_unlock(&sl->master->bus_mutex); return ret; } EXPORT_SYMBOL(w1_ds2781_io); -int w1_ds2781_io_nolock(struct device *dev, char *buf, int addr, size_t count, - int io) -{ - int ret; - - if (!dev) - return -ENODEV; - - ret = w1_ds2781_do_io(dev, buf, addr, count, io); - - return ret; -} -EXPORT_SYMBOL(w1_ds2781_io_nolock); - int w1_ds2781_eeprom_cmd(struct device *dev, int addr, int cmd) { struct w1_slave *sl = container_of(dev, struct w1_slave, dev); @@ -89,14 +75,14 @@ int w1_ds2781_eeprom_cmd(struct device *dev, int addr, int cmd) if (!dev) return -EINVAL; - mutex_lock(&sl->master->mutex); + mutex_lock(&sl->master->bus_mutex); if (w1_reset_select_slave(sl) == 0) { w1_write_8(sl->master, cmd); w1_write_8(sl->master, addr); } - mutex_unlock(&sl->master->mutex); + mutex_unlock(&sl->master->bus_mutex); return 0; } EXPORT_SYMBOL(w1_ds2781_eeprom_cmd); diff --git a/drivers/w1/slaves/w1_ds2781.h b/drivers/w1/slaves/w1_ds2781.h index 82bc664..557dfb0 100644 --- a/drivers/w1/slaves/w1_ds2781.h +++ b/drivers/w1/slaves/w1_ds2781.h @@ -129,8 +129,6 @@ extern int w1_ds2781_io(struct device *dev, char *buf, int addr, size_t count, int io); -extern int w1_ds2781_io_nolock(struct device *dev, char *buf, int addr, - size_t count, int io); extern int w1_ds2781_eeprom_cmd(struct device *dev, int addr, int cmd); #endif /* !_W1_DS2781_H */ diff --git a/drivers/w1/slaves/w1_ds28e04.c b/drivers/w1/slaves/w1_ds28e04.c new file mode 100644 index 0000000..98117db --- /dev/null +++ b/drivers/w1/slaves/w1_ds28e04.c @@ -0,0 +1,469 @@ +/* + * w1_ds28e04.c - w1 family 1C (DS28E04) driver + * + * Copyright (c) 2012 Markus Franke <franke.m@sebakmt.com> + * + * This source code is licensed under the GNU General Public License, + * Version 2. See the file COPYING for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/crc16.h> +#include <linux/uaccess.h> + +#define CRC16_INIT 0 +#define CRC16_VALID 0xb001 + +#include "../w1.h" +#include "../w1_int.h" +#include "../w1_family.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Markus Franke <franke.m@sebakmt.com>, <franm@hrz.tu-chemnitz.de>"); +MODULE_DESCRIPTION("w1 family 1C driver for DS28E04, 4kb EEPROM and PIO"); + +/* Allow the strong pullup to be disabled, but default to enabled. + * If it was disabled a parasite powered device might not get the required + * current to copy the data from the scratchpad to EEPROM. If it is enabled + * parasite powered devices have a better chance of getting the current + * required. + */ +static int w1_strong_pullup = 1; +module_param_named(strong_pullup, w1_strong_pullup, int, 0); + +/* enable/disable CRC checking on DS28E04-100 memory accesses */ +static char w1_enable_crccheck = 1; + +#define W1_EEPROM_SIZE 512 +#define W1_PAGE_COUNT 16 +#define W1_PAGE_SIZE 32 +#define W1_PAGE_BITS 5 +#define W1_PAGE_MASK 0x1F + +#define W1_F1C_READ_EEPROM 0xF0 +#define W1_F1C_WRITE_SCRATCH 0x0F +#define W1_F1C_READ_SCRATCH 0xAA +#define W1_F1C_COPY_SCRATCH 0x55 +#define W1_F1C_ACCESS_WRITE 0x5A + +#define W1_1C_REG_LOGIC_STATE 0x220 + +struct w1_f1C_data { + u8 memory[W1_EEPROM_SIZE]; + u32 validcrc; +}; + +/** + * Check the file size bounds and adjusts count as needed. + * This would not be needed if the file size didn't reset to 0 after a write. + */ +static inline size_t w1_f1C_fix_count(loff_t off, size_t count, size_t size) +{ + if (off > size) + return 0; + + if ((off + count) > size) + return size - off; + + return count; +} + +static int w1_f1C_refresh_block(struct w1_slave *sl, struct w1_f1C_data *data, + int block) +{ + u8 wrbuf[3]; + int off = block * W1_PAGE_SIZE; + + if (data->validcrc & (1 << block)) + return 0; + + if (w1_reset_select_slave(sl)) { + data->validcrc = 0; + return -EIO; + } + + wrbuf[0] = W1_F1C_READ_EEPROM; + wrbuf[1] = off & 0xff; + wrbuf[2] = off >> 8; + w1_write_block(sl->master, wrbuf, 3); + w1_read_block(sl->master, &data->memory[off], W1_PAGE_SIZE); + + /* cache the block if the CRC is valid */ + if (crc16(CRC16_INIT, &data->memory[off], W1_PAGE_SIZE) == CRC16_VALID) + data->validcrc |= (1 << block); + + return 0; +} + +static int w1_f1C_read(struct w1_slave *sl, int addr, int len, char *data) +{ + u8 wrbuf[3]; + + /* read directly from the EEPROM */ + if (w1_reset_select_slave(sl)) + return -EIO; + + wrbuf[0] = W1_F1C_READ_EEPROM; + wrbuf[1] = addr & 0xff; + wrbuf[2] = addr >> 8; + + w1_write_block(sl->master, wrbuf, sizeof(wrbuf)); + return w1_read_block(sl->master, data, len); +} + +static ssize_t w1_f1C_read_bin(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + struct w1_f1C_data *data = sl->family_data; + int i, min_page, max_page; + + count = w1_f1C_fix_count(off, count, W1_EEPROM_SIZE); + if (count == 0) + return 0; + + mutex_lock(&sl->master->mutex); + + if (w1_enable_crccheck) { + min_page = (off >> W1_PAGE_BITS); + max_page = (off + count - 1) >> W1_PAGE_BITS; + for (i = min_page; i <= max_page; i++) { + if (w1_f1C_refresh_block(sl, data, i)) { + count = -EIO; + goto out_up; + } + } + memcpy(buf, &data->memory[off], count); + } else { + count = w1_f1C_read(sl, off, count, buf); + } + +out_up: + mutex_unlock(&sl->master->mutex); + + return count; +} + +/** + * Writes to the scratchpad and reads it back for verification. + * Then copies the scratchpad to EEPROM. + * The data must be on one page. + * The master must be locked. + * + * @param sl The slave structure + * @param addr Address for the write + * @param len length must be <= (W1_PAGE_SIZE - (addr & W1_PAGE_MASK)) + * @param data The data to write + * @return 0=Success -1=failure + */ +static int w1_f1C_write(struct w1_slave *sl, int addr, int len, const u8 *data) +{ + u8 wrbuf[4]; + u8 rdbuf[W1_PAGE_SIZE + 3]; + u8 es = (addr + len - 1) & 0x1f; + unsigned int tm = 10; + int i; + struct w1_f1C_data *f1C = sl->family_data; + + /* Write the data to the scratchpad */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F1C_WRITE_SCRATCH; + wrbuf[1] = addr & 0xff; + wrbuf[2] = addr >> 8; + + w1_write_block(sl->master, wrbuf, 3); + w1_write_block(sl->master, data, len); + + /* Read the scratchpad and verify */ + if (w1_reset_select_slave(sl)) + return -1; + + w1_write_8(sl->master, W1_F1C_READ_SCRATCH); + w1_read_block(sl->master, rdbuf, len + 3); + + /* Compare what was read against the data written */ + if ((rdbuf[0] != wrbuf[1]) || (rdbuf[1] != wrbuf[2]) || + (rdbuf[2] != es) || (memcmp(data, &rdbuf[3], len) != 0)) + return -1; + + /* Copy the scratchpad to EEPROM */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F1C_COPY_SCRATCH; + wrbuf[3] = es; + + for (i = 0; i < sizeof(wrbuf); ++i) { + /* issue 10ms strong pullup (or delay) on the last byte + for writing the data from the scratchpad to EEPROM */ + if (w1_strong_pullup && i == sizeof(wrbuf)-1) + w1_next_pullup(sl->master, tm); + + w1_write_8(sl->master, wrbuf[i]); + } + + if (!w1_strong_pullup) + msleep(tm); + + if (w1_enable_crccheck) { + /* invalidate cached data */ + f1C->validcrc &= ~(1 << (addr >> W1_PAGE_BITS)); + } + + /* Reset the bus to wake up the EEPROM (this may not be needed) */ + w1_reset_bus(sl->master); + + return 0; +} + +static ssize_t w1_f1C_write_bin(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) + +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int addr, len, idx; + + count = w1_f1C_fix_count(off, count, W1_EEPROM_SIZE); + if (count == 0) + return 0; + + if (w1_enable_crccheck) { + /* can only write full blocks in cached mode */ + if ((off & W1_PAGE_MASK) || (count & W1_PAGE_MASK)) { + dev_err(&sl->dev, "invalid offset/count off=%d cnt=%zd\n", + (int)off, count); + return -EINVAL; + } + + /* make sure the block CRCs are valid */ + for (idx = 0; idx < count; idx += W1_PAGE_SIZE) { + if (crc16(CRC16_INIT, &buf[idx], W1_PAGE_SIZE) + != CRC16_VALID) { + dev_err(&sl->dev, "bad CRC at offset %d\n", + (int)off); + return -EINVAL; + } + } + } + + mutex_lock(&sl->master->mutex); + + /* Can only write data to one page at a time */ + idx = 0; + while (idx < count) { + addr = off + idx; + len = W1_PAGE_SIZE - (addr & W1_PAGE_MASK); + if (len > (count - idx)) + len = count - idx; + + if (w1_f1C_write(sl, addr, len, &buf[idx]) < 0) { + count = -EIO; + goto out_up; + } + idx += len; + } + +out_up: + mutex_unlock(&sl->master->mutex); + + return count; +} + +static ssize_t w1_f1C_read_pio(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) + +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + + /* check arguments */ + if (off != 0 || count != 1 || buf == NULL) + return -EINVAL; + + mutex_lock(&sl->master->mutex); + ret = w1_f1C_read(sl, W1_1C_REG_LOGIC_STATE, count, buf); + mutex_unlock(&sl->master->mutex); + + return ret; +} + +static ssize_t w1_f1C_write_pio(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) + +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + u8 wrbuf[3]; + u8 ack; + + /* check arguments */ + if (off != 0 || count != 1 || buf == NULL) + return -EINVAL; + + mutex_lock(&sl->master->mutex); + + /* Write the PIO data */ + if (w1_reset_select_slave(sl)) { + mutex_unlock(&sl->master->mutex); + return -1; + } + + /* set bit 7..2 to value '1' */ + *buf = *buf | 0xFC; + + wrbuf[0] = W1_F1C_ACCESS_WRITE; + wrbuf[1] = *buf; + wrbuf[2] = ~(*buf); + w1_write_block(sl->master, wrbuf, 3); + + w1_read_block(sl->master, &ack, sizeof(ack)); + + mutex_unlock(&sl->master->mutex); + + /* check for acknowledgement */ + if (ack != 0xAA) + return -EIO; + + return count; +} + +static ssize_t w1_f1C_show_crccheck(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (put_user(w1_enable_crccheck + 0x30, buf)) + return -EFAULT; + + return sizeof(w1_enable_crccheck); +} + +static ssize_t w1_f1C_store_crccheck(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + char val; + + if (count != 1 || !buf) + return -EINVAL; + + if (get_user(val, buf)) + return -EFAULT; + + /* convert to decimal */ + val = val - 0x30; + if (val != 0 && val != 1) + return -EINVAL; + + /* set the new value */ + w1_enable_crccheck = val; + + return sizeof(w1_enable_crccheck); +} + +#define NB_SYSFS_BIN_FILES 2 +static struct bin_attribute w1_f1C_bin_attr[NB_SYSFS_BIN_FILES] = { + { + .attr = { + .name = "eeprom", + .mode = S_IRUGO | S_IWUSR, + }, + .size = W1_EEPROM_SIZE, + .read = w1_f1C_read_bin, + .write = w1_f1C_write_bin, + }, + { + .attr = { + .name = "pio", + .mode = S_IRUGO | S_IWUSR, + }, + .size = 1, + .read = w1_f1C_read_pio, + .write = w1_f1C_write_pio, + } +}; + +static DEVICE_ATTR(crccheck, S_IWUSR | S_IRUGO, + w1_f1C_show_crccheck, w1_f1C_store_crccheck); + +static int w1_f1C_add_slave(struct w1_slave *sl) +{ + int err = 0; + int i; + struct w1_f1C_data *data = NULL; + + if (w1_enable_crccheck) { + data = kzalloc(sizeof(struct w1_f1C_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + sl->family_data = data; + } + + /* create binary sysfs attributes */ + for (i = 0; i < NB_SYSFS_BIN_FILES && !err; ++i) + err = sysfs_create_bin_file( + &sl->dev.kobj, &(w1_f1C_bin_attr[i])); + + if (!err) { + /* create device attributes */ + err = device_create_file(&sl->dev, &dev_attr_crccheck); + } + + if (err) { + /* remove binary sysfs attributes */ + for (i = 0; i < NB_SYSFS_BIN_FILES; ++i) + sysfs_remove_bin_file( + &sl->dev.kobj, &(w1_f1C_bin_attr[i])); + + kfree(data); + } + + return err; +} + +static void w1_f1C_remove_slave(struct w1_slave *sl) +{ + int i; + + kfree(sl->family_data); + sl->family_data = NULL; + + /* remove device attributes */ + device_remove_file(&sl->dev, &dev_attr_crccheck); + + /* remove binary sysfs attributes */ + for (i = 0; i < NB_SYSFS_BIN_FILES; ++i) + sysfs_remove_bin_file(&sl->dev.kobj, &(w1_f1C_bin_attr[i])); +} + +static struct w1_family_ops w1_f1C_fops = { + .add_slave = w1_f1C_add_slave, + .remove_slave = w1_f1C_remove_slave, +}; + +static struct w1_family w1_family_1C = { + .fid = W1_FAMILY_DS28E04, + .fops = &w1_f1C_fops, +}; + +static int __init w1_f1C_init(void) +{ + return w1_register_family(&w1_family_1C); +} + +static void __exit w1_f1C_fini(void) +{ + w1_unregister_family(&w1_family_1C); +} + +module_init(w1_f1C_init); +module_exit(w1_f1C_fini); diff --git a/drivers/w1/slaves/w1_therm.c b/drivers/w1/slaves/w1_therm.c index ff29ae7..d90062b 100644 --- a/drivers/w1/slaves/w1_therm.c +++ b/drivers/w1/slaves/w1_therm.c @@ -179,7 +179,7 @@ static ssize_t w1_therm_read(struct device *device, int i, max_trying = 10; ssize_t c = PAGE_SIZE; - i = mutex_lock_interruptible(&dev->mutex); + i = mutex_lock_interruptible(&dev->bus_mutex); if (i != 0) return i; @@ -207,19 +207,19 @@ static ssize_t w1_therm_read(struct device *device, w1_write_8(dev, W1_CONVERT_TEMP); if (external_power) { - mutex_unlock(&dev->mutex); + mutex_unlock(&dev->bus_mutex); sleep_rem = msleep_interruptible(tm); if (sleep_rem != 0) return -EINTR; - i = mutex_lock_interruptible(&dev->mutex); + i = mutex_lock_interruptible(&dev->bus_mutex); if (i != 0) return i; } else if (!w1_strong_pullup) { sleep_rem = msleep_interruptible(tm); if (sleep_rem != 0) { - mutex_unlock(&dev->mutex); + mutex_unlock(&dev->bus_mutex); return -EINTR; } } @@ -258,7 +258,7 @@ static ssize_t w1_therm_read(struct device *device, c -= snprintf(buf + PAGE_SIZE - c, c, "t=%d\n", w1_convert_temp(rom, sl->family->fid)); - mutex_unlock(&dev->mutex); + mutex_unlock(&dev->bus_mutex); return PAGE_SIZE - c; } diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index 2f2e894..1a57437 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -557,7 +557,7 @@ static int w1_uevent(struct device *dev, struct kobj_uevent_env *env) struct w1_master *md = NULL; struct w1_slave *sl = NULL; char *event_owner, *name; - int err; + int err = 0; if (dev->driver == &w1_master_driver) { md = container_of(dev, struct w1_master, dev); @@ -576,19 +576,17 @@ static int w1_uevent(struct device *dev, struct kobj_uevent_env *env) event_owner, name, dev_name(dev)); if (dev->driver != &w1_slave_driver || !sl) - return 0; + goto end; err = add_uevent_var(env, "W1_FID=%02X", sl->reg_num.family); if (err) - return err; + goto end; err = add_uevent_var(env, "W1_SLAVE_ID=%024LX", (unsigned long long)sl->reg_num.id); - if (err) - return err; - - return 0; -}; +end: + return err; +} #else static int w1_uevent(struct device *dev, struct kobj_uevent_env *env) { @@ -887,16 +885,21 @@ void w1_search(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb * * Return 0 - device(s) present, 1 - no devices present. */ + mutex_lock(&dev->bus_mutex); if (w1_reset_bus(dev)) { + mutex_unlock(&dev->bus_mutex); dev_dbg(&dev->dev, "No devices present on the wire.\n"); break; } /* Do fast search on single slave bus */ if (dev->max_slave_count == 1) { + int rv; w1_write_8(dev, W1_READ_ROM); + rv = w1_read_block(dev, (u8 *)&rn, 8); + mutex_unlock(&dev->bus_mutex); - if (w1_read_block(dev, (u8 *)&rn, 8) == 8 && rn) + if (rv == 8 && rn) cb(dev, rn); break; @@ -929,10 +932,12 @@ void w1_search(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb rn |= (tmp64 << i); if (kthread_should_stop()) { + mutex_unlock(&dev->bus_mutex); dev_dbg(&dev->dev, "Abort w1_search\n"); return; } } + mutex_unlock(&dev->bus_mutex); if ( (triplet_ret & 0x03) != 0x03 ) { if ( (desc_bit == last_zero) || (last_zero < 0)) diff --git a/drivers/w1/w1.h b/drivers/w1/w1.h index 4d012ca..45908e5 100644 --- a/drivers/w1/w1.h +++ b/drivers/w1/w1.h @@ -180,6 +180,7 @@ struct w1_master struct task_struct *thread; struct mutex mutex; + struct mutex bus_mutex; struct device_driver *driver; struct device dev; diff --git a/drivers/w1/w1_family.h b/drivers/w1/w1_family.h index 874aeb0..b00ada4 100644 --- a/drivers/w1/w1_family.h +++ b/drivers/w1/w1_family.h @@ -30,6 +30,7 @@ #define W1_FAMILY_SMEM_01 0x01 #define W1_FAMILY_SMEM_81 0x81 #define W1_THERM_DS18S20 0x10 +#define W1_FAMILY_DS28E04 0x1C #define W1_COUNTER_DS2423 0x1D #define W1_THERM_DS1822 0x22 #define W1_EEPROM_DS2433 0x23 diff --git a/drivers/w1/w1_int.c b/drivers/w1/w1_int.c index 6828835..5a98649 100644 --- a/drivers/w1/w1_int.c +++ b/drivers/w1/w1_int.c @@ -76,6 +76,7 @@ static struct w1_master * w1_alloc_dev(u32 id, int slave_count, int slave_ttl, INIT_LIST_HEAD(&dev->slist); mutex_init(&dev->mutex); + mutex_init(&dev->bus_mutex); memcpy(&dev->dev, device, sizeof(struct device)); dev_set_name(&dev->dev, "w1_bus_master%u", dev->id); @@ -117,7 +118,7 @@ int w1_add_master_device(struct w1_bus_master *master) return(-EINVAL); } /* While it would be electrically possible to make a device that - * generated a strong pullup in bit bang mode, only hardare that + * generated a strong pullup in bit bang mode, only hardware that * controls 1-wire time frames are even expected to support a strong * pullup. w1_io.c would need to support calling set_pullup before * the last write_bit operation of a w1_write_8 which it currently diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index d17c20f..4733eab 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c @@ -495,7 +495,7 @@ void debugfs_remove(struct dentry *dentry) struct dentry *parent; int ret; - if (!dentry) + if (IS_ERR_OR_NULL(dentry)) return; parent = dentry->d_parent; @@ -527,7 +527,7 @@ void debugfs_remove_recursive(struct dentry *dentry) struct dentry *child; struct dentry *parent; - if (!dentry) + if (IS_ERR_OR_NULL(dentry)) return; parent = dentry->d_parent; diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index a5cf784..6b0bb00 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -307,6 +307,7 @@ static int sysfs_dentry_revalidate(struct dentry *dentry, unsigned int flags) { struct sysfs_dirent *sd; int is_dir; + int type; if (flags & LOOKUP_RCU) return -ECHILD; @@ -326,6 +327,15 @@ static int sysfs_dentry_revalidate(struct dentry *dentry, unsigned int flags) if (strcmp(dentry->d_name.name, sd->s_name) != 0) goto out_bad; + /* The sysfs dirent has been moved to a different namespace */ + type = KOBJ_NS_TYPE_NONE; + if (sd->s_parent) { + type = sysfs_ns_type(sd->s_parent); + if (type != KOBJ_NS_TYPE_NONE && + sysfs_info(dentry->d_sb)->ns[type] != sd->s_ns) + goto out_bad; + } + mutex_unlock(&sysfs_mutex); out_valid: return 1; diff --git a/include/linux/device.h b/include/linux/device.h index 5083bcc..52a5f15 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -691,6 +691,11 @@ struct device { struct iommu_group *iommu_group; }; +static inline struct device *kobj_to_dev(struct kobject *kobj) +{ + return container_of(kobj, struct device, kobj); +} + /* Get the wakeup routines, which depend on struct device */ #include <linux/pm_wakeup.h> diff --git a/include/linux/extcon/extcon_gpio.h b/include/linux/extcon/extcon_gpio.h index a2129b7..2d8307f 100644 --- a/include/linux/extcon/extcon_gpio.h +++ b/include/linux/extcon/extcon_gpio.h @@ -31,7 +31,7 @@ * @irq_flags IRQ Flags (e.g., IRQF_TRIGGER_LOW). * @state_on print_state is overriden with state_on if attached. If Null, * default method of extcon class is used. - * @state_off print_state is overriden with state_on if dettached. If Null, + * @state_off print_state is overriden with state_on if detached. If Null, * default method of extcon class is used. * * Note that in order for state_on or state_off to be valid, both state_on diff --git a/include/linux/genhd.h b/include/linux/genhd.h index 017a7fb..ae0aaa9 100644 --- a/include/linux/genhd.h +++ b/include/linux/genhd.h @@ -16,7 +16,6 @@ #ifdef CONFIG_BLOCK -#define kobj_to_dev(k) container_of((k), struct device, kobj) #define dev_to_disk(device) container_of((device), struct gendisk, part0.__dev) #define dev_to_part(device) container_of((device), struct hd_struct, __dev) #define disk_to_dev(disk) (&(disk)->part0.__dev) diff --git a/kernel/printk.c b/kernel/printk.c index ac4bc9e..50c96b5 100644 --- a/kernel/printk.c +++ b/kernel/printk.c @@ -216,6 +216,7 @@ struct log { */ static DEFINE_RAW_SPINLOCK(logbuf_lock); +#ifdef CONFIG_PRINTK /* the next printk record to read by syslog(READ) or /proc/kmsg */ static u64 syslog_seq; static u32 syslog_idx; @@ -228,14 +229,19 @@ static u32 log_first_idx; /* index and sequence number of the next record to store in the buffer */ static u64 log_next_seq; -#ifdef CONFIG_PRINTK static u32 log_next_idx; +/* the next printk record to write to the console */ +static u64 console_seq; +static u32 console_idx; +static enum log_flags console_prev; + /* the next printk record to read after the last 'clear' command */ static u64 clear_seq; static u32 clear_idx; -#define LOG_LINE_MAX 1024 +#define PREFIX_MAX 32 +#define LOG_LINE_MAX 1024 - PREFIX_MAX /* record buffer */ #if defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) @@ -360,6 +366,7 @@ static void log_store(int facility, int level, struct devkmsg_user { u64 seq; u32 idx; + enum log_flags prev; struct mutex lock; char buf[8192]; }; @@ -425,6 +432,7 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf, struct log *msg; u64 ts_usec; size_t i; + char cont = '-'; size_t len; ssize_t ret; @@ -462,8 +470,25 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf, msg = log_from_idx(user->idx); ts_usec = msg->ts_nsec; do_div(ts_usec, 1000); - len = sprintf(user->buf, "%u,%llu,%llu;", - (msg->facility << 3) | msg->level, user->seq, ts_usec); + + /* + * If we couldn't merge continuation line fragments during the print, + * export the stored flags to allow an optional external merge of the + * records. Merging the records isn't always neccessarily correct, like + * when we hit a race during printing. In most cases though, it produces + * better readable output. 'c' in the record flags mark the first + * fragment of a line, '+' the following. + */ + if (msg->flags & LOG_CONT && !(user->prev & LOG_CONT)) + cont = 'c'; + else if ((msg->flags & LOG_CONT) || + ((user->prev & LOG_CONT) && !(msg->flags & LOG_PREFIX))) + cont = '+'; + + len = sprintf(user->buf, "%u,%llu,%llu,%c;", + (msg->facility << 3) | msg->level, + user->seq, ts_usec, cont); + user->prev = msg->flags; /* escape non-printable characters */ for (i = 0; i < msg->text_len; i++) { @@ -646,6 +671,15 @@ void log_buf_kexec_setup(void) VMCOREINFO_SYMBOL(log_buf_len); VMCOREINFO_SYMBOL(log_first_idx); VMCOREINFO_SYMBOL(log_next_idx); + /* + * Export struct log size and field offsets. User space tools can + * parse it and detect any changes to structure down the line. + */ + VMCOREINFO_STRUCT_SIZE(log); + VMCOREINFO_OFFSET(log, ts_nsec); + VMCOREINFO_OFFSET(log, len); + VMCOREINFO_OFFSET(log, text_len); + VMCOREINFO_OFFSET(log, dict_len); } #endif @@ -876,7 +910,7 @@ static size_t msg_print_text(const struct log *msg, enum log_flags prev, if (buf) { if (print_prefix(msg, syslog, NULL) + - text_len + 1>= size - len) + text_len + 1 >= size - len) break; if (prefix) @@ -907,7 +941,7 @@ static int syslog_print(char __user *buf, int size) struct log *msg; int len = 0; - text = kmalloc(LOG_LINE_MAX, GFP_KERNEL); + text = kmalloc(LOG_LINE_MAX + PREFIX_MAX, GFP_KERNEL); if (!text) return -ENOMEM; @@ -930,7 +964,8 @@ static int syslog_print(char __user *buf, int size) skip = syslog_partial; msg = log_from_idx(syslog_idx); - n = msg_print_text(msg, syslog_prev, true, text, LOG_LINE_MAX); + n = msg_print_text(msg, syslog_prev, true, text, + LOG_LINE_MAX + PREFIX_MAX); if (n - syslog_partial <= size) { /* message fits into buffer, move forward */ syslog_idx = log_next(syslog_idx); @@ -969,7 +1004,7 @@ static int syslog_print_all(char __user *buf, int size, bool clear) char *text; int len = 0; - text = kmalloc(LOG_LINE_MAX, GFP_KERNEL); + text = kmalloc(LOG_LINE_MAX + PREFIX_MAX, GFP_KERNEL); if (!text) return -ENOMEM; @@ -1022,7 +1057,8 @@ static int syslog_print_all(char __user *buf, int size, bool clear) struct log *msg = log_from_idx(idx); int textlen; - textlen = msg_print_text(msg, prev, true, text, LOG_LINE_MAX); + textlen = msg_print_text(msg, prev, true, text, + LOG_LINE_MAX + PREFIX_MAX); if (textlen < 0) { len = textlen; break; @@ -1349,20 +1385,36 @@ static struct cont { u64 ts_nsec; /* time of first print */ u8 level; /* log level of first message */ u8 facility; /* log level of first message */ + enum log_flags flags; /* prefix, newline flags */ bool flushed:1; /* buffer sealed and committed */ } cont; -static void cont_flush(void) +static void cont_flush(enum log_flags flags) { if (cont.flushed) return; if (cont.len == 0) return; - log_store(cont.facility, cont.level, LOG_NOCONS, cont.ts_nsec, - NULL, 0, cont.buf, cont.len); - - cont.flushed = true; + if (cont.cons) { + /* + * If a fragment of this line was directly flushed to the + * console; wait for the console to pick up the rest of the + * line. LOG_NOCONS suppresses a duplicated output. + */ + log_store(cont.facility, cont.level, flags | LOG_NOCONS, + cont.ts_nsec, NULL, 0, cont.buf, cont.len); + cont.flags = flags; + cont.flushed = true; + } else { + /* + * If no fragment of this line ever reached the console, + * just submit it to the store and free the buffer. + */ + log_store(cont.facility, cont.level, flags, 0, + NULL, 0, cont.buf, cont.len); + cont.len = 0; + } } static bool cont_add(int facility, int level, const char *text, size_t len) @@ -1371,7 +1423,8 @@ static bool cont_add(int facility, int level, const char *text, size_t len) return false; if (cont.len + len > sizeof(cont.buf)) { - cont_flush(); + /* the line gets too long, split it up in separate records */ + cont_flush(LOG_CONT); return false; } @@ -1380,12 +1433,17 @@ static bool cont_add(int facility, int level, const char *text, size_t len) cont.level = level; cont.owner = current; cont.ts_nsec = local_clock(); + cont.flags = 0; cont.cons = 0; cont.flushed = false; } memcpy(cont.buf + cont.len, text, len); cont.len += len; + + if (cont.len > (sizeof(cont.buf) * 80) / 100) + cont_flush(LOG_CONT); + return true; } @@ -1394,7 +1452,7 @@ static size_t cont_print_text(char *text, size_t size) size_t textlen = 0; size_t len; - if (cont.cons == 0) { + if (cont.cons == 0 && (console_prev & LOG_NEWLINE)) { textlen += print_time(cont.ts_nsec, text); size -= textlen; } @@ -1409,7 +1467,8 @@ static size_t cont_print_text(char *text, size_t size) } if (cont.flushed) { - text[textlen++] = '\n'; + if (cont.flags & LOG_NEWLINE) + text[textlen++] = '\n'; /* got everything, release buffer */ cont.len = 0; } @@ -1507,7 +1566,7 @@ asmlinkage int vprintk_emit(int facility, int level, * or another task also prints continuation lines. */ if (cont.len && (lflags & LOG_PREFIX || cont.owner != current)) - cont_flush(); + cont_flush(LOG_NEWLINE); /* buffer line if possible, otherwise store it right away */ if (!cont_add(facility, level, text, text_len)) @@ -1525,7 +1584,7 @@ asmlinkage int vprintk_emit(int facility, int level, if (cont.len && cont.owner == current) { if (!(lflags & LOG_PREFIX)) stored = cont_add(facility, level, text, text_len); - cont_flush(); + cont_flush(LOG_NEWLINE); } if (!stored) @@ -1616,9 +1675,20 @@ asmlinkage int printk(const char *fmt, ...) } EXPORT_SYMBOL(printk); -#else +#else /* CONFIG_PRINTK */ +#define LOG_LINE_MAX 0 +#define PREFIX_MAX 0 #define LOG_LINE_MAX 0 +static u64 syslog_seq; +static u32 syslog_idx; +static u64 console_seq; +static u32 console_idx; +static enum log_flags syslog_prev; +static u64 log_first_seq; +static u32 log_first_idx; +static u64 log_next_seq; +static enum log_flags console_prev; static struct cont { size_t len; size_t cons; @@ -1902,10 +1972,34 @@ void wake_up_klogd(void) this_cpu_or(printk_pending, PRINTK_PENDING_WAKEUP); } -/* the next printk record to write to the console */ -static u64 console_seq; -static u32 console_idx; -static enum log_flags console_prev; +static void console_cont_flush(char *text, size_t size) +{ + unsigned long flags; + size_t len; + + raw_spin_lock_irqsave(&logbuf_lock, flags); + + if (!cont.len) + goto out; + + /* + * We still queue earlier records, likely because the console was + * busy. The earlier ones need to be printed before this one, we + * did not flush any fragment so far, so just let it queue up. + */ + if (console_seq < log_next_seq && !cont.cons) + goto out; + + len = cont_print_text(text, size); + raw_spin_unlock(&logbuf_lock); + stop_critical_timings(); + call_console_drivers(cont.level, text, len); + start_critical_timings(); + local_irq_restore(flags); + return; +out: + raw_spin_unlock_irqrestore(&logbuf_lock, flags); +} /** * console_unlock - unlock the console system @@ -1923,7 +2017,7 @@ static enum log_flags console_prev; */ void console_unlock(void) { - static char text[LOG_LINE_MAX]; + static char text[LOG_LINE_MAX + PREFIX_MAX]; static u64 seen_seq; unsigned long flags; bool wake_klogd = false; @@ -1937,19 +2031,7 @@ void console_unlock(void) console_may_schedule = 0; /* flush buffered message fragment immediately to console */ - raw_spin_lock_irqsave(&logbuf_lock, flags); - if (cont.len && (cont.cons < cont.len || cont.flushed)) { - size_t len; - - len = cont_print_text(text, sizeof(text)); - raw_spin_unlock(&logbuf_lock); - stop_critical_timings(); - call_console_drivers(cont.level, text, len); - start_critical_timings(); - local_irq_restore(flags); - } else - raw_spin_unlock_irqrestore(&logbuf_lock, flags); - + console_cont_flush(text, sizeof(text)); again: for (;;) { struct log *msg; @@ -1986,6 +2068,7 @@ skip: * will properly dump everything later. */ msg->flags &= ~LOG_NOCONS; + console_prev = msg->flags; goto skip; } |