From 96dd86fa588169b745a71aedf2070e80f4943623 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Fri, 15 Mar 2013 12:30:06 -0700 Subject: Drivers: hv: Add a new driver to support host initiated backup This driver supports host initiated backup of the guest. On Windows guests, the host can generate application consistent backups using the Windows VSS framework. On Linux, we ensure that the backup will be file system consistent. This driver allows the host to initiate a "Freeze" operation on all the mounted file systems in the guest. Once the mounted file systems in the guest are frozen, the host snapshots the guest's file systems. Once this is done, the guest's file systems are "thawed". This driver has a user-level component (daemon) that invokes the appropriate operation on all the mounted file systems in response to the requests from the host. The duration for which the guest is frozen is very short - a few seconds. During this interval, the diff disk is comitted. In this version of the patch I have addressed the feedback from Olaf Herring. Also, some of the connector related issues have been fixed. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Cc: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- include/linux/hyperv.h | 69 ++++++++++++++++++++++++++++++++++++++++++ include/uapi/linux/connector.h | 5 ++- 2 files changed, 73 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index df77ba9a8166..95d0850584da 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -27,6 +27,63 @@ #include + +/* + * Implementation of host controlled snapshot of the guest. + */ + +#define VSS_OP_REGISTER 128 + +enum hv_vss_op { + VSS_OP_CREATE = 0, + VSS_OP_DELETE, + VSS_OP_HOT_BACKUP, + VSS_OP_GET_DM_INFO, + VSS_OP_BU_COMPLETE, + /* + * Following operations are only supported with IC version >= 5.0 + */ + VSS_OP_FREEZE, /* Freeze the file systems in the VM */ + VSS_OP_THAW, /* Unfreeze the file systems */ + VSS_OP_AUTO_RECOVER, + VSS_OP_COUNT /* Number of operations, must be last */ +}; + + +/* + * Header for all VSS messages. + */ +struct hv_vss_hdr { + __u8 operation; + __u8 reserved[7]; +} __attribute__((packed)); + + +/* + * Flag values for the hv_vss_check_feature. Linux supports only + * one value. + */ +#define VSS_HBU_NO_AUTO_RECOVERY 0x00000005 + +struct hv_vss_check_feature { + __u32 flags; +} __attribute__((packed)); + +struct hv_vss_check_dm_info { + __u32 flags; +} __attribute__((packed)); + +struct hv_vss_msg { + union { + struct hv_vss_hdr vss_hdr; + int error; + }; + union { + struct hv_vss_check_feature vss_cf; + struct hv_vss_check_dm_info dm_info; + }; +} __attribute__((packed)); + /* * An implementation of HyperV key value pair (KVP) functionality for Linux. * @@ -1252,6 +1309,14 @@ void vmbus_driver_unregister(struct hv_driver *hv_driver); 0xb9, 0x8b, 0x8b, 0xa1, 0xa1, 0xf3, 0xf9, 0x5a \ } +/* + * VSS (Backup/Restore) GUID + */ +#define HV_VSS_GUID \ + .guid = { \ + 0x29, 0x2e, 0xfa, 0x35, 0x23, 0xea, 0x36, 0x42, \ + 0x96, 0xae, 0x3a, 0x6e, 0xba, 0xcb, 0xa4, 0x40 \ + } /* * Common header for Hyper-V ICs */ @@ -1356,6 +1421,10 @@ int hv_kvp_init(struct hv_util_service *); void hv_kvp_deinit(void); void hv_kvp_onchannelcallback(void *); +int hv_vss_init(struct hv_util_service *); +void hv_vss_deinit(void); +void hv_vss_onchannelcallback(void *); + /* * Negotiated version with the Host. */ diff --git a/include/uapi/linux/connector.h b/include/uapi/linux/connector.h index 8761a0349c74..4cb283505e45 100644 --- a/include/uapi/linux/connector.h +++ b/include/uapi/linux/connector.h @@ -44,8 +44,11 @@ #define CN_VAL_DRBD 0x1 #define CN_KVP_IDX 0x9 /* HyperV KVP */ #define CN_KVP_VAL 0x1 /* queries from the kernel */ +#define CN_VSS_IDX 0xA /* HyperV VSS */ +#define CN_VSS_VAL 0x1 /* queries from the kernel */ -#define CN_NETLINK_USERS 10 /* Highest index + 1 */ + +#define CN_NETLINK_USERS 11 /* Highest index + 1 */ /* * Maximum connector's message size. -- cgit v1.2.3 From fa882867ae5f8543eb304a1667563f1c99514475 Mon Sep 17 00:00:00 2001 From: Samuel Iglesias Gonsalvez Date: Fri, 8 Mar 2013 09:21:46 +0100 Subject: ipack: add ipack_get_device() ipack_put_device() Prepare everything for later use. Signed-off-by: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/ipack.c | 12 ++++++++++++ include/linux/ipack.h | 3 +++ 2 files changed, 15 insertions(+) (limited to 'include') diff --git a/drivers/ipack/ipack.c b/drivers/ipack/ipack.c index 7ec6b208b1cb..4f913aa88971 100644 --- a/drivers/ipack/ipack.c +++ b/drivers/ipack/ipack.c @@ -461,6 +461,18 @@ void ipack_device_unregister(struct ipack_device *dev) } EXPORT_SYMBOL_GPL(ipack_device_unregister); +void ipack_get_device(struct ipack_device *dev) +{ + get_device(&dev->dev); +} +EXPORT_SYMBOL_GPL(ipack_get_device); + +void ipack_put_device(struct ipack_device *dev) +{ + put_device(&dev->dev); +} +EXPORT_SYMBOL_GPL(ipack_put_device); + static int __init ipack_init(void) { ida_init(&ipack_ida); diff --git a/include/linux/ipack.h b/include/linux/ipack.h index fea12cbb2aeb..def91fd996f4 100644 --- a/include/linux/ipack.h +++ b/include/linux/ipack.h @@ -221,6 +221,9 @@ void ipack_driver_unregister(struct ipack_driver *edrv); int ipack_device_register(struct ipack_device *dev); void ipack_device_unregister(struct ipack_device *dev); +void ipack_get_device(struct ipack_device *dev); +void ipack_put_device(struct ipack_device *dev); + /** * DEFINE_IPACK_DEVICE_TABLE - macro used to describe a IndustryPack table * @_table: device table name -- cgit v1.2.3 From e926301b39a07f587ff8c66354a2e2ee4c29162c Mon Sep 17 00:00:00 2001 From: Samuel Iglesias Gonsalvez Date: Fri, 8 Mar 2013 09:21:47 +0100 Subject: ipack: split ipack_device_register() in several functions One function is ipack_device_init(). If it fails, the caller should execute ipack_put_device(). The second function is ipack_device_add that only adds the device. If it fails, the caller should execute ipack_put_device(). Then the device is removed with refcount = 0, as device_register() kernel documentation says. ipack_device_del() is added to remove the device. Signed-off-by: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/carriers/tpci200.c | 14 +++++++++++++- drivers/ipack/ipack.c | 24 ++++++++++++++---------- include/linux/ipack.h | 39 +++++++++++++++++++++++++++++---------- 3 files changed, 56 insertions(+), 21 deletions(-) (limited to 'include') diff --git a/drivers/ipack/carriers/tpci200.c b/drivers/ipack/carriers/tpci200.c index 0246b1fddffe..c276fde318e5 100644 --- a/drivers/ipack/carriers/tpci200.c +++ b/drivers/ipack/carriers/tpci200.c @@ -480,6 +480,7 @@ static void tpci200_release_device(struct ipack_device *dev) static int tpci200_create_device(struct tpci200_board *tpci200, int i) { + int ret; enum ipack_space space; struct ipack_device *dev = kzalloc(sizeof(struct ipack_device), GFP_KERNEL); @@ -495,7 +496,18 @@ static int tpci200_create_device(struct tpci200_board *tpci200, int i) + tpci200_space_interval[space] * i; dev->region[space].size = tpci200_space_size[space]; } - return ipack_device_register(dev); + + ret = ipack_device_init(dev); + if (ret < 0) { + ipack_put_device(dev); + return ret; + } + + ret = ipack_device_add(dev); + if (ret < 0) + ipack_put_device(dev); + + return ret; } static int tpci200_pci_probe(struct pci_dev *pdev, diff --git a/drivers/ipack/ipack.c b/drivers/ipack/ipack.c index 4f913aa88971..6e066c53acce 100644 --- a/drivers/ipack/ipack.c +++ b/drivers/ipack/ipack.c @@ -227,7 +227,7 @@ static int ipack_unregister_bus_member(struct device *dev, void *data) struct ipack_bus_device *bus = data; if (idev->bus == bus) - ipack_device_unregister(idev); + ipack_device_del(idev); return 1; } @@ -419,7 +419,7 @@ out: return ret; } -int ipack_device_register(struct ipack_device *dev) +int ipack_device_init(struct ipack_device *dev) { int ret; @@ -428,6 +428,7 @@ int ipack_device_register(struct ipack_device *dev) dev->dev.parent = dev->bus->parent; dev_set_name(&dev->dev, "ipack-dev.%u.%u", dev->bus->bus_nr, dev->slot); + device_initialize(&dev->dev); if (dev->bus->ops->set_clockrate(dev, 8)) dev_warn(&dev->dev, "failed to switch to 8 MHz operation for reading of device ID.\n"); @@ -447,19 +448,22 @@ int ipack_device_register(struct ipack_device *dev) dev_err(&dev->dev, "failed to switch to 32 MHz operation.\n"); } - ret = device_register(&dev->dev); - if (ret < 0) - kfree(dev->id); + return 0; +} +EXPORT_SYMBOL_GPL(ipack_device_init); - return ret; +int ipack_device_add(struct ipack_device *dev) +{ + return device_add(&dev->dev); } -EXPORT_SYMBOL_GPL(ipack_device_register); +EXPORT_SYMBOL_GPL(ipack_device_add); -void ipack_device_unregister(struct ipack_device *dev) +void ipack_device_del(struct ipack_device *dev) { - device_unregister(&dev->dev); + device_del(&dev->dev); + ipack_put_device(dev); } -EXPORT_SYMBOL_GPL(ipack_device_unregister); +EXPORT_SYMBOL_GPL(ipack_device_del); void ipack_get_device(struct ipack_device *dev) { diff --git a/include/linux/ipack.h b/include/linux/ipack.h index def91fd996f4..1888e06ddf64 100644 --- a/include/linux/ipack.h +++ b/include/linux/ipack.h @@ -207,19 +207,38 @@ int ipack_driver_register(struct ipack_driver *edrv, struct module *owner, void ipack_driver_unregister(struct ipack_driver *edrv); /** - * ipack_device_register -- register an IPack device with the kernel - * @dev: the new device to register. + * ipack_device_init -- initialize an IPack device + * @dev: the new device to initialize. * - * Register a new IPack device ("module" in IndustryPack jargon). The call - * is done by the carrier driver. The carrier should populate the fields - * bus and slot as well as the region array of @dev prior to calling this - * function. The rest of the fields will be allocated and populated - * during registration. + * Initialize a new IPack device ("module" in IndustryPack jargon). The call + * is done by the carrier driver. The carrier should populate the fields + * bus and slot as well as the region array of @dev prior to calling this + * function. The rest of the fields will be allocated and populated + * during initalization. * - * Return zero on success or error code on failure. + * Return zero on success or error code on failure. + * + * NOTE: _Never_ directly free @dev after calling this function, even + * if it returned an error! Always use ipack_put_device() to give up the + * reference initialized in this function instead. + */ +int ipack_device_init(struct ipack_device *dev); + +/** + * ipack_device_add -- Add an IPack device + * @dev: the new device to add. + * + * Add a new IPack device. The call is done by the carrier driver + * after calling ipack_device_init(). + * + * Return zero on success or error code on failure. + * + * NOTE: _Never_ directly free @dev after calling this function, even + * if it returned an error! Always use ipack_put_device() to give up the + * reference initialized in this function instead. */ -int ipack_device_register(struct ipack_device *dev); -void ipack_device_unregister(struct ipack_device *dev); +int ipack_device_add(struct ipack_device *dev); +void ipack_device_del(struct ipack_device *dev); void ipack_get_device(struct ipack_device *dev); void ipack_put_device(struct ipack_device *dev); -- cgit v1.2.3 From 6ed7ffddcf61f668114edb676417e5fb33773b59 Mon Sep 17 00:00:00 2001 From: H Hartley Sweeten Date: Wed, 6 Mar 2013 11:24:44 -0700 Subject: pcmcia/ds.h: introduce helper for pcmcia_driver module boilerplate Introduce the module_pcmcia_driver() macro which is a convenience macro for pcmcia driver modules. It is intended to be used by pcmcia drivers with init/exit sections that do nothing but register/unregister the pcmcia driver. Signed-off-by: H Hartley Sweeten Cc: linux-pcmcia@lists.infradead.org Signed-off-by: Greg Kroah-Hartman --- include/pcmcia/ds.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'include') diff --git a/include/pcmcia/ds.h b/include/pcmcia/ds.h index 3bbbd78e1439..2d56e428506c 100644 --- a/include/pcmcia/ds.h +++ b/include/pcmcia/ds.h @@ -65,6 +65,18 @@ struct pcmcia_driver { int pcmcia_register_driver(struct pcmcia_driver *driver); void pcmcia_unregister_driver(struct pcmcia_driver *driver); +/** + * module_pcmcia_driver() - Helper macro for registering a pcmcia driver + * @__pcmcia_driver: pcmcia_driver struct + * + * Helper macro for pcmcia drivers which do not do anything special in module + * init/exit. This eliminates a lot of boilerplate. Each module may only use + * this macro once, and calling it replaces module_init() and module_exit(). + */ +#define module_pcmcia_driver(__pcmcia_driver) \ + module_driver(__pcmcia_driver, pcmcia_register_driver, \ + pcmcia_unregister_driver) + /* for struct resource * array embedded in struct pcmcia_device */ enum { PCMCIA_IOPORT_0, -- cgit v1.2.3 From e44b0ceee4cc2a926225e73ac1e20b9a5bb22c2d Mon Sep 17 00:00:00 2001 From: Kenneth Heitke Date: Tue, 12 Mar 2013 11:41:46 -0700 Subject: add single-wire serial bus interface (SSBI) driver SSBI is the Qualcomm single-wire serial bus interface used to connect the MSM devices to the PMIC and other devices. Since SSBI only supports a single slave, the driver gets the name of the slave device passed in from the board file through the master device's platform data. SSBI registers pretty early (postcore), so that the PMIC can come up before the board init. This is useful if the board init requires the use of gpios that are connected through the PMIC. Based on a patch by Dima Zavin that can be found at: http://android.git.kernel.org/?p=kernel/msm.git;a=commitdiff;h=eb060bac4 This patch adds PMIC Arbiter support for the MSM8660. The PMIC Arbiter is a hardware wrapper around the SSBI 2.0 controller that is designed to overcome concurrency issues and security limitations. A controller_type field is added to the platform data to specify the type of the SSBI controller (1.0, 2.0, or PMIC Arbiter). [davidb@codeaurora.org: I've moved this driver into drivers/ssbi/ and added an include for linux/module.h so that it will compile] Signed-off-by: Kenneth Heitke Signed-off-by: David Brown Signed-off-by: Greg Kroah-Hartman --- drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/ssbi/Kconfig | 16 ++ drivers/ssbi/Makefile | 1 + drivers/ssbi/ssbi.c | 397 +++++++++++++++++++++++++++++++++++++++++++++++ include/linux/msm_ssbi.h | 49 ++++++ 6 files changed, 466 insertions(+) create mode 100644 drivers/ssbi/Kconfig create mode 100644 drivers/ssbi/Makefile create mode 100644 drivers/ssbi/ssbi.c create mode 100644 include/linux/msm_ssbi.h (limited to 'include') diff --git a/drivers/Kconfig b/drivers/Kconfig index 202fa6d051b9..78a956e286e6 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -52,6 +52,8 @@ source "drivers/i2c/Kconfig" source "drivers/spi/Kconfig" +source "drivers/ssbi/Kconfig" + source "drivers/hsi/Kconfig" source "drivers/pps/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index dce39a95fa71..778821ba3f68 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -114,6 +114,7 @@ obj-y += firmware/ obj-$(CONFIG_CRYPTO) += crypto/ obj-$(CONFIG_SUPERH) += sh/ obj-$(CONFIG_ARCH_SHMOBILE) += sh/ +obj-$(CONFIG_MSM_SSBI) += ssbi/ ifndef CONFIG_ARCH_USES_GETTIMEOFFSET obj-y += clocksource/ endif diff --git a/drivers/ssbi/Kconfig b/drivers/ssbi/Kconfig new file mode 100644 index 000000000000..b57c41bd0119 --- /dev/null +++ b/drivers/ssbi/Kconfig @@ -0,0 +1,16 @@ +# +# MSM SSBI bus support +# + +menu "Qualcomm MSM SSBI bus support" + +config MSM_SSBI + bool "Qualcomm Single-wire Serial Bus Interface (SSBI)" + help + If you say yes to this option, support will be included for the + built-in SSBI interface on Qualcomm MSM family processors. + + This is required for communicating with Qualcomm PMICs and + other devices that have the SSBI interface. + +endmenu diff --git a/drivers/ssbi/Makefile b/drivers/ssbi/Makefile new file mode 100644 index 000000000000..22e408f45d61 --- /dev/null +++ b/drivers/ssbi/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_MSM_SSBI) += ssbi.o diff --git a/drivers/ssbi/ssbi.c b/drivers/ssbi/ssbi.c new file mode 100644 index 000000000000..8b0b10d6e1de --- /dev/null +++ b/drivers/ssbi/ssbi.c @@ -0,0 +1,397 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * Copyright (c) 2010, Google Inc. + * + * Original authors: Code Aurora Forum + * + * Author: Dima Zavin + * - Largely rewritten from original to not be an i2c driver. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* SSBI 2.0 controller registers */ +#define SSBI2_CMD 0x0008 +#define SSBI2_RD 0x0010 +#define SSBI2_STATUS 0x0014 +#define SSBI2_MODE2 0x001C + +/* SSBI_CMD fields */ +#define SSBI_CMD_RDWRN (1 << 24) + +/* SSBI_STATUS fields */ +#define SSBI_STATUS_RD_READY (1 << 2) +#define SSBI_STATUS_READY (1 << 1) +#define SSBI_STATUS_MCHN_BUSY (1 << 0) + +/* SSBI_MODE2 fields */ +#define SSBI_MODE2_REG_ADDR_15_8_SHFT 0x04 +#define SSBI_MODE2_REG_ADDR_15_8_MASK (0x7f << SSBI_MODE2_REG_ADDR_15_8_SHFT) + +#define SET_SSBI_MODE2_REG_ADDR_15_8(MD, AD) \ + (((MD) & 0x0F) | ((((AD) >> 8) << SSBI_MODE2_REG_ADDR_15_8_SHFT) & \ + SSBI_MODE2_REG_ADDR_15_8_MASK)) + +/* SSBI PMIC Arbiter command registers */ +#define SSBI_PA_CMD 0x0000 +#define SSBI_PA_RD_STATUS 0x0004 + +/* SSBI_PA_CMD fields */ +#define SSBI_PA_CMD_RDWRN (1 << 24) +#define SSBI_PA_CMD_ADDR_MASK 0x7fff /* REG_ADDR_7_0, REG_ADDR_8_14*/ + +/* SSBI_PA_RD_STATUS fields */ +#define SSBI_PA_RD_STATUS_TRANS_DONE (1 << 27) +#define SSBI_PA_RD_STATUS_TRANS_DENIED (1 << 26) + +#define SSBI_TIMEOUT_US 100 + +struct msm_ssbi { + struct device *dev; + struct device *slave; + void __iomem *base; + spinlock_t lock; + enum msm_ssbi_controller_type controller_type; + int (*read)(struct msm_ssbi *, u16 addr, u8 *buf, int len); + int (*write)(struct msm_ssbi *, u16 addr, u8 *buf, int len); +}; + +#define to_msm_ssbi(dev) platform_get_drvdata(to_platform_device(dev)) + +static inline u32 ssbi_readl(struct msm_ssbi *ssbi, u32 reg) +{ + return readl(ssbi->base + reg); +} + +static inline void ssbi_writel(struct msm_ssbi *ssbi, u32 val, u32 reg) +{ + writel(val, ssbi->base + reg); +} + +static int ssbi_wait_mask(struct msm_ssbi *ssbi, u32 set_mask, u32 clr_mask) +{ + u32 timeout = SSBI_TIMEOUT_US; + u32 val; + + while (timeout--) { + val = ssbi_readl(ssbi, SSBI2_STATUS); + if (((val & set_mask) == set_mask) && ((val & clr_mask) == 0)) + return 0; + udelay(1); + } + + dev_err(ssbi->dev, "%s: timeout (status %x set_mask %x clr_mask %x)\n", + __func__, ssbi_readl(ssbi, SSBI2_STATUS), set_mask, clr_mask); + return -ETIMEDOUT; +} + +static int +msm_ssbi_read_bytes(struct msm_ssbi *ssbi, u16 addr, u8 *buf, int len) +{ + u32 cmd = SSBI_CMD_RDWRN | ((addr & 0xff) << 16); + int ret = 0; + + if (ssbi->controller_type == MSM_SBI_CTRL_SSBI2) { + u32 mode2 = ssbi_readl(ssbi, SSBI2_MODE2); + mode2 = SET_SSBI_MODE2_REG_ADDR_15_8(mode2, addr); + ssbi_writel(ssbi, mode2, SSBI2_MODE2); + } + + while (len) { + ret = ssbi_wait_mask(ssbi, SSBI_STATUS_READY, 0); + if (ret) + goto err; + + ssbi_writel(ssbi, cmd, SSBI2_CMD); + ret = ssbi_wait_mask(ssbi, SSBI_STATUS_RD_READY, 0); + if (ret) + goto err; + *buf++ = ssbi_readl(ssbi, SSBI2_RD) & 0xff; + len--; + } + +err: + return ret; +} + +static int +msm_ssbi_write_bytes(struct msm_ssbi *ssbi, u16 addr, u8 *buf, int len) +{ + int ret = 0; + + if (ssbi->controller_type == MSM_SBI_CTRL_SSBI2) { + u32 mode2 = ssbi_readl(ssbi, SSBI2_MODE2); + mode2 = SET_SSBI_MODE2_REG_ADDR_15_8(mode2, addr); + ssbi_writel(ssbi, mode2, SSBI2_MODE2); + } + + while (len) { + ret = ssbi_wait_mask(ssbi, SSBI_STATUS_READY, 0); + if (ret) + goto err; + + ssbi_writel(ssbi, ((addr & 0xff) << 16) | *buf, SSBI2_CMD); + ret = ssbi_wait_mask(ssbi, 0, SSBI_STATUS_MCHN_BUSY); + if (ret) + goto err; + buf++; + len--; + } + +err: + return ret; +} + +static inline int +msm_ssbi_pa_transfer(struct msm_ssbi *ssbi, u32 cmd, u8 *data) +{ + u32 timeout = SSBI_TIMEOUT_US; + u32 rd_status = 0; + + ssbi_writel(ssbi, cmd, SSBI_PA_CMD); + + while (timeout--) { + rd_status = ssbi_readl(ssbi, SSBI_PA_RD_STATUS); + + if (rd_status & SSBI_PA_RD_STATUS_TRANS_DENIED) { + dev_err(ssbi->dev, "%s: transaction denied (0x%x)\n", + __func__, rd_status); + return -EPERM; + } + + if (rd_status & SSBI_PA_RD_STATUS_TRANS_DONE) { + if (data) + *data = rd_status & 0xff; + return 0; + } + udelay(1); + } + + dev_err(ssbi->dev, "%s: timeout, status 0x%x\n", __func__, rd_status); + return -ETIMEDOUT; +} + +static int +msm_ssbi_pa_read_bytes(struct msm_ssbi *ssbi, u16 addr, u8 *buf, int len) +{ + u32 cmd; + int ret = 0; + + cmd = SSBI_PA_CMD_RDWRN | (addr & SSBI_PA_CMD_ADDR_MASK) << 8; + + while (len) { + ret = msm_ssbi_pa_transfer(ssbi, cmd, buf); + if (ret) + goto err; + buf++; + len--; + } + +err: + return ret; +} + +static int +msm_ssbi_pa_write_bytes(struct msm_ssbi *ssbi, u16 addr, u8 *buf, int len) +{ + u32 cmd; + int ret = 0; + + while (len) { + cmd = (addr & SSBI_PA_CMD_ADDR_MASK) << 8 | *buf; + ret = msm_ssbi_pa_transfer(ssbi, cmd, NULL); + if (ret) + goto err; + buf++; + len--; + } + +err: + return ret; +} + +int msm_ssbi_read(struct device *dev, u16 addr, u8 *buf, int len) +{ + struct msm_ssbi *ssbi = to_msm_ssbi(dev); + unsigned long flags; + int ret; + + if (ssbi->dev != dev) + return -ENXIO; + + spin_lock_irqsave(&ssbi->lock, flags); + ret = ssbi->read(ssbi, addr, buf, len); + spin_unlock_irqrestore(&ssbi->lock, flags); + + return ret; +} +EXPORT_SYMBOL(msm_ssbi_read); + +int msm_ssbi_write(struct device *dev, u16 addr, u8 *buf, int len) +{ + struct msm_ssbi *ssbi = to_msm_ssbi(dev); + unsigned long flags; + int ret; + + if (ssbi->dev != dev) + return -ENXIO; + + spin_lock_irqsave(&ssbi->lock, flags); + ret = ssbi->write(ssbi, addr, buf, len); + spin_unlock_irqrestore(&ssbi->lock, flags); + + return ret; +} +EXPORT_SYMBOL(msm_ssbi_write); + +static int msm_ssbi_add_slave(struct msm_ssbi *ssbi, + const struct msm_ssbi_slave_info *slave) +{ + struct platform_device *slave_pdev; + int ret; + + if (ssbi->slave) { + pr_err("slave already attached??\n"); + return -EBUSY; + } + + slave_pdev = platform_device_alloc(slave->name, -1); + if (!slave_pdev) { + pr_err("cannot allocate pdev for slave '%s'", slave->name); + ret = -ENOMEM; + goto err; + } + + slave_pdev->dev.parent = ssbi->dev; + slave_pdev->dev.platform_data = slave->platform_data; + + ret = platform_device_add(slave_pdev); + if (ret) { + pr_err("cannot add slave platform device for '%s'\n", + slave->name); + goto err; + } + + ssbi->slave = &slave_pdev->dev; + return 0; + +err: + if (slave_pdev) + platform_device_put(slave_pdev); + return ret; +} + +static int msm_ssbi_probe(struct platform_device *pdev) +{ + const struct msm_ssbi_platform_data *pdata = pdev->dev.platform_data; + struct resource *mem_res; + struct msm_ssbi *ssbi; + int ret = 0; + + if (!pdata) { + pr_err("missing platform data\n"); + return -EINVAL; + } + + pr_debug("%s\n", pdata->slave.name); + + ssbi = kzalloc(sizeof(struct msm_ssbi), GFP_KERNEL); + if (!ssbi) { + pr_err("can not allocate ssbi_data\n"); + return -ENOMEM; + } + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem_res) { + pr_err("missing mem resource\n"); + ret = -EINVAL; + goto err_get_mem_res; + } + + ssbi->base = ioremap(mem_res->start, resource_size(mem_res)); + if (!ssbi->base) { + pr_err("ioremap of 0x%p failed\n", (void *)mem_res->start); + ret = -EINVAL; + goto err_ioremap; + } + ssbi->dev = &pdev->dev; + platform_set_drvdata(pdev, ssbi); + + ssbi->controller_type = pdata->controller_type; + if (ssbi->controller_type == MSM_SBI_CTRL_PMIC_ARBITER) { + ssbi->read = msm_ssbi_pa_read_bytes; + ssbi->write = msm_ssbi_pa_write_bytes; + } else { + ssbi->read = msm_ssbi_read_bytes; + ssbi->write = msm_ssbi_write_bytes; + } + + spin_lock_init(&ssbi->lock); + + ret = msm_ssbi_add_slave(ssbi, &pdata->slave); + if (ret) + goto err_ssbi_add_slave; + + return 0; + +err_ssbi_add_slave: + platform_set_drvdata(pdev, NULL); + iounmap(ssbi->base); +err_ioremap: +err_get_mem_res: + kfree(ssbi); + return ret; +} + +static int msm_ssbi_remove(struct platform_device *pdev) +{ + struct msm_ssbi *ssbi = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + iounmap(ssbi->base); + kfree(ssbi); + return 0; +} + +static struct platform_driver msm_ssbi_driver = { + .probe = msm_ssbi_probe, + .remove = __exit_p(msm_ssbi_remove), + .driver = { + .name = "msm_ssbi", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_ssbi_init(void) +{ + return platform_driver_register(&msm_ssbi_driver); +} +postcore_initcall(msm_ssbi_init); + +static void __exit msm_ssbi_exit(void) +{ + platform_driver_unregister(&msm_ssbi_driver); +} +module_exit(msm_ssbi_exit) + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:msm_ssbi"); +MODULE_AUTHOR("Dima Zavin "); diff --git a/include/linux/msm_ssbi.h b/include/linux/msm_ssbi.h new file mode 100644 index 000000000000..cfa47df6d003 --- /dev/null +++ b/include/linux/msm_ssbi.h @@ -0,0 +1,49 @@ +/* Copyright (C) 2010 Google, Inc. + * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * Author: Dima Zavin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + */ + +#ifndef _LINUX_MSM_SSBI_H +#define _LINUX_MSM_SSBI_H + +#include + +struct msm_ssbi_slave_info { + const char *name; + void *platform_data; +}; + +enum msm_ssbi_controller_type { + MSM_SBI_CTRL_SSBI = 0, + MSM_SBI_CTRL_SSBI2, + MSM_SBI_CTRL_PMIC_ARBITER, +}; + +struct msm_ssbi_platform_data { + struct msm_ssbi_slave_info slave; + enum msm_ssbi_controller_type controller_type; +}; + +#ifdef CONFIG_MSM_SSBI +int msm_ssbi_write(struct device *dev, u16 addr, u8 *buf, int len); +int msm_ssbi_read(struct device *dev, u16 addr, u8 *buf, int len); +#else +static inline int msm_ssbi_write(struct device *dev, u16 addr, u8 *buf, int len) +{ + return -ENXIO; +} +static inline int msm_ssbi_read(struct device *dev, u16 addr, u8 *buf, int len) +{ + return -ENXIO; +} +#endif +#endif -- cgit v1.2.3 From 4a6692e2ac4c6b09235a9568468dd83a380c271d Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 12 Mar 2013 11:41:49 -0700 Subject: ssbi: Allow compilation as a module The ssbi driver's read/write entry points are protected with wrappers in the case when the driver isn't enabled. These wrappers don't make any sense, since a client of the SSBI bus won't work without it. Make these just regular functions, so that the SSBI driver can be built as a module. Signed-off-by: David Brown Signed-off-by: Greg Kroah-Hartman --- drivers/ssbi/Kconfig | 2 +- include/linux/msm_ssbi.h | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) (limited to 'include') diff --git a/drivers/ssbi/Kconfig b/drivers/ssbi/Kconfig index b57c41bd0119..c7bc534ddf50 100644 --- a/drivers/ssbi/Kconfig +++ b/drivers/ssbi/Kconfig @@ -5,7 +5,7 @@ menu "Qualcomm MSM SSBI bus support" config MSM_SSBI - bool "Qualcomm Single-wire Serial Bus Interface (SSBI)" + tristate "Qualcomm Single-wire Serial Bus Interface (SSBI)" help If you say yes to this option, support will be included for the built-in SSBI interface on Qualcomm MSM family processors. diff --git a/include/linux/msm_ssbi.h b/include/linux/msm_ssbi.h index cfa47df6d003..0fe245bb2940 100644 --- a/include/linux/msm_ssbi.h +++ b/include/linux/msm_ssbi.h @@ -33,17 +33,6 @@ struct msm_ssbi_platform_data { enum msm_ssbi_controller_type controller_type; }; -#ifdef CONFIG_MSM_SSBI int msm_ssbi_write(struct device *dev, u16 addr, u8 *buf, int len); int msm_ssbi_read(struct device *dev, u16 addr, u8 *buf, int len); -#else -static inline int msm_ssbi_write(struct device *dev, u16 addr, u8 *buf, int len) -{ - return -ENXIO; -} -static inline int msm_ssbi_read(struct device *dev, u16 addr, u8 *buf, int len) -{ - return -ENXIO; -} -#endif #endif -- cgit v1.2.3 From ce44bf5b5544cbe6358abb01f039361a99b80901 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 12 Mar 2013 11:41:54 -0700 Subject: SSBI: Remove MSM_ prefix from SSBI drivers Although the SSBI sub is currently only used on MSM SoCs, it is still a bus in its own right. Remove this msm_ prefix from the driver and it's symbols. Clients can now refer directly to ssbi_write() and ssbi_read(). Signed-off-by: David Brown Signed-off-by: Greg Kroah-Hartman --- drivers/Makefile | 2 +- drivers/mfd/Kconfig | 2 +- drivers/mfd/pm8921-core.c | 14 ++++---- drivers/ssbi/Kconfig | 4 +-- drivers/ssbi/Makefile | 2 +- drivers/ssbi/ssbi.c | 86 +++++++++++++++++++++++------------------------ include/linux/msm_ssbi.h | 38 --------------------- include/linux/ssbi.h | 38 +++++++++++++++++++++ 8 files changed, 93 insertions(+), 93 deletions(-) delete mode 100644 include/linux/msm_ssbi.h create mode 100644 include/linux/ssbi.h (limited to 'include') diff --git a/drivers/Makefile b/drivers/Makefile index 778821ba3f68..4865ed24708a 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -114,7 +114,7 @@ obj-y += firmware/ obj-$(CONFIG_CRYPTO) += crypto/ obj-$(CONFIG_SUPERH) += sh/ obj-$(CONFIG_ARCH_SHMOBILE) += sh/ -obj-$(CONFIG_MSM_SSBI) += ssbi/ +obj-$(CONFIG_SSBI) += ssbi/ ifndef CONFIG_ARCH_USES_GETTIMEOFFSET obj-y += clocksource/ endif diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 671f5b171c73..5bfa7bb555b7 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -990,7 +990,7 @@ config MFD_PM8XXX config MFD_PM8921_CORE tristate "Qualcomm PM8921 PMIC chip" - depends on MSM_SSBI + depends on SSBI select MFD_CORE select MFD_PM8XXX help diff --git a/drivers/mfd/pm8921-core.c b/drivers/mfd/pm8921-core.c index d4b297cbd801..ecc137ffa8c3 100644 --- a/drivers/mfd/pm8921-core.c +++ b/drivers/mfd/pm8921-core.c @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include #include @@ -35,7 +35,7 @@ static int pm8921_readb(const struct device *dev, u16 addr, u8 *val) const struct pm8xxx_drvdata *pm8921_drvdata = dev_get_drvdata(dev); const struct pm8921 *pmic = pm8921_drvdata->pm_chip_data; - return msm_ssbi_read(pmic->dev->parent, addr, val, 1); + return ssbi_read(pmic->dev->parent, addr, val, 1); } static int pm8921_writeb(const struct device *dev, u16 addr, u8 val) @@ -43,7 +43,7 @@ static int pm8921_writeb(const struct device *dev, u16 addr, u8 val) const struct pm8xxx_drvdata *pm8921_drvdata = dev_get_drvdata(dev); const struct pm8921 *pmic = pm8921_drvdata->pm_chip_data; - return msm_ssbi_write(pmic->dev->parent, addr, &val, 1); + return ssbi_write(pmic->dev->parent, addr, &val, 1); } static int pm8921_read_buf(const struct device *dev, u16 addr, u8 *buf, @@ -52,7 +52,7 @@ static int pm8921_read_buf(const struct device *dev, u16 addr, u8 *buf, const struct pm8xxx_drvdata *pm8921_drvdata = dev_get_drvdata(dev); const struct pm8921 *pmic = pm8921_drvdata->pm_chip_data; - return msm_ssbi_read(pmic->dev->parent, addr, buf, cnt); + return ssbi_read(pmic->dev->parent, addr, buf, cnt); } static int pm8921_write_buf(const struct device *dev, u16 addr, u8 *buf, @@ -61,7 +61,7 @@ static int pm8921_write_buf(const struct device *dev, u16 addr, u8 *buf, const struct pm8xxx_drvdata *pm8921_drvdata = dev_get_drvdata(dev); const struct pm8921 *pmic = pm8921_drvdata->pm_chip_data; - return msm_ssbi_write(pmic->dev->parent, addr, buf, cnt); + return ssbi_write(pmic->dev->parent, addr, buf, cnt); } static int pm8921_read_irq_stat(const struct device *dev, int irq) @@ -124,7 +124,7 @@ static int pm8921_probe(struct platform_device *pdev) } /* Read PMIC chip revision */ - rc = msm_ssbi_read(pdev->dev.parent, REG_HWREV, &val, sizeof(val)); + rc = ssbi_read(pdev->dev.parent, REG_HWREV, &val, sizeof(val)); if (rc) { pr_err("Failed to read hw rev reg %d:rc=%d\n", REG_HWREV, rc); goto err_read_rev; @@ -133,7 +133,7 @@ static int pm8921_probe(struct platform_device *pdev) rev = val; /* Read PMIC chip revision 2 */ - rc = msm_ssbi_read(pdev->dev.parent, REG_HWREV_2, &val, sizeof(val)); + rc = ssbi_read(pdev->dev.parent, REG_HWREV_2, &val, sizeof(val)); if (rc) { pr_err("Failed to read hw rev 2 reg %d:rc=%d\n", REG_HWREV_2, rc); diff --git a/drivers/ssbi/Kconfig b/drivers/ssbi/Kconfig index c7bc534ddf50..1ae4040afedd 100644 --- a/drivers/ssbi/Kconfig +++ b/drivers/ssbi/Kconfig @@ -1,10 +1,10 @@ # -# MSM SSBI bus support +# SSBI bus support # menu "Qualcomm MSM SSBI bus support" -config MSM_SSBI +config SSBI tristate "Qualcomm Single-wire Serial Bus Interface (SSBI)" help If you say yes to this option, support will be included for the diff --git a/drivers/ssbi/Makefile b/drivers/ssbi/Makefile index 22e408f45d61..38fb70c31caf 100644 --- a/drivers/ssbi/Makefile +++ b/drivers/ssbi/Makefile @@ -1 +1 @@ -obj-$(CONFIG_MSM_SSBI) += ssbi.o +obj-$(CONFIG_SSBI) += ssbi.o diff --git a/drivers/ssbi/ssbi.c b/drivers/ssbi/ssbi.c index b056a072c3b3..f32da0258a8e 100644 --- a/drivers/ssbi/ssbi.c +++ b/drivers/ssbi/ssbi.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2009-2013, The Linux Foundation. All rights reserved. * Copyright (c) 2010, Google Inc. * * Original authors: Code Aurora Forum @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include #include @@ -65,23 +65,23 @@ #define SSBI_TIMEOUT_US 100 -struct msm_ssbi { +struct ssbi { struct device *slave; void __iomem *base; spinlock_t lock; - enum msm_ssbi_controller_type controller_type; - int (*read)(struct msm_ssbi *, u16 addr, u8 *buf, int len); - int (*write)(struct msm_ssbi *, u16 addr, u8 *buf, int len); + enum ssbi_controller_type controller_type; + int (*read)(struct ssbi *, u16 addr, u8 *buf, int len); + int (*write)(struct ssbi *, u16 addr, u8 *buf, int len); }; -#define to_msm_ssbi(dev) platform_get_drvdata(to_platform_device(dev)) +#define to_ssbi(dev) platform_get_drvdata(to_platform_device(dev)) -static inline u32 ssbi_readl(struct msm_ssbi *ssbi, u32 reg) +static inline u32 ssbi_readl(struct ssbi *ssbi, u32 reg) { return readl(ssbi->base + reg); } -static inline void ssbi_writel(struct msm_ssbi *ssbi, u32 val, u32 reg) +static inline void ssbi_writel(struct ssbi *ssbi, u32 val, u32 reg) { writel(val, ssbi->base + reg); } @@ -95,7 +95,7 @@ static inline void ssbi_writel(struct msm_ssbi *ssbi, u32 val, u32 reg) * * As such, this wait merely spins, with a udelay. */ -static int ssbi_wait_mask(struct msm_ssbi *ssbi, u32 set_mask, u32 clr_mask) +static int ssbi_wait_mask(struct ssbi *ssbi, u32 set_mask, u32 clr_mask) { u32 timeout = SSBI_TIMEOUT_US; u32 val; @@ -111,7 +111,7 @@ static int ssbi_wait_mask(struct msm_ssbi *ssbi, u32 set_mask, u32 clr_mask) } static int -msm_ssbi_read_bytes(struct msm_ssbi *ssbi, u16 addr, u8 *buf, int len) +ssbi_read_bytes(struct ssbi *ssbi, u16 addr, u8 *buf, int len) { u32 cmd = SSBI_CMD_RDWRN | ((addr & 0xff) << 16); int ret = 0; @@ -140,7 +140,7 @@ err: } static int -msm_ssbi_write_bytes(struct msm_ssbi *ssbi, u16 addr, u8 *buf, int len) +ssbi_write_bytes(struct ssbi *ssbi, u16 addr, u8 *buf, int len) { int ret = 0; @@ -172,7 +172,7 @@ err: * busywait. */ static inline int -msm_ssbi_pa_transfer(struct msm_ssbi *ssbi, u32 cmd, u8 *data) +ssbi_pa_transfer(struct ssbi *ssbi, u32 cmd, u8 *data) { u32 timeout = SSBI_TIMEOUT_US; u32 rd_status = 0; @@ -197,7 +197,7 @@ msm_ssbi_pa_transfer(struct msm_ssbi *ssbi, u32 cmd, u8 *data) } static int -msm_ssbi_pa_read_bytes(struct msm_ssbi *ssbi, u16 addr, u8 *buf, int len) +ssbi_pa_read_bytes(struct ssbi *ssbi, u16 addr, u8 *buf, int len) { u32 cmd; int ret = 0; @@ -205,7 +205,7 @@ msm_ssbi_pa_read_bytes(struct msm_ssbi *ssbi, u16 addr, u8 *buf, int len) cmd = SSBI_PA_CMD_RDWRN | (addr & SSBI_PA_CMD_ADDR_MASK) << 8; while (len) { - ret = msm_ssbi_pa_transfer(ssbi, cmd, buf); + ret = ssbi_pa_transfer(ssbi, cmd, buf); if (ret) goto err; buf++; @@ -217,14 +217,14 @@ err: } static int -msm_ssbi_pa_write_bytes(struct msm_ssbi *ssbi, u16 addr, u8 *buf, int len) +ssbi_pa_write_bytes(struct ssbi *ssbi, u16 addr, u8 *buf, int len) { u32 cmd; int ret = 0; while (len) { cmd = (addr & SSBI_PA_CMD_ADDR_MASK) << 8 | *buf; - ret = msm_ssbi_pa_transfer(ssbi, cmd, NULL); + ret = ssbi_pa_transfer(ssbi, cmd, NULL); if (ret) goto err; buf++; @@ -235,9 +235,9 @@ err: return ret; } -int msm_ssbi_read(struct device *dev, u16 addr, u8 *buf, int len) +int ssbi_read(struct device *dev, u16 addr, u8 *buf, int len) { - struct msm_ssbi *ssbi = to_msm_ssbi(dev); + struct ssbi *ssbi = to_ssbi(dev); unsigned long flags; int ret; @@ -247,11 +247,11 @@ int msm_ssbi_read(struct device *dev, u16 addr, u8 *buf, int len) return ret; } -EXPORT_SYMBOL_GPL(msm_ssbi_read); +EXPORT_SYMBOL_GPL(ssbi_read); -int msm_ssbi_write(struct device *dev, u16 addr, u8 *buf, int len) +int ssbi_write(struct device *dev, u16 addr, u8 *buf, int len) { - struct msm_ssbi *ssbi = to_msm_ssbi(dev); + struct ssbi *ssbi = to_ssbi(dev); unsigned long flags; int ret; @@ -261,17 +261,17 @@ int msm_ssbi_write(struct device *dev, u16 addr, u8 *buf, int len) return ret; } -EXPORT_SYMBOL_GPL(msm_ssbi_write); +EXPORT_SYMBOL_GPL(ssbi_write); -static int msm_ssbi_probe(struct platform_device *pdev) +static int ssbi_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct resource *mem_res; - struct msm_ssbi *ssbi; + struct ssbi *ssbi; int ret = 0; const char *type; - ssbi = kzalloc(sizeof(struct msm_ssbi), GFP_KERNEL); + ssbi = kzalloc(sizeof(struct ssbi), GFP_KERNEL); if (!ssbi) { pr_err("can not allocate ssbi_data\n"); return -ENOMEM; @@ -312,11 +312,11 @@ static int msm_ssbi_probe(struct platform_device *pdev) } if (ssbi->controller_type == MSM_SBI_CTRL_PMIC_ARBITER) { - ssbi->read = msm_ssbi_pa_read_bytes; - ssbi->write = msm_ssbi_pa_write_bytes; + ssbi->read = ssbi_pa_read_bytes; + ssbi->write = ssbi_pa_write_bytes; } else { - ssbi->read = msm_ssbi_read_bytes; - ssbi->write = msm_ssbi_write_bytes; + ssbi->read = ssbi_read_bytes; + ssbi->write = ssbi_write_bytes; } spin_lock_init(&ssbi->lock); @@ -336,9 +336,9 @@ err_get_mem_res: return ret; } -static int msm_ssbi_remove(struct platform_device *pdev) +static int ssbi_remove(struct platform_device *pdev) { - struct msm_ssbi *ssbi = platform_get_drvdata(pdev); + struct ssbi *ssbi = platform_get_drvdata(pdev); platform_set_drvdata(pdev, NULL); iounmap(ssbi->base); @@ -351,29 +351,29 @@ static struct of_device_id ssbi_match_table[] = { {} }; -static struct platform_driver msm_ssbi_driver = { - .probe = msm_ssbi_probe, - .remove = msm_ssbi_remove, +static struct platform_driver ssbi_driver = { + .probe = ssbi_probe, + .remove = ssbi_remove, .driver = { - .name = "msm_ssbi", + .name = "ssbi", .owner = THIS_MODULE, .of_match_table = ssbi_match_table, }, }; -static int __init msm_ssbi_init(void) +static int __init ssbi_init(void) { - return platform_driver_register(&msm_ssbi_driver); + return platform_driver_register(&ssbi_driver); } -module_init(msm_ssbi_init); +module_init(ssbi_init); -static void __exit msm_ssbi_exit(void) +static void __exit ssbi_exit(void) { - platform_driver_unregister(&msm_ssbi_driver); + platform_driver_unregister(&ssbi_driver); } -module_exit(msm_ssbi_exit) +module_exit(ssbi_exit) MODULE_LICENSE("GPL v2"); MODULE_VERSION("1.0"); -MODULE_ALIAS("platform:msm_ssbi"); +MODULE_ALIAS("platform:ssbi"); MODULE_AUTHOR("Dima Zavin "); diff --git a/include/linux/msm_ssbi.h b/include/linux/msm_ssbi.h deleted file mode 100644 index 0fe245bb2940..000000000000 --- a/include/linux/msm_ssbi.h +++ /dev/null @@ -1,38 +0,0 @@ -/* Copyright (C) 2010 Google, Inc. - * Copyright (c) 2011, Code Aurora Forum. All rights reserved. - * Author: Dima Zavin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * 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. - */ - -#ifndef _LINUX_MSM_SSBI_H -#define _LINUX_MSM_SSBI_H - -#include - -struct msm_ssbi_slave_info { - const char *name; - void *platform_data; -}; - -enum msm_ssbi_controller_type { - MSM_SBI_CTRL_SSBI = 0, - MSM_SBI_CTRL_SSBI2, - MSM_SBI_CTRL_PMIC_ARBITER, -}; - -struct msm_ssbi_platform_data { - struct msm_ssbi_slave_info slave; - enum msm_ssbi_controller_type controller_type; -}; - -int msm_ssbi_write(struct device *dev, u16 addr, u8 *buf, int len); -int msm_ssbi_read(struct device *dev, u16 addr, u8 *buf, int len); -#endif diff --git a/include/linux/ssbi.h b/include/linux/ssbi.h new file mode 100644 index 000000000000..44ef5da21470 --- /dev/null +++ b/include/linux/ssbi.h @@ -0,0 +1,38 @@ +/* Copyright (C) 2010 Google, Inc. + * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * Author: Dima Zavin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + */ + +#ifndef _LINUX_SSBI_H +#define _LINUX_SSBI_H + +#include + +struct ssbi_slave_info { + const char *name; + void *platform_data; +}; + +enum ssbi_controller_type { + MSM_SBI_CTRL_SSBI = 0, + MSM_SBI_CTRL_SSBI2, + MSM_SBI_CTRL_PMIC_ARBITER, +}; + +struct ssbi_platform_data { + struct ssbi_slave_info slave; + enum ssbi_controller_type controller_type; +}; + +int ssbi_write(struct device *dev, u16 addr, u8 *buf, int len); +int ssbi_read(struct device *dev, u16 addr, u8 *buf, int len); +#endif -- cgit v1.2.3 From f91a595d0b813c3a2f3b848c165e865ca4c5b8cc Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Sat, 16 Mar 2013 11:46:44 +0530 Subject: memory: emif: Handle devices which are not rated for >85C As per JESD209-2E specification for LPDDR2, http://www.jedec.org/standards-documents/results/jesd209-2E Table 73, LPDDR2 memories come in two flavors - Standard and Extended. The Standard types can operate from -25C to +85C However, beyond that and upto +105C can only be supported by Extended types. Unfortunately, it seems there is no info in MR0(device info) or MR[1,2](device feature) for run time detection of this capability as far as seen on the spec. Hence, we provide a custom_config flag to be populated by platforms which have these "extended" type memories. For the "Standard" memories, we need to consider MR4 notifications of temperature triggers >85C as equivalent to thermal shutdown events (equivalent to Spec specified thermal shutdown events for "extended" parts). Reported-by: Richard Woodruff Signed-off-by: Nishanth Menon Signed-off-by: Lokesh Vutla Acked-by: Santosh Shilimkar Signed-off-by: Greg Kroah-Hartman --- drivers/memory/emif.c | 27 +++++++++++++++++++++++++++ include/linux/platform_data/emif_plat.h | 1 + 2 files changed, 28 insertions(+) (limited to 'include') diff --git a/drivers/memory/emif.c b/drivers/memory/emif.c index 249222905c94..96add5b9ce5d 100644 --- a/drivers/memory/emif.c +++ b/drivers/memory/emif.c @@ -918,6 +918,7 @@ static irqreturn_t handle_temp_alert(void __iomem *base, struct emif_data *emif) { u32 old_temp_level; irqreturn_t ret = IRQ_HANDLED; + struct emif_custom_configs *custom_configs; spin_lock_irqsave(&emif_lock, irq_state); old_temp_level = emif->temperature_level; @@ -930,6 +931,29 @@ static irqreturn_t handle_temp_alert(void __iomem *base, struct emif_data *emif) goto out; } + custom_configs = emif->plat_data->custom_configs; + + /* + * IF we detect higher than "nominal rating" from DDR sensor + * on an unsupported DDR part, shutdown system + */ + if (custom_configs && !(custom_configs->mask & + EMIF_CUSTOM_CONFIG_EXTENDED_TEMP_PART)) { + if (emif->temperature_level >= SDRAM_TEMP_HIGH_DERATE_REFRESH) { + dev_err(emif->dev, + "%s:NOT Extended temperature capable memory." + "Converting MR4=0x%02x as shutdown event\n", + __func__, emif->temperature_level); + /* + * Temperature far too high - do kernel_power_off() + * from thread context + */ + emif->temperature_level = SDRAM_TEMP_VERY_HIGH_SHUTDOWN; + ret = IRQ_WAKE_THREAD; + goto out; + } + } + if (emif->temperature_level < old_temp_level || emif->temperature_level == SDRAM_TEMP_VERY_HIGH_SHUTDOWN) { /* @@ -1228,6 +1252,9 @@ static void __init_or_module of_get_custom_configs(struct device_node *np_emif, cust_cfgs->temp_alert_poll_interval_ms = *poll_intvl; } + if (of_find_property(np_emif, "extended-temp-part", &len)) + cust_cfgs->mask |= EMIF_CUSTOM_CONFIG_EXTENDED_TEMP_PART; + if (!is_custom_config_valid(cust_cfgs, emif->dev)) { devm_kfree(emif->dev, cust_cfgs); return; diff --git a/include/linux/platform_data/emif_plat.h b/include/linux/platform_data/emif_plat.h index 03378ca84061..5c19a2a647c4 100644 --- a/include/linux/platform_data/emif_plat.h +++ b/include/linux/platform_data/emif_plat.h @@ -40,6 +40,7 @@ /* Custom config requests */ #define EMIF_CUSTOM_CONFIG_LPMODE 0x00000001 #define EMIF_CUSTOM_CONFIG_TEMP_ALERT_POLL_INTERVAL 0x00000002 +#define EMIF_CUSTOM_CONFIG_EXTENDED_TEMP_PART 0x00000004 #ifndef __ASSEMBLY__ /** -- cgit v1.2.3 From f607e31ce3963327f749b56c65dfec2642aa623c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 22 Feb 2013 18:36:53 +0000 Subject: ASoC: arizona: Fix interaction between headphone outputs and identification Running HPDET while the headphone outputs are enabled can disrupt the operation of HPDET. In order to avoid this HPDET needs to disable the headphone outputs and ASoC needs to not enable them while HPDET is running. Do the ASoC side of this by storing the enable state in the core driver structure and only writing to the device if a flag indicating that the accessory detection side is in a state where it can have the headphone output stage enabled. Signed-off-by: Mark Brown --- include/linux/mfd/arizona/core.h | 3 +++ sound/soc/codecs/arizona.c | 33 +++++++++++++++++++++++++++++++++ sound/soc/codecs/arizona.h | 3 +++ sound/soc/codecs/wm5102.c | 8 ++++---- sound/soc/codecs/wm5110.c | 8 ++++---- 5 files changed, 47 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/include/linux/mfd/arizona/core.h b/include/linux/mfd/arizona/core.h index a710255528d7..cc281368dc55 100644 --- a/include/linux/mfd/arizona/core.h +++ b/include/linux/mfd/arizona/core.h @@ -100,6 +100,9 @@ struct arizona { struct regmap_irq_chip_data *aod_irq_chip; struct regmap_irq_chip_data *irq_chip; + bool hpdet_magic; + unsigned int hp_ena; + struct mutex clk_lock; int clk32k_ref; diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c index ac948a671ea6..e7d34711412c 100644 --- a/sound/soc/codecs/arizona.c +++ b/sound/soc/codecs/arizona.c @@ -364,6 +364,39 @@ int arizona_out_ev(struct snd_soc_dapm_widget *w, } EXPORT_SYMBOL_GPL(arizona_out_ev); +int arizona_hp_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct arizona_priv *priv = snd_soc_codec_get_drvdata(w->codec); + unsigned int mask = 1 << w->shift; + unsigned int val; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + val = mask; + break; + case SND_SOC_DAPM_PRE_PMD: + val = 0; + break; + default: + return -EINVAL; + } + + /* Store the desired state for the HP outputs */ + priv->arizona->hp_ena &= ~mask; + priv->arizona->hp_ena |= val; + + /* Force off if HPDET magic is active */ + if (priv->arizona->hpdet_magic) + val = 0; + + snd_soc_update_bits(w->codec, ARIZONA_OUTPUT_ENABLES_1, mask, val); + + return arizona_out_ev(w, kcontrol, event); +} +EXPORT_SYMBOL_GPL(arizona_hp_ev); + static unsigned int arizona_sysclk_48k_rates[] = { 6144000, 12288000, diff --git a/sound/soc/codecs/arizona.h b/sound/soc/codecs/arizona.h index 116372c91f5d..13dd2916b721 100644 --- a/sound/soc/codecs/arizona.h +++ b/sound/soc/codecs/arizona.h @@ -184,6 +184,9 @@ extern int arizona_in_ev(struct snd_soc_dapm_widget *w, extern int arizona_out_ev(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event); +extern int arizona_hp_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event); extern int arizona_set_sysclk(struct snd_soc_codec *codec, int clk_id, int source, unsigned int freq, int dir); diff --git a/sound/soc/codecs/wm5102.c b/sound/soc/codecs/wm5102.c index b82bbf584146..2657aad3f8b1 100644 --- a/sound/soc/codecs/wm5102.c +++ b/sound/soc/codecs/wm5102.c @@ -1131,11 +1131,11 @@ ARIZONA_DSP_WIDGETS(DSP1, "DSP1"), SND_SOC_DAPM_VALUE_MUX("AEC Loopback", ARIZONA_DAC_AEC_CONTROL_1, ARIZONA_AEC_LOOPBACK_ENA, 0, &wm5102_aec_loopback_mux), -SND_SOC_DAPM_PGA_E("OUT1L", ARIZONA_OUTPUT_ENABLES_1, - ARIZONA_OUT1L_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, +SND_SOC_DAPM_PGA_E("OUT1L", SND_SOC_NOPM, + ARIZONA_OUT1L_ENA_SHIFT, 0, NULL, 0, arizona_hp_ev, SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), -SND_SOC_DAPM_PGA_E("OUT1R", ARIZONA_OUTPUT_ENABLES_1, - ARIZONA_OUT1R_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, +SND_SOC_DAPM_PGA_E("OUT1R", SND_SOC_NOPM, + ARIZONA_OUT1R_ENA_SHIFT, 0, NULL, 0, arizona_hp_ev, SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), SND_SOC_DAPM_PGA_E("OUT2L", ARIZONA_OUTPUT_ENABLES_1, ARIZONA_OUT2L_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, diff --git a/sound/soc/codecs/wm5110.c b/sound/soc/codecs/wm5110.c index cdeb301da1f6..7841b42a819c 100644 --- a/sound/soc/codecs/wm5110.c +++ b/sound/soc/codecs/wm5110.c @@ -551,11 +551,11 @@ SND_SOC_DAPM_AIF_IN("AIF3RX1", NULL, 0, SND_SOC_DAPM_AIF_IN("AIF3RX2", NULL, 0, ARIZONA_AIF3_RX_ENABLES, ARIZONA_AIF3RX2_ENA_SHIFT, 0), -SND_SOC_DAPM_PGA_E("OUT1L", ARIZONA_OUTPUT_ENABLES_1, - ARIZONA_OUT1L_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, +SND_SOC_DAPM_PGA_E("OUT1L", SND_SOC_NOPM, + ARIZONA_OUT1L_ENA_SHIFT, 0, NULL, 0, arizona_hp_ev, SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), -SND_SOC_DAPM_PGA_E("OUT1R", ARIZONA_OUTPUT_ENABLES_1, - ARIZONA_OUT1R_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, +SND_SOC_DAPM_PGA_E("OUT1R", SND_SOC_NOPM, + ARIZONA_OUT1R_ENA_SHIFT, 0, NULL, 0, arizona_hp_ev, SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), SND_SOC_DAPM_PGA_E("OUT2L", ARIZONA_OUTPUT_ENABLES_1, ARIZONA_OUT2L_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, -- cgit v1.2.3 From e5354107e14755991da82e0d2a4791db92908d9d Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Wed, 27 Mar 2013 17:29:53 +0200 Subject: mei: bus: Initial MEI Client bus type implementation mei client bus will present some of the mei clients as devices for other standard subsystems Implement the probe, remove, match, device addtion routines, along with the sysfs and uevent ones. mei_cl_device_id is also added to mod_devicetable.h A mei-cleint-bus.txt document describing the rationale and the API usage is also added while ABI/testing/sysfs-bus-mei describeis the modalias ABI. Signed-off-by: Samuel Ortiz Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-mei | 7 + Documentation/misc-devices/mei/mei-client-bus.txt | 135 +++++++++++++++++ drivers/misc/mei/Makefile | 1 + drivers/misc/mei/bus.c | 172 ++++++++++++++++++++++ drivers/misc/mei/mei_dev.h | 26 ++++ include/linux/mei_cl_bus.h | 20 +++ include/linux/mod_devicetable.h | 9 ++ scripts/mod/devicetable-offsets.c | 3 + scripts/mod/file2alias.c | 12 ++ 9 files changed, 385 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-mei create mode 100644 Documentation/misc-devices/mei/mei-client-bus.txt create mode 100644 drivers/misc/mei/bus.c create mode 100644 include/linux/mei_cl_bus.h (limited to 'include') diff --git a/Documentation/ABI/testing/sysfs-bus-mei b/Documentation/ABI/testing/sysfs-bus-mei new file mode 100644 index 000000000000..2066f0bbd453 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-mei @@ -0,0 +1,7 @@ +What: /sys/bus/mei/devices/.../modalias +Date: March 2013 +KernelVersion: 3.10 +Contact: Samuel Ortiz + linux-mei@linux.intel.com +Description: Stores the same MODALIAS value emitted by uevent + Format: mei: diff --git a/Documentation/misc-devices/mei/mei-client-bus.txt b/Documentation/misc-devices/mei/mei-client-bus.txt new file mode 100644 index 000000000000..9dc5ebf94eb1 --- /dev/null +++ b/Documentation/misc-devices/mei/mei-client-bus.txt @@ -0,0 +1,135 @@ +Intel(R) Management Engine (ME) Client bus API +=============================================== + + +Rationale +========= +MEI misc character device is useful for dedicated applications to send and receive +data to the many FW appliance found in Intel's ME from the user space. +However for some of the ME functionalities it make sense to leverage existing software +stack and expose them through existing kernel subsystems. + +In order to plug seamlessly into the kernel device driver model we add kernel virtual +bus abstraction on top of the MEI driver. This allows implementing linux kernel drivers +for the various MEI features as a stand alone entities found in their respective subsystem. +Existing device drivers can even potentially be re-used by adding an MEI CL bus layer to +the existing code. + + +MEI CL bus API +=========== +A driver implementation for an MEI Client is very similar to existing bus +based device drivers. The driver registers itself as an MEI CL bus driver through +the mei_cl_driver structure: + +struct mei_cl_driver { + struct device_driver driver; + const char *name; + + const struct mei_cl_device_id *id_table; + + int (*probe)(struct mei_cl_device *dev, const struct mei_cl_id *id); + int (*remove)(struct mei_cl_device *dev); +}; + +struct mei_cl_id { + char name[MEI_NAME_SIZE]; + kernel_ulong_t driver_info; +}; + +The mei_cl_id structure allows the driver to bind itself against a device name. + +To actually register a driver on the ME Client bus one must call the mei_cl_add_driver() +API. This is typically called at module init time. + +Once registered on the ME Client bus, a driver will typically try to do some I/O on +this bus and this should be done through the mei_cl_send() and mei_cl_recv() +routines. The latter is synchronous (blocks and sleeps until data shows up). +In order for drivers to be notified of pending events waiting for them (e.g. +an Rx event) they can register an event handler through the +mei_cl_register_event_cb() routine. Currently only the MEI_EVENT_RX event +will trigger an event handler call and the driver implementation is supposed +to call mei_recv() from the event handler in order to fetch the pending +received buffers. + + +Example +======= +As a theoretical example let's pretend the ME comes with a "contact" NFC IP. +The driver init and exit routines for this device would look like: + +#define CONTACT_DRIVER_NAME "contact" + +static struct mei_cl_device_id contact_mei_cl_tbl[] = { + { CONTACT_DRIVER_NAME, }, + + /* required last entry */ + { } +}; +MODULE_DEVICE_TABLE(mei_cl, contact_mei_cl_tbl); + +static struct mei_cl_driver contact_driver = { + .id_table = contact_mei_tbl, + .name = CONTACT_DRIVER_NAME, + + .probe = contact_probe, + .remove = contact_remove, +}; + +static int contact_init(void) +{ + int r; + + r = mei_cl_driver_register(&contact_driver); + if (r) { + pr_err(CONTACT_DRIVER_NAME ": driver registration failed\n"); + return r; + } + + return 0; +} + +static void __exit contact_exit(void) +{ + mei_cl_driver_unregister(&contact_driver); +} + +module_init(contact_init); +module_exit(contact_exit); + +And the driver's simplified probe routine would look like that: + +int contact_probe(struct mei_cl_device *dev, struct mei_cl_device_id *id) +{ + struct contact_driver *contact; + + [...] + mei_cl_register_event_cb(dev, contact_event_cb, contact); + + return 0; + } + +In the probe routine the driver basically registers an ME bus event handler +which is as close as it can get to registering a threaded IRQ handler. +The handler implementation will typically call some I/O routine depending on +the pending events: + +#define MAX_NFC_PAYLOAD 128 + +static void contact_event_cb(struct mei_cl_device *dev, u32 events, + void *context) +{ + struct contact_driver *contact = context; + + if (events & BIT(MEI_EVENT_RX)) { + u8 payload[MAX_NFC_PAYLOAD]; + int payload_size; + + payload_size = mei_recv(dev, payload, MAX_NFC_PAYLOAD); + if (payload_size <= 0) + return; + + /* Hook to the NFC subsystem */ + nfc_hci_recv_frame(contact->hdev, payload, payload_size); + } +} diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile index 2c336d087749..1b29f7ccac49 100644 --- a/drivers/misc/mei/Makefile +++ b/drivers/misc/mei/Makefile @@ -10,6 +10,7 @@ mei-objs += client.o mei-objs += main.o mei-objs += amthif.o mei-objs += wd.o +mei-objs += bus.o obj-$(CONFIG_INTEL_MEI_ME) += mei-me.o mei-me-objs := pci-me.o diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c new file mode 100644 index 000000000000..78c876af2676 --- /dev/null +++ b/drivers/misc/mei/bus.c @@ -0,0 +1,172 @@ +/* + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2012-2013, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mei_dev.h" + +#define to_mei_cl_driver(d) container_of(d, struct mei_cl_driver, driver) +#define to_mei_cl_device(d) container_of(d, struct mei_cl_device, dev) + +static int mei_cl_device_match(struct device *dev, struct device_driver *drv) +{ + struct mei_cl_device *device = to_mei_cl_device(dev); + struct mei_cl_driver *driver = to_mei_cl_driver(drv); + const struct mei_cl_device_id *id; + + if (!device) + return 0; + + if (!driver || !driver->id_table) + return 0; + + id = driver->id_table; + + while (id->name[0]) { + if (!strcmp(dev_name(dev), id->name)) + return 1; + + id++; + } + + return 0; +} + +static int mei_cl_device_probe(struct device *dev) +{ + struct mei_cl_device *device = to_mei_cl_device(dev); + struct mei_cl_driver *driver; + struct mei_cl_device_id id; + + if (!device) + return 0; + + driver = to_mei_cl_driver(dev->driver); + if (!driver || !driver->probe) + return -ENODEV; + + dev_dbg(dev, "Device probe\n"); + + strncpy(id.name, dev_name(dev), MEI_CL_NAME_SIZE); + + return driver->probe(device, &id); +} + +static int mei_cl_device_remove(struct device *dev) +{ + struct mei_cl_device *device = to_mei_cl_device(dev); + struct mei_cl_driver *driver; + + if (!device || !dev->driver) + return 0; + + driver = to_mei_cl_driver(dev->driver); + if (!driver->remove) { + dev->driver = NULL; + + return 0; + } + + return driver->remove(device); +} + +static ssize_t modalias_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + int len; + + len = snprintf(buf, PAGE_SIZE, "mei:%s\n", dev_name(dev)); + + return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len; +} + +static struct device_attribute mei_cl_dev_attrs[] = { + __ATTR_RO(modalias), + __ATTR_NULL, +}; + +static int mei_cl_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + if (add_uevent_var(env, "MODALIAS=mei:%s", dev_name(dev))) + return -ENOMEM; + + return 0; +} + +static struct bus_type mei_cl_bus_type = { + .name = "mei", + .dev_attrs = mei_cl_dev_attrs, + .match = mei_cl_device_match, + .probe = mei_cl_device_probe, + .remove = mei_cl_device_remove, + .uevent = mei_cl_uevent, +}; + +static void mei_cl_dev_release(struct device *dev) +{ + kfree(to_mei_cl_device(dev)); +} + +static struct device_type mei_cl_device_type = { + .release = mei_cl_dev_release, +}; + +struct mei_cl_device *mei_cl_add_device(struct mei_device *mei_device, + uuid_le uuid, char *name) +{ + struct mei_cl_device *device; + int status; + + device = kzalloc(sizeof(struct mei_cl_device), GFP_KERNEL); + if (!device) + return NULL; + + device->dev.parent = &mei_device->pdev->dev; + device->dev.bus = &mei_cl_bus_type; + device->dev.type = &mei_cl_device_type; + + dev_set_name(&device->dev, "%s", name); + + status = device_register(&device->dev); + if (status) + goto out_err; + + dev_dbg(&device->dev, "client %s registered\n", name); + + return device; + +out_err: + dev_err(device->dev.parent, "Failed to register MEI client\n"); + + kfree(device); + + return NULL; +} +EXPORT_SYMBOL_GPL(mei_cl_add_device); + +void mei_cl_remove_device(struct mei_cl_device *device) +{ + device_unregister(&device->dev); +} +EXPORT_SYMBOL_GPL(mei_cl_remove_device); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index b5d66076de3d..7abb705ddf3f 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -21,6 +21,7 @@ #include #include #include +#include #include "hw.h" #include "hw-me-regs.h" @@ -262,6 +263,31 @@ struct mei_hw_ops { unsigned char *buf, unsigned long len); }; +/* MEI bus API*/ +struct mei_cl_device *mei_cl_add_device(struct mei_device *dev, + uuid_le uuid, char *name); +void mei_cl_remove_device(struct mei_cl_device *device); + +/** + * struct mei_cl_device - MEI device handle + * An mei_cl_device pointer is returned from mei_add_device() + * and links MEI bus clients to their actual ME host client pointer. + * Drivers for MEI devices will get an mei_cl_device pointer + * when being probed and shall use it for doing ME bus I/O. + * + * @dev: linux driver model device pointer + * @uuid: me client uuid + * @cl: mei client + * @priv_data: client private data + */ +struct mei_cl_device { + struct device dev; + + struct mei_cl *cl; + + void *priv_data; +}; + /** * struct mei_device - MEI private device struct diff --git a/include/linux/mei_cl_bus.h b/include/linux/mei_cl_bus.h new file mode 100644 index 000000000000..4e7351de7eca --- /dev/null +++ b/include/linux/mei_cl_bus.h @@ -0,0 +1,20 @@ +#ifndef _LINUX_MEI_CL_BUS_H +#define _LINUX_MEI_CL_BUS_H + +#include +#include + +struct mei_cl_device; + +struct mei_cl_driver { + struct device_driver driver; + const char *name; + + const struct mei_cl_device_id *id_table; + + int (*probe)(struct mei_cl_device *dev, + const struct mei_cl_device_id *id); + int (*remove)(struct mei_cl_device *dev); +}; + +#endif /* _LINUX_MEI_CL_BUS_H */ diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index 779cf7c4a3d1..b508016fb76d 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -9,6 +9,7 @@ #ifdef __KERNEL__ #include +#include typedef unsigned long kernel_ulong_t; #endif @@ -568,4 +569,12 @@ struct ipack_device_id { __u32 device; /* Device ID or IPACK_ANY_ID */ }; +#define MEI_CL_MODULE_PREFIX "mei:" +#define MEI_CL_NAME_SIZE 32 + +struct mei_cl_device_id { + char name[MEI_CL_NAME_SIZE]; + kernel_ulong_t driver_info; +}; + #endif /* LINUX_MOD_DEVICETABLE_H */ diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c index b45260bfeaa0..e66d4d258e1a 100644 --- a/scripts/mod/devicetable-offsets.c +++ b/scripts/mod/devicetable-offsets.c @@ -174,5 +174,8 @@ int main(void) DEVID_FIELD(x86_cpu_id, model); DEVID_FIELD(x86_cpu_id, vendor); + DEVID(mei_cl_device_id); + DEVID_FIELD(mei_cl_device_id, name); + return 0; } diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c index 771ac17f635d..45f9a3377dcd 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c @@ -1133,6 +1133,18 @@ static int do_x86cpu_entry(const char *filename, void *symval, } ADD_TO_DEVTABLE("x86cpu", x86_cpu_id, do_x86cpu_entry); +/* Looks like: mei:S */ +static int do_mei_entry(const char *filename, void *symval, + char *alias) +{ + DEF_FIELD_ADDR(symval, mei_cl_device_id, name); + + sprintf(alias, MEI_CL_MODULE_PREFIX "%s", *name); + + return 1; +} +ADD_TO_DEVTABLE("mei", mei_cl_device_id, do_mei_entry); + /* Does namelen bytes of name exactly match the symbol? */ static bool sym_is(const char *name, unsigned namelen, const char *symbol) { -- cgit v1.2.3 From 333e4ee0781bd0b5938da263c4bb7ab66a0d1b57 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Wed, 27 Mar 2013 17:29:54 +0200 Subject: mei: bus: Implement driver registration Signed-off-by: Samuel Ortiz Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/bus.c | 26 ++++++++++++++++++++++++++ include/linux/mei_cl_bus.h | 7 +++++++ 2 files changed, 33 insertions(+) (limited to 'include') diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index 78c876af2676..d16b3c3e1b38 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -170,3 +170,29 @@ void mei_cl_remove_device(struct mei_cl_device *device) device_unregister(&device->dev); } EXPORT_SYMBOL_GPL(mei_cl_remove_device); + +int __mei_cl_driver_register(struct mei_cl_driver *driver, struct module *owner) +{ + int err; + + driver->driver.name = driver->name; + driver->driver.owner = owner; + driver->driver.bus = &mei_cl_bus_type; + + err = driver_register(&driver->driver); + if (err) + return err; + + pr_debug("mei: driver [%s] registered\n", driver->driver.name); + + return 0; +} +EXPORT_SYMBOL_GPL(__mei_cl_driver_register); + +void mei_cl_driver_unregister(struct mei_cl_driver *driver) +{ + driver_unregister(&driver->driver); + + pr_debug("mei: driver [%s] unregistered\n", driver->driver.name); +} +EXPORT_SYMBOL_GPL(mei_cl_driver_unregister); diff --git a/include/linux/mei_cl_bus.h b/include/linux/mei_cl_bus.h index 4e7351de7eca..ba2aa3b66f30 100644 --- a/include/linux/mei_cl_bus.h +++ b/include/linux/mei_cl_bus.h @@ -17,4 +17,11 @@ struct mei_cl_driver { int (*remove)(struct mei_cl_device *dev); }; +int __mei_cl_driver_register(struct mei_cl_driver *driver, + struct module *owner); +#define mei_cl_driver_register(driver) \ + __mei_cl_driver_register(driver, THIS_MODULE) + +void mei_cl_driver_unregister(struct mei_cl_driver *driver); + #endif /* _LINUX_MEI_CL_BUS_H */ -- cgit v1.2.3 From 3e8332952dedd2c17bb497e3909e3b6fbac10ce7 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Wed, 27 Mar 2013 17:29:55 +0200 Subject: mei: bus: Initial implementation for I/O routines Signed-off-by: Samuel Ortiz Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/bus.c | 226 +++++++++++++++++++++++++++++++++++++++++++++ drivers/misc/mei/mei_dev.h | 30 ++++++ include/linux/mei_cl_bus.h | 11 +++ 3 files changed, 267 insertions(+) (limited to 'include') diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index d16b3c3e1b38..16c7fff50549 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,8 @@ #include #include "mei_dev.h" +#include "hw-me.h" +#include "client.h" #define to_mei_cl_driver(d) container_of(d, struct mei_cl_driver, driver) #define to_mei_cl_device(d) container_of(d, struct mei_cl_device, dev) @@ -81,6 +84,11 @@ static int mei_cl_device_remove(struct device *dev) if (!device || !dev->driver) return 0; + if (device->event_cb) { + device->event_cb = NULL; + cancel_work_sync(&device->event_work); + } + driver = to_mei_cl_driver(dev->driver); if (!driver->remove) { dev->driver = NULL; @@ -196,3 +204,221 @@ void mei_cl_driver_unregister(struct mei_cl_driver *driver) pr_debug("mei: driver [%s] unregistered\n", driver->driver.name); } EXPORT_SYMBOL_GPL(mei_cl_driver_unregister); + +int __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length) +{ + struct mei_device *dev; + struct mei_msg_hdr mei_hdr; + struct mei_cl_cb *cb; + int me_cl_id, err; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + if (cl->state != MEI_FILE_CONNECTED) + return -ENODEV; + + cb = mei_io_cb_init(cl, NULL); + if (!cb) + return -ENOMEM; + + err = mei_io_cb_alloc_req_buf(cb, length); + if (err < 0) { + mei_io_cb_free(cb); + return err; + } + + memcpy(cb->request_buffer.data, buf, length); + cb->fop_type = MEI_FOP_WRITE; + + dev = cl->dev; + + mutex_lock(&dev->device_lock); + + /* Check if we have an ME client device */ + me_cl_id = mei_me_cl_by_id(dev, cl->me_client_id); + if (me_cl_id == dev->me_clients_num) { + err = -ENODEV; + goto out_err; + } + + if (length > dev->me_clients[me_cl_id].props.max_msg_length) { + err = -EINVAL; + goto out_err; + } + + err = mei_cl_flow_ctrl_creds(cl); + if (err < 0) + goto out_err; + + /* Host buffer is not ready, we queue the request */ + if (err == 0 || !dev->hbuf_is_ready) { + cb->buf_idx = 0; + mei_hdr.msg_complete = 0; + cl->writing_state = MEI_WRITING; + list_add_tail(&cb->list, &dev->write_list.list); + + mutex_unlock(&dev->device_lock); + + return length; + } + + dev->hbuf_is_ready = false; + + /* Check for a maximum length */ + if (length > mei_hbuf_max_len(dev)) { + mei_hdr.length = mei_hbuf_max_len(dev); + mei_hdr.msg_complete = 0; + } else { + mei_hdr.length = length; + mei_hdr.msg_complete = 1; + } + + mei_hdr.host_addr = cl->host_client_id; + mei_hdr.me_addr = cl->me_client_id; + mei_hdr.reserved = 0; + + if (mei_write_message(dev, &mei_hdr, buf)) { + err = -EIO; + goto out_err; + } + + cl->writing_state = MEI_WRITING; + cb->buf_idx = mei_hdr.length; + + if (!mei_hdr.msg_complete) { + list_add_tail(&cb->list, &dev->write_list.list); + } else { + if (mei_cl_flow_ctrl_reduce(cl)) { + err = -EIO; + goto out_err; + } + + list_add_tail(&cb->list, &dev->write_waiting_list.list); + } + + mutex_unlock(&dev->device_lock); + + return mei_hdr.length; + +out_err: + mutex_unlock(&dev->device_lock); + mei_io_cb_free(cb); + + return err; +} + +int __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length) +{ + struct mei_device *dev; + struct mei_cl_cb *cb; + size_t r_length; + int err; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + mutex_lock(&dev->device_lock); + + if (!cl->read_cb) { + err = mei_cl_read_start(cl); + if (err < 0) { + mutex_unlock(&dev->device_lock); + return err; + } + } + + if (cl->reading_state != MEI_READ_COMPLETE && + !waitqueue_active(&cl->rx_wait)) { + mutex_unlock(&dev->device_lock); + + if (wait_event_interruptible(cl->rx_wait, + (MEI_READ_COMPLETE == cl->reading_state))) { + if (signal_pending(current)) + return -EINTR; + return -ERESTARTSYS; + } + + mutex_lock(&dev->device_lock); + } + + cb = cl->read_cb; + + if (cl->reading_state != MEI_READ_COMPLETE) { + r_length = 0; + goto out; + } + + r_length = min_t(size_t, length, cb->buf_idx); + + memcpy(buf, cb->response_buffer.data, r_length); + + mei_io_cb_free(cb); + cl->reading_state = MEI_IDLE; + cl->read_cb = NULL; + +out: + mutex_unlock(&dev->device_lock); + + return r_length; +} + +int mei_cl_send(struct mei_cl_device *device, u8 *buf, size_t length) +{ + struct mei_cl *cl = NULL; + + /* TODO: hook between mei_bus_client and mei_cl */ + + if (device->ops && device->ops->send) + return device->ops->send(device, buf, length); + + return __mei_cl_send(cl, buf, length); +} +EXPORT_SYMBOL_GPL(mei_cl_send); + +int mei_cl_recv(struct mei_cl_device *device, u8 *buf, size_t length) +{ + struct mei_cl *cl = NULL; + + /* TODO: hook between mei_bus_client and mei_cl */ + + if (device->ops && device->ops->recv) + return device->ops->recv(device, buf, length); + + return __mei_cl_recv(cl, buf, length); +} +EXPORT_SYMBOL_GPL(mei_cl_recv); + +static void mei_bus_event_work(struct work_struct *work) +{ + struct mei_cl_device *device; + + device = container_of(work, struct mei_cl_device, event_work); + + if (device->event_cb) + device->event_cb(device, device->events, device->event_context); + + device->events = 0; + + /* Prepare for the next read */ + mei_cl_read_start(device->cl); +} + +int mei_cl_register_event_cb(struct mei_cl_device *device, + mei_cl_event_cb_t event_cb, void *context) +{ + if (device->event_cb) + return -EALREADY; + + device->events = 0; + device->event_cb = event_cb; + device->event_context = context; + INIT_WORK(&device->event_work, mei_bus_event_work); + + mei_cl_read_start(device->cl); + + return 0; +} +EXPORT_SYMBOL_GPL(mei_cl_register_event_cb); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 7abb705ddf3f..cde5687039f3 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -268,6 +268,25 @@ struct mei_cl_device *mei_cl_add_device(struct mei_device *dev, uuid_le uuid, char *name); void mei_cl_remove_device(struct mei_cl_device *device); +int __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length); +int __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length); + +/** + * struct mei_cl_transport_ops - MEI CL device transport ops + * This structure allows ME host clients to implement technology + * specific transport layers. + * + * @send: Tx hook for the device. This allows ME host clients to trap + * the device driver buffers before actually physically + * pushing it to the ME. + * @recv: Rx hook for the device. This allows ME host clients to trap the + * ME buffers before forwarding them to the device driver. + */ +struct mei_cl_transport_ops { + int (*send)(struct mei_cl_device *device, u8 *buf, size_t length); + int (*recv)(struct mei_cl_device *device, u8 *buf, size_t length); +}; + /** * struct mei_cl_device - MEI device handle * An mei_cl_device pointer is returned from mei_add_device() @@ -278,6 +297,10 @@ void mei_cl_remove_device(struct mei_cl_device *device); * @dev: linux driver model device pointer * @uuid: me client uuid * @cl: mei client + * @ops: ME transport ops + * @event_cb: Drivers register this callback to get asynchronous ME + * events (e.g. Rx buffer pending) notifications. + * @events: Events bitmask sent to the driver. * @priv_data: client private data */ struct mei_cl_device { @@ -285,6 +308,13 @@ struct mei_cl_device { struct mei_cl *cl; + const struct mei_cl_transport_ops *ops; + + struct work_struct event_work; + mei_cl_event_cb_t event_cb; + void *event_context; + unsigned long events; + void *priv_data; }; diff --git a/include/linux/mei_cl_bus.h b/include/linux/mei_cl_bus.h index ba2aa3b66f30..d9958c3960a2 100644 --- a/include/linux/mei_cl_bus.h +++ b/include/linux/mei_cl_bus.h @@ -24,4 +24,15 @@ int __mei_cl_driver_register(struct mei_cl_driver *driver, void mei_cl_driver_unregister(struct mei_cl_driver *driver); +int mei_cl_send(struct mei_cl_device *device, u8 *buf, size_t length); +int mei_cl_recv(struct mei_cl_device *device, u8 *buf, size_t length); + +typedef void (*mei_cl_event_cb_t)(struct mei_cl_device *device, + u32 events, void *context); +int mei_cl_register_event_cb(struct mei_cl_device *device, + mei_cl_event_cb_t read_cb, void *context); + +#define MEI_CL_EVENT_RX 0 +#define MEI_CL_EVENT_TX 1 + #endif /* _LINUX_MEI_CL_BUS_H */ -- cgit v1.2.3 From aa6aef216f8aea1a00b56aafc29b8745237a9b62 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Wed, 27 Mar 2013 17:29:59 +0200 Subject: mei: bus: Implement bus driver data setter/getter MEI drivers should be able to carry their private data around. Signed-off-by: Samuel Ortiz Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/bus.c | 12 ++++++++++++ include/linux/mei_cl_bus.h | 3 +++ 2 files changed, 15 insertions(+) (limited to 'include') diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index 2b4b5b3f639f..8dbcb1516dc6 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -461,6 +461,18 @@ int mei_cl_register_event_cb(struct mei_cl_device *device, } EXPORT_SYMBOL_GPL(mei_cl_register_event_cb); +void *mei_cl_get_drvdata(const struct mei_cl_device *device) +{ + return dev_get_drvdata(&device->dev); +} +EXPORT_SYMBOL_GPL(mei_cl_get_drvdata); + +void mei_cl_set_drvdata(struct mei_cl_device *device, void *data) +{ + dev_set_drvdata(&device->dev, data); +} +EXPORT_SYMBOL_GPL(mei_cl_set_drvdata); + void mei_cl_bus_rx_event(struct mei_cl *cl) { struct mei_cl_device *device = cl->device; diff --git a/include/linux/mei_cl_bus.h b/include/linux/mei_cl_bus.h index d9958c3960a2..1bece18825ba 100644 --- a/include/linux/mei_cl_bus.h +++ b/include/linux/mei_cl_bus.h @@ -35,4 +35,7 @@ int mei_cl_register_event_cb(struct mei_cl_device *device, #define MEI_CL_EVENT_RX 0 #define MEI_CL_EVENT_TX 1 +void *mei_cl_get_drvdata(const struct mei_cl_device *device); +void mei_cl_set_drvdata(struct mei_cl_device *device, void *data); + #endif /* _LINUX_MEI_CL_BUS_H */ -- cgit v1.2.3 From 253b5374f08f3908cc380c5665470a5b7609be1c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 11 Dec 2012 13:14:09 +0900 Subject: mfd: wm5102: Add registers for microphone detection level configuration Signed-off-by: Mark Brown --- drivers/mfd/wm5102-tables.c | 8 ++++++++ include/linux/mfd/arizona/registers.h | 4 ++++ 2 files changed, 12 insertions(+) (limited to 'include') diff --git a/drivers/mfd/wm5102-tables.c b/drivers/mfd/wm5102-tables.c index a433f580aa4c..ca2aed6bc830 100644 --- a/drivers/mfd/wm5102-tables.c +++ b/drivers/mfd/wm5102-tables.c @@ -331,6 +331,10 @@ static const struct reg_default wm5102_reg_default[] = { { 0x000002A3, 0x1102 }, /* R675 - Mic Detect 1 */ { 0x000002A4, 0x009F }, /* R676 - Mic Detect 2 */ { 0x000002A5, 0x0000 }, /* R677 - Mic Detect 3 */ + { 0x000002A6, 0x3737 }, /* R678 - Mic Detect Level 1 */ + { 0x000002A7, 0x372C }, /* R679 - Mic Detect Level 2 */ + { 0x000002A8, 0x1422 }, /* R680 - Mic Detect Level 3 */ + { 0x000002A9, 0x030A }, /* R681 - Mic Detect Level 4 */ { 0x000002C3, 0x0000 }, /* R707 - Mic noise mix control 1 */ { 0x000002CB, 0x0000 }, /* R715 - Isolation control */ { 0x000002D3, 0x0000 }, /* R723 - Jack detect analogue */ @@ -1090,6 +1094,10 @@ static bool wm5102_readable_register(struct device *dev, unsigned int reg) case ARIZONA_MIC_DETECT_1: case ARIZONA_MIC_DETECT_2: case ARIZONA_MIC_DETECT_3: + case ARIZONA_MIC_DETECT_LEVEL_1: + case ARIZONA_MIC_DETECT_LEVEL_2: + case ARIZONA_MIC_DETECT_LEVEL_3: + case ARIZONA_MIC_DETECT_LEVEL_4: case ARIZONA_MIC_NOISE_MIX_CONTROL_1: case ARIZONA_ISOLATION_CONTROL: case ARIZONA_JACK_DETECT_ANALOGUE: diff --git a/include/linux/mfd/arizona/registers.h b/include/linux/mfd/arizona/registers.h index 340355136069..f43aa7c8d040 100644 --- a/include/linux/mfd/arizona/registers.h +++ b/include/linux/mfd/arizona/registers.h @@ -124,6 +124,10 @@ #define ARIZONA_MIC_DETECT_1 0x2A3 #define ARIZONA_MIC_DETECT_2 0x2A4 #define ARIZONA_MIC_DETECT_3 0x2A5 +#define ARIZONA_MIC_DETECT_LEVEL_1 0x2A6 +#define ARIZONA_MIC_DETECT_LEVEL_2 0x2A7 +#define ARIZONA_MIC_DETECT_LEVEL_3 0x2A8 +#define ARIZONA_MIC_DETECT_LEVEL_4 0x2A9 #define ARIZONA_MIC_NOISE_MIX_CONTROL_1 0x2C3 #define ARIZONA_ISOLATION_CONTROL 0x2CB #define ARIZONA_JACK_DETECT_ANALOGUE 0x2D3 -- cgit v1.2.3 From 6fed4d869a11fdbb4c6a5e444dfb2c22f92c3e46 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 1 Apr 2013 22:03:06 +0100 Subject: extcon: arizona: Allow configuration of button detection The Arizona button detection circuit is configurable, allowing the system integrator to program a range of thresholds for the buttons supported on the accessory but currently the driver uses the default button ranges and does not provide any flexibility in how this is exposed to the application layer. Provide platform data allowing the user to control this and to map the buttons to keys in the input subsystem. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 164 +++++++++++++++++++++++++++++--------- include/linux/mfd/arizona/pdata.h | 9 +++ 2 files changed, 134 insertions(+), 39 deletions(-) (limited to 'include') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 4bb0e9ae405d..e2339629126a 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -33,7 +33,7 @@ #include #include -#define ARIZONA_NUM_BUTTONS 6 +#define ARIZONA_MAX_MICD_RANGE 8 #define ARIZONA_ACCDET_MODE_MIC 0 #define ARIZONA_ACCDET_MODE_HPL 1 @@ -50,6 +50,9 @@ struct arizona_extcon_info { const struct arizona_micd_config *micd_modes; int micd_num_modes; + const struct arizona_micd_range *micd_ranges; + int num_micd_ranges; + bool micd_reva; bool micd_clamp; @@ -71,20 +74,25 @@ struct arizona_extcon_info { }; static const struct arizona_micd_config micd_default_modes[] = { - { 0, 2 << ARIZONA_MICD_BIAS_SRC_SHIFT, 1 }, { ARIZONA_ACCDET_SRC, 1 << ARIZONA_MICD_BIAS_SRC_SHIFT, 0 }, + { 0, 2 << ARIZONA_MICD_BIAS_SRC_SHIFT, 1 }, }; -static struct { - u16 status; - int report; -} arizona_lvl_to_key[ARIZONA_NUM_BUTTONS] = { - { 0x1, BTN_0 }, - { 0x2, BTN_1 }, - { 0x4, BTN_2 }, - { 0x8, BTN_3 }, - { 0x10, BTN_4 }, - { 0x20, BTN_5 }, +static const struct arizona_micd_range micd_default_ranges[] = { + { .max = 11, .key = BTN_0 }, + { .max = 28, .key = BTN_1 }, + { .max = 54, .key = BTN_2 }, + { .max = 100, .key = BTN_3 }, + { .max = 186, .key = BTN_4 }, + { .max = 430, .key = BTN_5 }, +}; + +static const int arizona_micd_levels[] = { + 3, 6, 8, 11, 13, 16, 18, 21, 23, 26, 28, 31, 34, 36, 39, 41, 44, 46, + 49, 52, 54, 57, 60, 62, 65, 67, 70, 73, 75, 78, 81, 83, 89, 94, 100, + 105, 111, 116, 122, 127, 139, 150, 161, 173, 186, 196, 209, 220, 245, + 270, 295, 321, 348, 375, 402, 430, 489, 550, 614, 681, 752, 903, 1071, + 1257, }; #define ARIZONA_CABLE_MECHANICAL 0 @@ -153,7 +161,7 @@ static void arizona_extcon_set_mode(struct arizona_extcon_info *info, int mode) { struct arizona *arizona = info->arizona; - mode %= info->num_micd_modes; + mode %= info->micd_num_modes; if (arizona->pdata.micd_pol_gpio > 0) gpio_set_value_cansleep(arizona->pdata.micd_pol_gpio, @@ -728,7 +736,7 @@ static irqreturn_t arizona_micdet(int irq, void *data) struct arizona_extcon_info *info = data; struct arizona *arizona = info->arizona; unsigned int val, lvl; - int ret, i; + int ret, i, key; mutex_lock(&info->lock); @@ -815,12 +823,13 @@ static irqreturn_t arizona_micdet(int irq, void *data) lvl = val & ARIZONA_MICD_LVL_MASK; lvl >>= ARIZONA_MICD_LVL_SHIFT; - for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) - if (lvl & arizona_lvl_to_key[i].status) - input_report_key(info->input, - arizona_lvl_to_key[i].report, - 1); - input_sync(info->input); + WARN_ON(!lvl); + WARN_ON(ffs(lvl) - 1 >= info->num_micd_ranges); + if (lvl && ffs(lvl) - 1 < info->num_micd_ranges) { + key = info->micd_ranges[ffs(lvl) - 1].key; + input_report_key(info->input, key, 1); + input_sync(info->input); + } } else if (info->detecting) { dev_dbg(arizona->dev, "Headphone detected\n"); @@ -834,9 +843,9 @@ static irqreturn_t arizona_micdet(int irq, void *data) } } else { dev_dbg(arizona->dev, "Mic button released\n"); - for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) + for (i = 0; i < info->num_micd_ranges; i++) input_report_key(info->input, - arizona_lvl_to_key[i].report, 0); + info->micd_ranges[i].key, 0); input_sync(info->input); arizona_extcon_pulse_micbias(info); } @@ -923,9 +932,9 @@ static irqreturn_t arizona_jackdet(int irq, void *data) info->mic = false; info->hpdet_done = false; - for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) + for (i = 0; i < info->num_micd_ranges; i++) input_report_key(info->input, - arizona_lvl_to_key[i].report, 0); + info->micd_ranges[i].key, 0); input_sync(info->input); ret = extcon_update_state(&info->edev, 0xffffffff, 0); @@ -954,13 +963,33 @@ static irqreturn_t arizona_jackdet(int irq, void *data) return IRQ_HANDLED; } +/* Map a level onto a slot in the register bank */ +static void arizona_micd_set_level(struct arizona *arizona, int index, + unsigned int level) +{ + int reg; + unsigned int mask; + + reg = ARIZONA_MIC_DETECT_LEVEL_4 - (index / 2); + + if (!(index % 2)) { + mask = 0x3f00; + level <<= 8; + } else { + mask = 0x3f; + } + + /* Program the level itself */ + regmap_update_bits(arizona->regmap, reg, mask, level); +} + static int 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 jack_irq_fall, jack_irq_rise; - int ret, mode, i; + int ret, mode, i, j; if (!arizona->dapm || !arizona->dapm->card) return -EPROBE_DEFER; @@ -1013,6 +1042,17 @@ static int arizona_extcon_probe(struct platform_device *pdev) goto err; } + info->input = devm_input_allocate_device(&pdev->dev); + if (!info->input) { + dev_err(arizona->dev, "Can't allocate input dev\n"); + ret = -ENOMEM; + goto err_register; + } + + info->input->name = "Headset"; + info->input->phys = "arizona/extcon"; + info->input->dev.parent = &pdev->dev; + if (pdata->num_micd_configs) { info->micd_modes = pdata->micd_configs; info->micd_num_modes = pdata->num_micd_configs; @@ -1068,6 +1108,66 @@ static int arizona_extcon_probe(struct platform_device *pdev) arizona->pdata.micd_dbtime << ARIZONA_MICD_DBTIME_SHIFT); + BUILD_BUG_ON(ARRAY_SIZE(arizona_micd_levels) != 0x40); + + if (arizona->pdata.num_micd_ranges) { + info->micd_ranges = pdata->micd_ranges; + info->num_micd_ranges = pdata->num_micd_ranges; + } else { + info->micd_ranges = micd_default_ranges; + info->num_micd_ranges = ARRAY_SIZE(micd_default_ranges); + } + + if (arizona->pdata.num_micd_ranges > ARIZONA_MAX_MICD_RANGE) { + dev_err(arizona->dev, "Too many MICD ranges: %d\n", + arizona->pdata.num_micd_ranges); + } + + if (info->num_micd_ranges > 1) { + for (i = 1; i < info->num_micd_ranges; i++) { + if (info->micd_ranges[i - 1].max > + info->micd_ranges[i].max) { + dev_err(arizona->dev, + "MICD ranges must be sorted\n"); + ret = -EINVAL; + goto err_input; + } + } + } + + /* Disable all buttons by default */ + regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_2, + ARIZONA_MICD_LVL_SEL_MASK, 0x81); + + /* Set up all the buttons the user specified */ + for (i = 0; i < info->num_micd_ranges; i++) { + for (j = 0; j < ARRAY_SIZE(arizona_micd_levels); j++) + if (arizona_micd_levels[j] >= info->micd_ranges[i].max) + break; + + if (j == ARRAY_SIZE(arizona_micd_levels)) { + dev_err(arizona->dev, "Unsupported MICD level %d\n", + info->micd_ranges[i].max); + ret = -EINVAL; + goto err_input; + } + + dev_dbg(arizona->dev, "%d ohms for MICD threshold %d\n", + arizona_micd_levels[j], i); + + arizona_micd_set_level(arizona, i, j); + input_set_capability(info->input, EV_KEY, + info->micd_ranges[i].key); + + /* Enable reporting of that range */ + regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_2, + 1 << i, 1 << i); + } + + /* Set all the remaining keys to a maximum */ + for (; i < ARIZONA_MAX_MICD_RANGE; i++) + arizona_micd_set_level(arizona, i, 0x3f); + /* * If we have a clamp use it, activating in conjunction with * GPIO5 if that is connected for jack detect operation. @@ -1095,20 +1195,6 @@ static int arizona_extcon_probe(struct platform_device *pdev) arizona_extcon_set_mode(info, 0); - info->input = devm_input_allocate_device(&pdev->dev); - if (!info->input) { - dev_err(arizona->dev, "Can't allocate input dev\n"); - ret = -ENOMEM; - goto err_register; - } - - for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) - input_set_capability(info->input, EV_KEY, - arizona_lvl_to_key[i].report); - info->input->name = "Headset"; - info->input->phys = "arizona/extcon"; - info->input->dev.parent = &pdev->dev; - pm_runtime_enable(&pdev->dev); pm_runtime_idle(&pdev->dev); pm_runtime_get_sync(&pdev->dev); diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 455c51d22d6b..eb11a8ac6db2 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -86,6 +86,11 @@ struct arizona_micd_config { bool gpio; }; +struct arizona_micd_range { + int max; /** Ohms */ + int key; /** Key to report to input layer */ +}; + struct arizona_pdata { int reset; /** GPIO controlling /RESET, if any */ int ldoena; /** GPIO controlling LODENA, if any */ @@ -138,6 +143,10 @@ struct arizona_pdata { /** Force MICBIAS on for mic detect */ bool micd_force_micbias; + /** Mic detect level parameters */ + const struct arizona_micd_range *micd_ranges; + int num_micd_ranges; + /** Headset polarity configurations */ struct arizona_micd_config *micd_configs; int num_micd_configs; -- cgit v1.2.3 From e56a0a572be150c79cdbf62ff98f4a63419e1c0b Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 1 Apr 2013 19:03:52 +0100 Subject: extcon: arizona: Allow pull to be disabled on GPIO5 when used for JACKET In some designs an external pull won't be needed. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 9 +++++++-- include/linux/mfd/arizona/pdata.h | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 95748d3cbc4e..132bc99fdc06 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -986,6 +986,7 @@ static int 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; + unsigned int val; int jack_irq_fall, jack_irq_rise; int ret, mode, i, j; @@ -1172,9 +1173,13 @@ static int arizona_extcon_probe(struct platform_device *pdev) */ if (info->micd_clamp) { if (arizona->pdata.jd_gpio5) { - /* Put the GPIO into input mode */ + /* Put the GPIO into input mode with optional pull */ + val = 0xc101; + if (arizona->pdata.jd_gpio5_nopull) + val &= ~ARIZONA_GPN_PU; + regmap_write(arizona->regmap, ARIZONA_GPIO5_CTRL, - 0xc101); + val); regmap_update_bits(arizona->regmap, ARIZONA_MICD_CLAMP_CONTROL, diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index eb11a8ac6db2..008b8c40549f 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -122,6 +122,9 @@ struct arizona_pdata { /** GPIO5 is used for jack detection */ bool jd_gpio5; + /** Internal pull on GPIO5 is disabled when used for jack detection */ + bool jd_gpio5_nopull; + /** Use the headphone detect circuit to identify the accessory */ bool hpdet_acc_id; -- cgit v1.2.3 From 9c2ba270eaa227c999af451e1c2c9bf0d24aa8e5 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 25 Feb 2013 23:42:31 +0000 Subject: extcon: arizona: Simplify HPDET based identification Rather than measuring both HP channels we can simply directly measure the microphone impedance and then rely on MICDET for final confirmation of the presence of a suitable microphone. This improves the overall performance of the identification process. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 46 +++++++++++++++++++++------------------ include/linux/mfd/arizona/pdata.h | 3 +++ 2 files changed, 28 insertions(+), 21 deletions(-) (limited to 'include') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 7c4ce812d735..a83ca27aa99f 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -459,7 +459,8 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info) return val; } -static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) +static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading, + bool *mic) { struct arizona *arizona = info->arizona; int id_gpio = arizona->pdata.hpdet_id_gpio; @@ -470,11 +471,9 @@ static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) */ if (arizona->pdata.hpdet_acc_id) { info->hpdet_res[info->num_hpdet_res++] = *reading; - info->hpdet_res[info->num_hpdet_res++] = *reading; /* Only check the mic directly if we didn't already ID it */ - if (id_gpio && info->num_hpdet_res == 2 && - !((info->hpdet_res[0] > info->hpdet_res[1] * 2))) { + if (id_gpio && info->num_hpdet_res == 1) { dev_dbg(arizona->dev, "Measuring mic\n"); regmap_update_bits(arizona->regmap, @@ -493,10 +492,8 @@ static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) } /* OK, got both. Now, compare... */ - dev_dbg(arizona->dev, "HPDET measured %d %d %d\n", - info->hpdet_res[0], info->hpdet_res[1], - info->hpdet_res[2]); - + dev_dbg(arizona->dev, "HPDET measured %d %d\n", + info->hpdet_res[0], info->hpdet_res[1]); /* Take the headphone impedance for the main report */ *reading = info->hpdet_res[0]; @@ -512,13 +509,11 @@ static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) } /* - * Either the two grounds measure differently or we - * measure the mic as high impedance. + * If we measure the mic as */ - if ((info->hpdet_res[0] > info->hpdet_res[1] * 2) || - (id_gpio && info->hpdet_res[2] > 1257)) { + if (!id_gpio || info->hpdet_res[1] > 50) { dev_dbg(arizona->dev, "Detected mic\n"); - info->mic = true; + *mic = true; info->detecting = true; } else { dev_dbg(arizona->dev, "Detected headphone\n"); @@ -541,6 +536,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) int id_gpio = arizona->pdata.hpdet_id_gpio; int report = ARIZONA_CABLE_HEADPHONE; int ret, reading; + bool mic = false; mutex_lock(&info->lock); @@ -576,7 +572,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) ARIZONA_HP_IMPEDANCE_RANGE_MASK | ARIZONA_HP_POLL, 0); - ret = arizona_hpdet_do_id(info, &reading); + ret = arizona_hpdet_do_id(info, &reading, &mic); if (ret == -EAGAIN) { goto out; } else if (ret < 0) { @@ -606,7 +602,7 @@ done: ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); /* If we have a mic then reenable MICDET */ - if (info->mic) + if (mic || info->mic) arizona_start_mic(info); if (info->hpdet_active) { @@ -681,6 +677,8 @@ err: static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info) { struct arizona *arizona = info->arizona; + int hp_reading = 32; + bool mic; int ret; dev_dbg(arizona->dev, "Starting identification via HPDET\n"); @@ -702,12 +700,18 @@ static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info) goto err; } - ret = regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, - ARIZONA_HP_POLL, ARIZONA_HP_POLL); - if (ret != 0) { - dev_err(arizona->dev, "Can't start HPDETL measurement: %d\n", - ret); - goto err; + if (arizona->pdata.hpdet_acc_id_line) { + ret = regmap_update_bits(arizona->regmap, + ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_POLL, ARIZONA_HP_POLL); + if (ret != 0) { + dev_err(arizona->dev, + "Can't start HPDETL measurement: %d\n", + ret); + goto err; + } + } else { + arizona_hpdet_do_id(info, &hp_reading, &mic); } return; diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 008b8c40549f..45c84777c624 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -128,6 +128,9 @@ struct arizona_pdata { /** Use the headphone detect circuit to identify the accessory */ bool hpdet_acc_id; + /** Check for line output with HPDET method */ + bool hpdet_acc_id_line; + /** GPIO used for mic isolation with HPDET */ int hpdet_id_gpio; -- cgit v1.2.3 From cd59e79656f4e7137909166248a935d422b1245a Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 1 Apr 2013 19:21:48 +0100 Subject: extcon: arizona: Allow additional debounce during microphone detection Help mitigate against mechanical bounce during the initial detection by allowing the configuration of an additional debounce on top of that the hardware does during the initial phase of microphone detection operation. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 35 ++++++++++++++++++++++++++++++----- include/linux/mfd/arizona/pdata.h | 3 +++ 2 files changed, 33 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 26f9a1ae15c4..c7f8eb4299d2 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -64,6 +64,7 @@ struct arizona_extcon_info { bool micd_clamp; struct delayed_work hpdet_work; + struct delayed_work micd_detect_work; struct delayed_work micd_timeout_work; bool hpdet_active; @@ -750,9 +751,11 @@ static void arizona_micd_timeout_work(struct work_struct *work) mutex_unlock(&info->lock); } -static irqreturn_t arizona_micdet(int irq, void *data) +static void arizona_micd_detect(struct work_struct *work) { - struct arizona_extcon_info *info = data; + struct arizona_extcon_info *info = container_of(work, + struct arizona_extcon_info, + micd_detect_work.work); struct arizona *arizona = info->arizona; unsigned int val = 0, lvl; int ret, i, key; @@ -766,7 +769,7 @@ static irqreturn_t arizona_micdet(int irq, void *data) if (ret != 0) { dev_err(arizona->dev, "Failed to read MICDET: %d\n", ret); mutex_unlock(&info->lock); - return IRQ_NONE; + return; } dev_dbg(arizona->dev, "MICDET: %x\n", val); @@ -774,14 +777,14 @@ static irqreturn_t arizona_micdet(int irq, void *data) if (!(val & ARIZONA_MICD_VALID)) { dev_warn(arizona->dev, "Microphone detection state invalid\n"); mutex_unlock(&info->lock); - return IRQ_NONE; + return; } } if (i == 10 && !(val & 0x7fc)) { dev_err(arizona->dev, "Failed to get valid MICDET value\n"); mutex_unlock(&info->lock); - return IRQ_NONE; + return; } /* Due to jack detect this should never happen */ @@ -890,6 +893,27 @@ handled: pm_runtime_mark_last_busy(info->dev); mutex_unlock(&info->lock); +} + +static irqreturn_t arizona_micdet(int irq, void *data) +{ + struct arizona_extcon_info *info = data; + struct arizona *arizona = info->arizona; + int debounce = arizona->pdata.micd_detect_debounce; + + cancel_delayed_work_sync(&info->micd_detect_work); + cancel_delayed_work_sync(&info->micd_timeout_work); + + mutex_lock(&info->lock); + if (!info->detecting) + debounce = 0; + mutex_unlock(&info->lock); + + if (debounce) + schedule_delayed_work(&info->micd_detect_work, + msecs_to_jiffies(debounce)); + else + arizona_micd_detect(&info->micd_detect_work.work); return IRQ_HANDLED; } @@ -1072,6 +1096,7 @@ static int arizona_extcon_probe(struct platform_device *pdev) info->dev = &pdev->dev; info->last_jackdet = ~(ARIZONA_MICD_CLAMP_STS | ARIZONA_JD1_STS); INIT_DELAYED_WORK(&info->hpdet_work, arizona_hpdet_work); + INIT_DELAYED_WORK(&info->micd_detect_work, arizona_micd_detect); INIT_DELAYED_WORK(&info->micd_timeout_work, arizona_micd_timeout_work); platform_set_drvdata(pdev, info); diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 45c84777c624..3ef300baa2e6 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -134,6 +134,9 @@ struct arizona_pdata { /** GPIO used for mic isolation with HPDET */ int hpdet_id_gpio; + /** Extra debounce timeout used during initial mic detection (ms) */ + int micd_detect_debounce; + /** GPIO for mic detection polarity */ int micd_pol_gpio; -- cgit v1.2.3 From 7abd4e2a8f1c3e534da44c35e2d3d6353573e51f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 1 Apr 2013 19:25:55 +0100 Subject: extcon: arizona: Make mic detection timeout configurable Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 13 ++++++++++--- include/linux/mfd/arizona/pdata.h | 3 +++ 2 files changed, 13 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index c7f8eb4299d2..7a1b4a7791ba 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -42,7 +42,7 @@ #define ARIZONA_HPDET_MAX 10000 #define HPDET_DEBOUNCE 500 -#define MICD_TIMEOUT 2000 +#define DEFAULT_MICD_TIMEOUT 2000 struct arizona_extcon_info { struct device *dev; @@ -60,6 +60,8 @@ struct arizona_extcon_info { const struct arizona_micd_range *micd_ranges; int num_micd_ranges; + int micd_timeout; + bool micd_reva; bool micd_clamp; @@ -889,7 +891,7 @@ static void arizona_micd_detect(struct work_struct *work) handled: if (info->detecting) schedule_delayed_work(&info->micd_timeout_work, - msecs_to_jiffies(MICD_TIMEOUT)); + msecs_to_jiffies(info->micd_timeout)); pm_runtime_mark_last_busy(info->dev); mutex_unlock(&info->lock); @@ -970,7 +972,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data) if (cancelled_mic) schedule_delayed_work(&info->micd_timeout_work, - msecs_to_jiffies(MICD_TIMEOUT)); + msecs_to_jiffies(info->micd_timeout)); goto out; } @@ -1027,6 +1029,11 @@ static irqreturn_t arizona_jackdet(int irq, void *data) ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB); } + if (arizona->pdata.micd_timeout) + info->micd_timeout = arizona->pdata.micd_timeout; + else + info->micd_timeout = DEFAULT_MICD_TIMEOUT; + /* Clear trig_sts to make sure DCVDD is not forced up */ regmap_write(arizona->regmap, ARIZONA_AOD_WKUP_AND_TRIG, ARIZONA_MICD_CLAMP_FALL_TRIG_STS | diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 3ef300baa2e6..a0f940987a3e 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -149,6 +149,9 @@ struct arizona_pdata { /** Mic detect debounce level */ int micd_dbtime; + /** Mic detect timeout (ms) */ + int micd_timeout; + /** Force MICBIAS on for mic detect */ bool micd_force_micbias; -- cgit v1.2.3 From e46980a10a76ec3282dd6832c1974b880acd23d3 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Tue, 9 Apr 2013 01:51:38 +0300 Subject: mei: bus: Add device enabling and disabling API It should be left to the drivers to enable and disable the device on the MEI bus when e.g getting probed. For drivers to be able to safely call the enable and disable hooks, the mei_cl_ops must be set before it's probed and thus this should happen before registering the device on the MEI bus. Hence the mei_cl_add_device() prototype change. Signed-off-by: Samuel Ortiz Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- Documentation/misc-devices/mei/mei-client-bus.txt | 7 +- drivers/misc/mei/bus.c | 99 ++++++++++++++++++++++- drivers/misc/mei/mei_dev.h | 28 ++++--- include/linux/mei_cl_bus.h | 3 + 4 files changed, 123 insertions(+), 14 deletions(-) (limited to 'include') diff --git a/Documentation/misc-devices/mei/mei-client-bus.txt b/Documentation/misc-devices/mei/mei-client-bus.txt index 9dc5ebf94eb1..f83910a8ce76 100644 --- a/Documentation/misc-devices/mei/mei-client-bus.txt +++ b/Documentation/misc-devices/mei/mei-client-bus.txt @@ -104,13 +104,16 @@ int contact_probe(struct mei_cl_device *dev, struct mei_cl_device_id *id) struct contact_driver *contact; [...] + mei_cl_enable_device(dev); + mei_cl_register_event_cb(dev, contact_event_cb, contact); return 0; } -In the probe routine the driver basically registers an ME bus event handler -which is as close as it can get to registering a threaded IRQ handler. +In the probe routine the driver first enable the MEI device and then registers +an ME bus event handler which is as close as it can get to registering a +threaded IRQ handler. The handler implementation will typically call some I/O routine depending on the pending events: diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index 6badfa1110e9..834ceeb69cbf 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -153,7 +153,8 @@ static struct mei_cl *mei_bus_find_mei_cl_by_uuid(struct mei_device *dev, return NULL; } struct mei_cl_device *mei_cl_add_device(struct mei_device *dev, - uuid_le uuid, char *name) + uuid_le uuid, char *name, + struct mei_cl_ops *ops) { struct mei_cl_device *device; struct mei_cl *cl; @@ -168,6 +169,7 @@ struct mei_cl_device *mei_cl_add_device(struct mei_device *dev, return NULL; device->cl = cl; + device->ops = ops; device->dev.parent = &dev->pdev->dev; device->dev.bus = &mei_cl_bus_type; @@ -408,6 +410,101 @@ void mei_cl_set_drvdata(struct mei_cl_device *device, void *data) } EXPORT_SYMBOL_GPL(mei_cl_set_drvdata); +int mei_cl_enable_device(struct mei_cl_device *device) +{ + int err; + struct mei_device *dev; + struct mei_cl *cl = device->cl; + + if (cl == NULL) + return -ENODEV; + + dev = cl->dev; + + mutex_lock(&dev->device_lock); + + cl->state = MEI_FILE_CONNECTING; + + err = mei_cl_connect(cl, NULL); + if (err < 0) { + mutex_unlock(&dev->device_lock); + dev_err(&dev->pdev->dev, "Could not connect to the ME client"); + + return err; + } + + mutex_unlock(&dev->device_lock); + + if (device->event_cb && !cl->read_cb) + mei_cl_read_start(device->cl); + + if (!device->ops || !device->ops->enable) + return 0; + + return device->ops->enable(device); +} +EXPORT_SYMBOL_GPL(mei_cl_enable_device); + +int mei_cl_disable_device(struct mei_cl_device *device) +{ + int err; + struct mei_device *dev; + struct mei_cl *cl = device->cl; + + if (cl == NULL) + return -ENODEV; + + dev = cl->dev; + + mutex_lock(&dev->device_lock); + + if (cl->state != MEI_FILE_CONNECTED) { + mutex_unlock(&dev->device_lock); + dev_err(&dev->pdev->dev, "Already disconnected"); + + return 0; + } + + cl->state = MEI_FILE_DISCONNECTING; + + err = mei_cl_disconnect(cl); + if (err < 0) { + mutex_unlock(&dev->device_lock); + dev_err(&dev->pdev->dev, + "Could not disconnect from the ME client"); + + return err; + } + + /* Flush queues and remove any pending read */ + mei_cl_flush_queues(cl); + + if (cl->read_cb) { + struct mei_cl_cb *cb = NULL; + + cb = mei_cl_find_read_cb(cl); + /* Remove entry from read list */ + if (cb) + list_del(&cb->list); + + cb = cl->read_cb; + cl->read_cb = NULL; + + if (cb) { + mei_io_cb_free(cb); + cb = NULL; + } + } + + mutex_unlock(&dev->device_lock); + + if (!device->ops || !device->ops->disable) + return 0; + + return device->ops->disable(device); +} +EXPORT_SYMBOL_GPL(mei_cl_disable_device); + void mei_cl_bus_rx_event(struct mei_cl *cl) { struct mei_cl_device *device = cl->device; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index d786da6aa25b..c02967d01527 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -269,30 +269,36 @@ struct mei_hw_ops { }; /* MEI bus API*/ -struct mei_cl_device *mei_cl_add_device(struct mei_device *dev, - uuid_le uuid, char *name); -void mei_cl_remove_device(struct mei_cl_device *device); - -int __mei_cl_async_send(struct mei_cl *cl, u8 *buf, size_t length); -int __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length); -int __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length); /** - * struct mei_cl_transport_ops - MEI CL device transport ops + * struct mei_cl_ops - MEI CL device ops * This structure allows ME host clients to implement technology - * specific transport layers. + * specific operations. * + * @enable: Enable an MEI CL device. Some devices require specific + * HECI commands to initialize completely. + * @disable: Disable an MEI CL device. * @send: Tx hook for the device. This allows ME host clients to trap * the device driver buffers before actually physically * pushing it to the ME. * @recv: Rx hook for the device. This allows ME host clients to trap the * ME buffers before forwarding them to the device driver. */ -struct mei_cl_transport_ops { +struct mei_cl_ops { + int (*enable)(struct mei_cl_device *device); + int (*disable)(struct mei_cl_device *device); int (*send)(struct mei_cl_device *device, u8 *buf, size_t length); int (*recv)(struct mei_cl_device *device, u8 *buf, size_t length); }; +struct mei_cl_device *mei_cl_add_device(struct mei_device *dev, + uuid_le uuid, char *name, + struct mei_cl_ops *ops); +void mei_cl_remove_device(struct mei_cl_device *device); + +int __mei_cl_async_send(struct mei_cl *cl, u8 *buf, size_t length); +int __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length); +int __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length); void mei_cl_bus_rx_event(struct mei_cl *cl); int mei_cl_bus_init(void); void mei_cl_bus_exit(void); @@ -319,7 +325,7 @@ struct mei_cl_device { struct mei_cl *cl; - const struct mei_cl_transport_ops *ops; + const struct mei_cl_ops *ops; struct work_struct event_work; mei_cl_event_cb_t event_cb; diff --git a/include/linux/mei_cl_bus.h b/include/linux/mei_cl_bus.h index 1bece18825ba..d14af7b722ef 100644 --- a/include/linux/mei_cl_bus.h +++ b/include/linux/mei_cl_bus.h @@ -38,4 +38,7 @@ int mei_cl_register_event_cb(struct mei_cl_device *device, void *mei_cl_get_drvdata(const struct mei_cl_device *device); void mei_cl_set_drvdata(struct mei_cl_device *device, void *data); +int mei_cl_enable_device(struct mei_cl_device *device); +int mei_cl_disable_device(struct mei_cl_device *device); + #endif /* _LINUX_MEI_CL_BUS_H */ -- cgit v1.2.3