From patchwork Tue Jan 14 18:41:35 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Josh Cartwright X-Patchwork-Id: 3488591 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork2.web.kernel.org (Postfix) with ESMTP id C610EC02DC for ; Tue, 14 Jan 2014 23:03:38 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id DB2EC2024C for ; Tue, 14 Jan 2014 23:03:36 +0000 (UTC) Received: from casper.infradead.org (casper.infradead.org [85.118.1.10]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id A3A222024F for ; Tue, 14 Jan 2014 23:03:34 +0000 (UTC) Received: from merlin.infradead.org ([2001:4978:20e::2]) by casper.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1W3CzH-0006V4-Cv; Tue, 14 Jan 2014 23:01:37 +0000 Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1W3Cyg-0003Pi-Il; Tue, 14 Jan 2014 23:00:58 +0000 Received: from smtp.codeaurora.org ([198.145.11.231]) by merlin.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1W3Cxq-0003J6-W9 for linux-arm-kernel@lists.infradead.org; Tue, 14 Jan 2014 23:00:16 +0000 Received: from smtp.codeaurora.org (localhost [127.0.0.1]) by smtp.codeaurora.org (Postfix) with ESMTP id B881313F128; Tue, 14 Jan 2014 22:59:24 +0000 (UTC) Received: by smtp.codeaurora.org (Postfix, from userid 486) id A42A813F141; Tue, 14 Jan 2014 22:59:24 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Spam-Level: X-Spam-Status: No, score=-2.7 required=5.0 tests=BAYES_00, DATE_IN_PAST_03_06, RCVD_IN_DNSWL_MED, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 Received: from joshc.qualcomm.com (rrcs-67-52-129-61.west.biz.rr.com [67.52.129.61]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) (Authenticated sender: joshc@smtp.codeaurora.org) by smtp.codeaurora.org (Postfix) with ESMTPSA id 18AA013F12F; Tue, 14 Jan 2014 22:59:23 +0000 (UTC) Received: by joshc.qualcomm.com (Postfix, from userid 1000) id 0CB7560CE0; Tue, 14 Jan 2014 16:56:35 -0600 (CST) Message-Id: <51a79722d25060b91bad6122fd4f327acb56ecd7.1389738151.git.joshc@codeaurora.org> In-Reply-To: References: From: Josh Cartwright To: Greg Kroah-Hartman Subject: [PATCH v4 1/6] spmi: Linux driver framework for SPMI Date: Tue, 14 Jan 2014 12:41:35 -0600 X-Virus-Scanned: ClamAV using ClamSMTP X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20140114_180007_526455_225FDFFA X-CRM114-Status: GOOD ( 26.43 ) X-Spam-Score: -0.4 (/) Cc: linux-arm-msm@vger.kernel.org, Gilad Avidov , linux-kernel@vger.kernel.org, Michael Bohan , Sagar Dharia , linux-arm-kernel@lists.infradead.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Virus-Scanned: ClamAV using ClamSMTP From: Kenneth Heitke System Power Management Interface (SPMI) is a specification developed by the MIPI (Mobile Industry Process Interface) Alliance optimized for the real time control of Power Management ICs (PMIC). SPMI is a two-wire serial interface that supports up to 4 master devices and up to 16 logical slaves. The framework supports message APIs, multiple busses (1 controller per bus) and multiple clients/slave devices per controller. Signed-off-by: Kenneth Heitke Signed-off-by: Michael Bohan Signed-off-by: Josh Cartwright --- drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/spmi/Kconfig | 9 + drivers/spmi/Makefile | 4 + drivers/spmi/spmi.c | 609 ++++++++++++++++++++++++++++++++++++++++ include/dt-bindings/spmi/spmi.h | 18 ++ include/linux/mod_devicetable.h | 8 + include/linux/spmi.h | 191 +++++++++++++ 8 files changed, 842 insertions(+) create mode 100644 drivers/spmi/Kconfig create mode 100644 drivers/spmi/Makefile create mode 100644 drivers/spmi/spmi.c create mode 100644 include/dt-bindings/spmi/spmi.h create mode 100644 include/linux/spmi.h diff --git a/drivers/Kconfig b/drivers/Kconfig index b3138fb..e0a4ae6 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -52,6 +52,8 @@ source "drivers/i2c/Kconfig" source "drivers/spi/Kconfig" +source "drivers/spmi/Kconfig" + source "drivers/hsi/Kconfig" source "drivers/pps/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 3cc8214..0a2fb78 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -66,6 +66,7 @@ obj-$(CONFIG_ATA) += ata/ obj-$(CONFIG_TARGET_CORE) += target/ obj-$(CONFIG_MTD) += mtd/ obj-$(CONFIG_SPI) += spi/ +obj-$(CONFIG_SPMI) += spmi/ obj-y += hsi/ obj-y += net/ obj-$(CONFIG_ATM) += atm/ diff --git a/drivers/spmi/Kconfig b/drivers/spmi/Kconfig new file mode 100644 index 0000000..1dbfee0 --- /dev/null +++ b/drivers/spmi/Kconfig @@ -0,0 +1,9 @@ +# +# SPMI driver configuration +# +menuconfig SPMI + tristate "SPMI support" + help + SPMI (System Power Management Interface) is a two-wire + serial interface between baseband and application processors + and Power Management Integrated Circuits (PMIC). diff --git a/drivers/spmi/Makefile b/drivers/spmi/Makefile new file mode 100644 index 0000000..1de1acd --- /dev/null +++ b/drivers/spmi/Makefile @@ -0,0 +1,4 @@ +# +# Makefile for kernel SPMI framework. +# +obj-$(CONFIG_SPMI) += spmi.o diff --git a/drivers/spmi/spmi.c b/drivers/spmi/spmi.c new file mode 100644 index 0000000..6122c8f --- /dev/null +++ b/drivers/spmi/spmi.c @@ -0,0 +1,609 @@ +/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static DEFINE_IDA(ctrl_ida); + +static void spmi_dev_release(struct device *dev) +{ + struct spmi_device *sdev = to_spmi_device(dev); + kfree(sdev); +} + +static const struct device_type spmi_dev_type = { + .release = spmi_dev_release, +}; + +static void spmi_ctrl_release(struct device *dev) +{ + struct spmi_controller *ctrl = to_spmi_controller(dev); + ida_simple_remove(&ctrl_ida, ctrl->nr); + kfree(ctrl); +} + +static const struct device_type spmi_ctrl_type = { + .release = spmi_ctrl_release, +}; + +#ifdef CONFIG_PM_RUNTIME +static int spmi_runtime_suspend(struct device *dev) +{ + struct spmi_device *sdev = to_spmi_device(dev); + int err; + + err = pm_generic_runtime_suspend(dev); + if (err) + return err; + + return spmi_command_sleep(sdev); +} + +static int spmi_runtime_resume(struct device *dev) +{ + struct spmi_device *sdev = to_spmi_device(dev); + int err; + + err = spmi_command_wakeup(sdev); + if (err) + return err; + + return pm_generic_runtime_resume(dev); +} +#endif + +static const struct dev_pm_ops spmi_pm_ops = { + SET_RUNTIME_PM_OPS( + spmi_runtime_suspend, + spmi_runtime_resume, + NULL + ) +}; + +static int spmi_device_match(struct device *dev, struct device_driver *drv) +{ + if (of_driver_match_device(dev, drv)) + return 1; + + if (drv->name) + return strncmp(dev_name(dev), drv->name, + SPMI_NAME_SIZE) == 0; + + return 0; +} + +/** + * spmi_device_add() - add a device previously constructed via spmi_device_alloc() + * @sdev: spmi_device to be added + */ +int spmi_device_add(struct spmi_device *sdev) +{ + struct spmi_controller *ctrl = sdev->ctrl; + int err; + + dev_set_name(&sdev->dev, "%d-%02x", ctrl->nr, sdev->usid); + + err = device_add(&sdev->dev); + if (err < 0) { + dev_err(&sdev->dev, "Can't add %s, status %d\n", + dev_name(&sdev->dev), err); + goto err_device_add; + } + + dev_dbg(&sdev->dev, "device %s registered\n", dev_name(&sdev->dev)); + +err_device_add: + return err; +} +EXPORT_SYMBOL_GPL(spmi_device_add); + +/** + * spmi_device_remove(): remove an SPMI device + * @sdev: spmi_device to be removed + */ +void spmi_device_remove(struct spmi_device *sdev) +{ + device_unregister(&sdev->dev); +} +EXPORT_SYMBOL_GPL(spmi_device_remove); + +static inline int +spmi_cmd(struct spmi_controller *ctrl, u8 opcode, u8 sid) +{ + if (!ctrl || !ctrl->cmd || ctrl->dev.type != &spmi_ctrl_type) + return -EINVAL; + + return ctrl->cmd(ctrl, opcode, sid); +} + +static inline int spmi_read_cmd(struct spmi_controller *ctrl, u8 opcode, + u8 sid, u16 addr, u8 *buf, size_t len) +{ + if (!ctrl || !ctrl->read_cmd || ctrl->dev.type != &spmi_ctrl_type) + return -EINVAL; + + return ctrl->read_cmd(ctrl, opcode, sid, addr, buf, len); +} + +static inline int spmi_write_cmd(struct spmi_controller *ctrl, u8 opcode, + u8 sid, u16 addr, const u8 *buf, size_t len) +{ + if (!ctrl || !ctrl->write_cmd || ctrl->dev.type != &spmi_ctrl_type) + return -EINVAL; + + return ctrl->write_cmd(ctrl, opcode, sid, addr, buf, len); +} + +/** + * spmi_register_read() - register read + * @sdev: SPMI device. + * @addr: slave register address (5-bit address). + * @buf: buffer to be populated with data from the Slave. + * + * Reads 1 byte of data from a Slave device register. + */ +int spmi_register_read(struct spmi_device *sdev, u8 addr, u8 *buf) +{ + /* 5-bit register address */ + if (addr > 0x1F) + return -EINVAL; + + return spmi_read_cmd(sdev->ctrl, SPMI_CMD_READ, sdev->usid, addr, + buf, 1); +} +EXPORT_SYMBOL_GPL(spmi_register_read); + +/** + * spmi_ext_register_read() - extended register read + * @sdev: SPMI device. + * @addr: slave register address (8-bit address). + * @buf: buffer to be populated with data from the Slave. + * @len: the request number of bytes to read (up to 16 bytes). + * + * Reads up to 16 bytes of data from the extended register space on a + * Slave device. + */ +int spmi_ext_register_read(struct spmi_device *sdev, u8 addr, u8 *buf, + size_t len) +{ + /* 8-bit register address, up to 16 bytes */ + if (len == 0 || len > 16) + return -EINVAL; + + return spmi_read_cmd(sdev->ctrl, SPMI_CMD_EXT_READ, sdev->usid, addr, + buf, len); +} +EXPORT_SYMBOL_GPL(spmi_ext_register_read); + +/** + * spmi_ext_register_readl() - extended register read long + * @sdev: SPMI device. + * @addr: slave register address (16-bit address). + * @buf: buffer to be populated with data from the Slave. + * @len: the request number of bytes to read (up to 8 bytes). + * + * Reads up to 8 bytes of data from the extended register space on a + * Slave device using 16-bit address. + */ +int spmi_ext_register_readl(struct spmi_device *sdev, u16 addr, u8 *buf, + size_t len) +{ + /* 16-bit register address, up to 8 bytes */ + if (len == 0 || len > 8) + return -EINVAL; + + return spmi_read_cmd(sdev->ctrl, SPMI_CMD_EXT_READL, sdev->usid, addr, + buf, len); +} +EXPORT_SYMBOL_GPL(spmi_ext_register_readl); + +/** + * spmi_register_write() - register write + * @sdev: SPMI device + * @addr: slave register address (5-bit address). + * @data: buffer containing the data to be transferred to the Slave. + * + * Writes 1 byte of data to a Slave device register. + */ +int spmi_register_write(struct spmi_device *sdev, u8 addr, u8 data) +{ + /* 5-bit register address */ + if (addr > 0x1F) + return -EINVAL; + + return spmi_write_cmd(sdev->ctrl, SPMI_CMD_WRITE, sdev->usid, addr, + &data, 1); +} +EXPORT_SYMBOL_GPL(spmi_register_write); + +/** + * spmi_register_zero_write() - register zero write + * @sdev: SPMI device. + * @data: the data to be written to register 0 (7-bits). + * + * Writes data to register 0 of the Slave device. + */ +int spmi_register_zero_write(struct spmi_device *sdev, u8 data) +{ + return spmi_write_cmd(sdev->ctrl, SPMI_CMD_ZERO_WRITE, sdev->usid, 0, + &data, 1); +} +EXPORT_SYMBOL_GPL(spmi_register_zero_write); + +/** + * spmi_ext_register_write() - extended register write + * @sdev: SPMI device. + * @addr: slave register address (8-bit address). + * @buf: buffer containing the data to be transferred to the Slave. + * @len: the request number of bytes to read (up to 16 bytes). + * + * Writes up to 16 bytes of data to the extended register space of a + * Slave device. + */ +int spmi_ext_register_write(struct spmi_device *sdev, u8 addr, const u8 *buf, + size_t len) +{ + /* 8-bit register address, up to 16 bytes */ + if (len == 0 || len > 16) + return -EINVAL; + + return spmi_write_cmd(sdev->ctrl, SPMI_CMD_EXT_WRITE, sdev->usid, addr, + buf, len); +} +EXPORT_SYMBOL_GPL(spmi_ext_register_write); + +/** + * spmi_ext_register_writel() - extended register write long + * @sdev: SPMI device. + * @addr: slave register address (16-bit address). + * @buf: buffer containing the data to be transferred to the Slave. + * @len: the request number of bytes to read (up to 8 bytes). + * + * Writes up to 8 bytes of data to the extended register space of a + * Slave device using 16-bit address. + */ +int spmi_ext_register_writel(struct spmi_device *sdev, u16 addr, const u8 *buf, + size_t len) +{ + /* 4-bit Slave Identifier, 16-bit register address, up to 8 bytes */ + if (len == 0 || len > 8) + return -EINVAL; + + return spmi_write_cmd(sdev->ctrl, SPMI_CMD_EXT_WRITEL, sdev->usid, + addr, buf, len); +} +EXPORT_SYMBOL_GPL(spmi_ext_register_writel); + +/** + * spmi_command_reset() - sends RESET command to the specified slave + * @sdev: SPMI device. + * + * The Reset command initializes the Slave and forces all registers to + * their reset values. The Slave shall enter the STARTUP state after + * receiving a Reset command. + */ +int spmi_command_reset(struct spmi_device *sdev) +{ + return spmi_cmd(sdev->ctrl, SPMI_CMD_RESET, sdev->usid); +} +EXPORT_SYMBOL_GPL(spmi_command_reset); + +/** + * spmi_command_sleep() - sends SLEEP command to the specified SPMI device + * @sdev: SPMI device. + * + * The Sleep command causes the Slave to enter the user defined SLEEP state. + */ +int spmi_command_sleep(struct spmi_device *sdev) +{ + return spmi_cmd(sdev->ctrl, SPMI_CMD_SLEEP, sdev->usid); +} +EXPORT_SYMBOL_GPL(spmi_command_sleep); + +/** + * spmi_command_wakeup() - sends WAKEUP command to the specified SPMI device + * @sdev: SPMI device. + * + * The Wakeup command causes the Slave to move from the SLEEP state to + * the ACTIVE state. + */ +int spmi_command_wakeup(struct spmi_device *sdev) +{ + return spmi_cmd(sdev->ctrl, SPMI_CMD_WAKEUP, sdev->usid); +} +EXPORT_SYMBOL_GPL(spmi_command_wakeup); + +/** + * spmi_command_shutdown() - sends SHUTDOWN command to the specified SPMI device + * @sdev: SPMI device. + * + * The Shutdown command causes the Slave to enter the SHUTDOWN state. + */ +int spmi_command_shutdown(struct spmi_device *sdev) +{ + return spmi_cmd(sdev->ctrl, SPMI_CMD_SHUTDOWN, sdev->usid); +} +EXPORT_SYMBOL_GPL(spmi_command_shutdown); + +static int spmi_drv_probe(struct device *dev) +{ + const struct spmi_driver *sdrv = to_spmi_driver(dev->driver); + struct spmi_device *sdev = to_spmi_device(dev); + int err; + + /* Ensure the slave is in ACTIVE state */ + err = spmi_command_wakeup(sdev); + if (err) + goto fail_wakeup; + + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + err = sdrv->probe(sdev); + if (err) + goto fail_probe; + + return 0; + +fail_probe: + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_put_noidle(dev); +fail_wakeup: + return err; +} + +static int spmi_drv_remove(struct device *dev) +{ + const struct spmi_driver *sdrv = to_spmi_driver(dev->driver); + + pm_runtime_get_sync(dev); + sdrv->remove(to_spmi_device(dev)); + pm_runtime_put_noidle(dev); + + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_put_noidle(dev); + return 0; +} + +static struct bus_type spmi_bus_type = { + .name = "spmi", + .match = spmi_device_match, + .pm = &spmi_pm_ops, + .probe = spmi_drv_probe, + .remove = spmi_drv_remove, +}; + +/** + * spmi_controller_alloc() - Allocate a new SPMI device + * @ctrl: associated controller + * + * Caller is responsible for either calling spmi_device_add() to add the + * newly allocated controller, or calling spmi_device_put() to discard it. + */ +struct spmi_device *spmi_device_alloc(struct spmi_controller *ctrl) +{ + struct spmi_device *sdev; + + sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); + if (!sdev) + return NULL; + + sdev->ctrl = ctrl; + device_initialize(&sdev->dev); + sdev->dev.parent = &ctrl->dev; + sdev->dev.bus = &spmi_bus_type; + sdev->dev.type = &spmi_dev_type; + return sdev; +} +EXPORT_SYMBOL_GPL(spmi_device_alloc); + +/** + * spmi_controller_alloc() - Allocate a new SPMI controller + * @parent: parent device + * @size: size of private data + * + * Caller is responsible for either calling spmi_controller_add() to add the + * newly allocated controller, or calling spmi_controller_put() to discard it. + * The allocated private data region may be accessed via + * spmi_controller_get_drvdata() + */ +struct spmi_controller *spmi_controller_alloc(struct device *parent, + size_t size) +{ + struct spmi_controller *ctrl; + int id; + + if (WARN_ON(!parent)) + return NULL; + + ctrl = kzalloc(sizeof(*ctrl) + size, GFP_KERNEL); + if (!ctrl) + return NULL; + + device_initialize(&ctrl->dev); + ctrl->dev.type = &spmi_ctrl_type; + ctrl->dev.bus = &spmi_bus_type; + ctrl->dev.parent = parent; + ctrl->dev.of_node = parent->of_node; + spmi_controller_set_drvdata(ctrl, &ctrl[1]); + + id = ida_simple_get(&ctrl_ida, 0, 0, GFP_KERNEL); + if (id < 0) { + dev_err(parent, + "unable to allocate SPMI controller identifier.\n"); + spmi_controller_put(ctrl); + return NULL; + } + + ctrl->nr = id; + dev_set_name(&ctrl->dev, "spmi-%d", id); + + dev_dbg(&ctrl->dev, "allocated controller 0x%p id %d\n", ctrl, id); + return ctrl; +} +EXPORT_SYMBOL_GPL(spmi_controller_alloc); + +static void of_spmi_register_devices(struct spmi_controller *ctrl) +{ + struct device_node *node; + int err; + + if (!ctrl->dev.of_node) + return; + + for_each_available_child_of_node(ctrl->dev.of_node, node) { + struct spmi_device *sdev; + u32 reg[2]; + + dev_dbg(&ctrl->dev, "adding child %s\n", node->full_name); + + err = of_property_read_u32_array(node, "reg", reg, 2); + if (err) { + dev_err(&ctrl->dev, + "node %s err (%d) does not have 'reg' property\n", + node->full_name, err); + continue; + } + + if (reg[1] != SPMI_USID) { + dev_err(&ctrl->dev, + "node %s contains unsupported 'reg' entry\n", + node->full_name); + continue; + } + + if (reg[0] >= SPMI_MAX_SLAVE_ID) { + dev_err(&ctrl->dev, + "invalid usid on node %s\n", + node->full_name); + continue; + } + + dev_dbg(&ctrl->dev, "read usid %02x\n", reg[0]); + + sdev = spmi_device_alloc(ctrl); + if (!sdev) + continue; + + sdev->dev.of_node = node; + sdev->usid = (u8) reg[0]; + + err = spmi_device_add(sdev); + if (err) { + dev_err(&sdev->dev, + "failure adding device. status %d\n", err); + spmi_device_put(sdev); + } + } +} + +/** + * spmi_controller_add() - Add an SPMI controller + * @ctrl: controller to be registered. + * + * Register a controller previously allocated via spmi_controller_alloc() with + * the SPMI core. + */ +int spmi_controller_add(struct spmi_controller *ctrl) +{ + int ret; + + /* Can't register until after driver model init */ + if (WARN_ON(!spmi_bus_type.p)) + return -EAGAIN; + + ret = device_add(&ctrl->dev); + if (ret) + return ret; + + if (IS_ENABLED(CONFIG_OF)) + of_spmi_register_devices(ctrl); + + dev_dbg(&ctrl->dev, "spmi-%d registered: dev:%p\n", + ctrl->nr, &ctrl->dev); + + return 0; +}; +EXPORT_SYMBOL_GPL(spmi_controller_add); + +/* Remove a device associated with a controller */ +static int spmi_ctrl_remove_device(struct device *dev, void *data) +{ + struct spmi_device *spmidev = to_spmi_device(dev); + if (dev->type == &spmi_dev_type) + spmi_device_remove(spmidev); + return 0; +} + +/** + * spmi_controller_remove(): remove an SPMI controller + * @ctrl: controller to remove + * + * Remove a SPMI controller. Caller is responsible for calling + * spmi_controller_put() to discard the allocated controller. + */ +void spmi_controller_remove(struct spmi_controller *ctrl) +{ + int dummy; + + if (!ctrl) + return; + + dummy = device_for_each_child(&ctrl->dev, NULL, + spmi_ctrl_remove_device); + device_del(&ctrl->dev); +} +EXPORT_SYMBOL_GPL(spmi_controller_remove); + +/** + * spmi_driver_register() - Register client driver with SPMI core + * @sdrv: client driver to be associated with client-device. + * + * This API will register the client driver with the SPMI framework. + * It is typically called from the driver's module-init function. + */ +int spmi_driver_register(struct spmi_driver *sdrv) +{ + sdrv->driver.bus = &spmi_bus_type; + return driver_register(&sdrv->driver); +} +EXPORT_SYMBOL_GPL(spmi_driver_register); + +static void __exit spmi_exit(void) +{ + bus_unregister(&spmi_bus_type); +} +module_exit(spmi_exit); + +static int __init spmi_init(void) +{ + return bus_register(&spmi_bus_type); +} +postcore_initcall(spmi_init); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("SPMI module"); +MODULE_ALIAS("platform:spmi"); diff --git a/include/dt-bindings/spmi/spmi.h b/include/dt-bindings/spmi/spmi.h new file mode 100644 index 0000000..d11e1e5 --- /dev/null +++ b/include/dt-bindings/spmi/spmi.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * 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 __DT_BINDINGS_SPMI_H +#define __DT_BINDINGS_SPMI_H + +#define SPMI_USID 0 +#define SPMI_GSID 1 + +#endif diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index 45e9214..677e474 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -432,6 +432,14 @@ struct spi_device_id { kernel_ulong_t driver_data; /* Data private to the driver */ }; +#define SPMI_NAME_SIZE 32 +#define SPMI_MODULE_PREFIX "spmi:" + +struct spmi_device_id { + char name[SPMI_NAME_SIZE]; + kernel_ulong_t driver_data; /* Data private to the driver */ +}; + /* dmi */ enum dmi_field { DMI_NONE, diff --git a/include/linux/spmi.h b/include/linux/spmi.h new file mode 100644 index 0000000..91f5eab --- /dev/null +++ b/include/linux/spmi.h @@ -0,0 +1,191 @@ +/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * + * 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_SPMI_H +#define _LINUX_SPMI_H + +#include +#include +#include + +/* Maximum slave identifier */ +#define SPMI_MAX_SLAVE_ID 16 + +/* SPMI Commands */ +#define SPMI_CMD_EXT_WRITE 0x00 +#define SPMI_CMD_RESET 0x10 +#define SPMI_CMD_SLEEP 0x11 +#define SPMI_CMD_SHUTDOWN 0x12 +#define SPMI_CMD_WAKEUP 0x13 +#define SPMI_CMD_AUTHENTICATE 0x14 +#define SPMI_CMD_MSTR_READ 0x15 +#define SPMI_CMD_MSTR_WRITE 0x16 +#define SPMI_CMD_TRANSFER_BUS_OWNERSHIP 0x1A +#define SPMI_CMD_DDB_MASTER_READ 0x1B +#define SPMI_CMD_DDB_SLAVE_READ 0x1C +#define SPMI_CMD_EXT_READ 0x20 +#define SPMI_CMD_EXT_WRITEL 0x30 +#define SPMI_CMD_EXT_READL 0x38 +#define SPMI_CMD_WRITE 0x40 +#define SPMI_CMD_READ 0x60 +#define SPMI_CMD_ZERO_WRITE 0x80 + +/** + * struct spmi_device - Basic representation of an SPMI device + * @dev: Driver model representation of the device. + * @ctrl: SPMI controller managing the bus hosting this device. + * @usid: This devices' Unique Slave IDentifier. + */ +struct spmi_device { + struct device dev; + struct spmi_controller *ctrl; + u8 usid; +}; + +static inline struct spmi_device *to_spmi_device(struct device *d) +{ + return container_of(d, struct spmi_device, dev); +} + +static inline void *spmi_device_get_drvdata(const struct spmi_device *sdev) +{ + return dev_get_drvdata(&sdev->dev); +} + +static inline void spmi_device_set_drvdata(struct spmi_device *sdev, void *data) +{ + dev_set_drvdata(&sdev->dev, data); +} + +struct spmi_device *spmi_device_alloc(struct spmi_controller *ctrl); + +static inline void spmi_device_put(struct spmi_device *sdev) +{ + if (sdev) + put_device(&sdev->dev); +} + +int spmi_device_add(struct spmi_device *sdev); + +void spmi_device_remove(struct spmi_device *sdev); + +/** + * struct spmi_controller - interface to the SPMI master controller + * @dev: Driver model representation of the device. + * @nr: board-specific number identifier for this controller/bus + * @cmd: sends a non-data command sequence on the SPMI bus. + * @read_cmd: sends a register read command sequence on the SPMI bus. + * @write_cmd: sends a register write command sequence on the SPMI bus. + */ +struct spmi_controller { + struct device dev; + unsigned int nr; + int (*cmd)(struct spmi_controller *ctrl, u8 opcode, u8 sid); + int (*read_cmd)(struct spmi_controller *ctrl, u8 opcode, + u8 sid, u16 addr, u8 *buf, size_t len); + int (*write_cmd)(struct spmi_controller *ctrl, u8 opcode, + u8 sid, u16 addr, const u8 *buf, size_t len); +}; + +static inline struct spmi_controller *to_spmi_controller(struct device *d) +{ + return container_of(d, struct spmi_controller, dev); +} + +static inline +void *spmi_controller_get_drvdata(const struct spmi_controller *ctrl) +{ + return dev_get_drvdata(&ctrl->dev); +} + +static inline void spmi_controller_set_drvdata(struct spmi_controller *ctrl, + void *data) +{ + dev_set_drvdata(&ctrl->dev, data); +} + +struct spmi_controller *spmi_controller_alloc(struct device *parent, + size_t size); + +/** + * spmi_controller_put() - decrement controller refcount + * @ctrl SPMI controller. + */ +static inline void spmi_controller_put(struct spmi_controller *ctrl) +{ + if (ctrl) + put_device(&ctrl->dev); +} + +int spmi_controller_add(struct spmi_controller *ctrl); +void spmi_controller_remove(struct spmi_controller *ctrl); + +/** + * struct spmi_driver - SPMI slave device driver + * @driver: SPMI device drivers should initialize name and owner field of + * this structure. + * @probe: binds this driver to a SPMI device. + * @remove: unbinds this driver from the SPMI device. + * @shutdown: standard shutdown callback used during powerdown/halt. + * @suspend: standard suspend callback used during system suspend. + * @resume: standard resume callback used during system resume. + * + * If PM runtime support is desired for a slave, a device driver can call + * pm_runtime_put() from their probe() routine (and a balancing + * pm_runtime_get() in remove()). PM runtime support for a slave is + * implemented by issuing a SLEEP command to the slave on runtime_suspend(), + * transitioning the slave into the SLEEP state. On runtime_resume(), a WAKEUP + * command is sent to the slave to bring it back to ACTIVE. + */ +struct spmi_driver { + struct device_driver driver; + int (*probe)(struct spmi_device *sdev); + void (*remove)(struct spmi_device *sdev); +}; + +static inline struct spmi_driver *to_spmi_driver(struct device_driver *d) +{ + return container_of(d, struct spmi_driver, driver); +} + +int spmi_driver_register(struct spmi_driver *sdrv); + +/** + * spmi_driver_unregister() - unregister an SPMI client driver + * @sdrv: the driver to unregister + */ +static inline void spmi_driver_unregister(struct spmi_driver *sdrv) +{ + if (sdrv) + driver_unregister(&sdrv->driver); +} + +#define module_spmi_driver(__spmi_driver) \ + module_driver(__spmi_driver, spmi_driver_register, \ + spmi_driver_unregister) + +int spmi_register_read(struct spmi_device *sdev, u8 addr, u8 *buf); +int spmi_ext_register_read(struct spmi_device *sdev, u8 addr, u8 *buf, + size_t len); +int spmi_ext_register_readl(struct spmi_device *sdev, u16 addr, u8 *buf, + size_t len); +int spmi_register_write(struct spmi_device *sdev, u8 addr, u8 data); +int spmi_register_zero_write(struct spmi_device *sdev, u8 data); +int spmi_ext_register_write(struct spmi_device *sdev, u8 addr, + const u8 *buf, size_t len); +int spmi_ext_register_writel(struct spmi_device *sdev, u16 addr, + const u8 *buf, size_t len); +int spmi_command_reset(struct spmi_device *sdev); +int spmi_command_sleep(struct spmi_device *sdev); +int spmi_command_wakeup(struct spmi_device *sdev); +int spmi_command_shutdown(struct spmi_device *sdev); + +#endif