From 42120dccbab016dff3bf2b9f87c1f6a8e3e5b539 Mon Sep 17 00:00:00 2001 From: Kenneth Heitke Date: Mon, 29 Apr 2013 16:00:59 -0600 Subject: [PATCH] spmi: Add del_controller functionality The del_controller function is required to release any client device resources as well as the debugfs entries. This patch adds that functionality. Change-Id: I91c6b840acfe002f096570cbe1867b302c475e60 Signed-off-by: Kenneth Heitke --- drivers/spmi/spmi-dbgfs.c | 40 +++++++++++++++++++-- drivers/spmi/spmi-dbgfs.h | 4 ++- drivers/spmi/spmi.c | 73 ++++++++++++++++++++++++++++++++++----- include/linux/spmi.h | 1 + 4 files changed, 106 insertions(+), 12 deletions(-) diff --git a/drivers/spmi/spmi-dbgfs.c b/drivers/spmi/spmi-dbgfs.c index b825adea677..27df09333ea 100644 --- a/drivers/spmi/spmi-dbgfs.c +++ b/drivers/spmi/spmi-dbgfs.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012, The Linux Foundation. All rights reserved. +/* 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 @@ -24,7 +24,6 @@ * /spmi-# */ -#define DEBUG #define pr_fmt(fmt) "%s:%d: " fmt, __func__, __LINE__ #include @@ -582,7 +581,7 @@ static struct dentry *spmi_dfs_create_fs(void) { struct dentry *root, *file; - pr_debug("Creating SPMI debugfs file-system at\n"); + pr_debug("Creating SPMI debugfs file-system\n"); root = debugfs_create_dir(DFS_ROOT_NAME, NULL); if (IS_ERR(root)) { pr_err("Error creating top level directory err:%ld", @@ -696,6 +695,41 @@ err_create_dir_failed: return -ENOMEM; } +/* + * spmi_dfs_del_controller: deletes spmi controller entry + * @return zero on success + */ +int spmi_dfs_del_controller(struct spmi_controller *ctrl) +{ + int rc; + struct list_head *pos, *tmp; + struct spmi_ctrl_data *ctrl_data; + + pr_debug("Deleting controller %s\n", ctrl->dev.kobj.name); + + rc = mutex_lock_interruptible(&dbgfs_data.lock); + if (rc) + return rc; + + list_for_each_safe(pos, tmp, &dbgfs_data.ctrl) { + ctrl_data = list_entry(pos, struct spmi_ctrl_data, node); + + if (ctrl_data->ctrl == ctrl) { + debugfs_remove_recursive(ctrl_data->dir); + list_del(pos); + kfree(ctrl_data); + rc = 0; + goto done; + } + } + rc = -EINVAL; + pr_debug("Unknown controller %s\n", ctrl->dev.kobj.name); + +done: + mutex_unlock(&dbgfs_data.lock); + return rc; +} + /* * spmi_dfs_create_file: creates a new file in the SPMI debugfs * @returns valid dentry pointer on success or NULL diff --git a/drivers/spmi/spmi-dbgfs.h b/drivers/spmi/spmi-dbgfs.h index 2a0d815fec5..10e98b9eddb 100644 --- a/drivers/spmi/spmi-dbgfs.h +++ b/drivers/spmi/spmi-dbgfs.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2012, The Linux Foundation. All rights reserved. +/* 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 @@ -16,8 +16,10 @@ #ifdef CONFIG_DEBUG_FS int spmi_dfs_add_controller(struct spmi_controller *ctrl); +int spmi_dfs_del_controller(struct spmi_controller *ctrl); #else static int spmi_dfs_add_controller(struct spmi_controller *ctrl) { return 0; } +static int spmi_dfs_del_controller(struct spmi_controller *ctrl) { return 0; } #endif struct dentry *spmi_dfs_create_file(struct spmi_controller *ctrl, diff --git a/drivers/spmi/spmi.c b/drivers/spmi/spmi.c index f5c9d2f9b22..fc21fbb9fec 100644 --- a/drivers/spmi/spmi.c +++ b/drivers/spmi/spmi.c @@ -32,9 +32,8 @@ struct spmii_boardinfo { static DEFINE_MUTEX(board_lock); static LIST_HEAD(board_list); static DEFINE_IDR(ctrl_idr); -static struct device_type spmi_ctrl_type = { 0 }; - -#define to_spmi(dev) platform_get_drvdata(to_platform_device(dev)) +static struct device_type spmi_dev_type; +static struct device_type spmi_ctrl_type; /* Forward declarations */ struct bus_type spmi_bus_type; @@ -69,6 +68,9 @@ int spmi_add_controller(struct spmi_controller *ctrl) int id; int status; + if (!ctrl) + return -EINVAL; + pr_debug("adding controller for bus %d (0x%p)\n", ctrl->nr, ctrl); if (ctrl->nr & ~MAX_ID_MASK) { @@ -98,18 +100,70 @@ retry: } EXPORT_SYMBOL_GPL(spmi_add_controller); +/* 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); + struct spmi_controller *ctrl = data; + + if (dev->type == &spmi_dev_type && spmidev->ctrl == ctrl) + spmi_remove_device(spmidev); + + return 0; +} + /** * spmi_del_controller: Controller tear-down. - * @ctrl: controller to which this device is to be added to. + * @ctrl: controller to be removed. * * Controller added with the above API is torn down using this API. */ int spmi_del_controller(struct spmi_controller *ctrl) { - return -ENXIO; + struct spmi_controller *found; + + if (!ctrl) + return -EINVAL; + + /* Check that the ctrl has been added */ + mutex_lock(&board_lock); + found = idr_find(&ctrl_idr, ctrl->nr); + mutex_unlock(&board_lock); + if (found != ctrl) + return -EINVAL; + + /* Remove all the clients associated with this controller */ + mutex_lock(&board_lock); + bus_for_each_dev(&spmi_bus_type, NULL, ctrl, spmi_ctrl_remove_device); + mutex_unlock(&board_lock); + + spmi_dfs_del_controller(ctrl); + + mutex_lock(&board_lock); + idr_remove(&ctrl_idr, ctrl->nr); + mutex_unlock(&board_lock); + + init_completion(&ctrl->dev_released); + device_unregister(&ctrl->dev); + wait_for_completion(&ctrl->dev_released); + + return 0; } EXPORT_SYMBOL_GPL(spmi_del_controller); +#define spmi_ctrl_attr_gr NULL +static void spmi_ctrl_release(struct device *dev) +{ + struct spmi_controller *ctrl = to_spmi_controller(dev); + + complete(&ctrl->dev_released); +} + +static struct device_type spmi_ctrl_type = { + .groups = spmi_ctrl_attr_gr, + .release = spmi_ctrl_release, +}; + #define spmi_device_attr_gr NULL #define spmi_device_uevent NULL static void spmi_dev_release(struct device *dev) @@ -141,7 +195,7 @@ struct spmi_device *spmi_alloc_device(struct spmi_controller *ctrl) { struct spmi_device *spmidev; - if (!ctrl) { + if (!ctrl || !spmi_busnum_to_ctrl(ctrl->nr)) { pr_err("Missing SPMI controller\n"); return NULL; } @@ -174,12 +228,15 @@ static struct device *get_valid_device(struct spmi_device *spmidev) if (dev->bus != &spmi_bus_type || dev->type != &spmi_dev_type) return NULL; + if (!spmidev->ctrl || !spmi_busnum_to_ctrl(spmidev->ctrl->nr)) + return NULL; + return dev; } /** * spmi_add_device: Add a new device without register board info. - * @ctrl: controller to which this device is to be added to. + * @spmi_dev: spmi_device to be added (registered). * * Called when device doesn't have an explicit client-driver to be probed, or * the client-driver is a module installed dynamically. @@ -190,7 +247,7 @@ int spmi_add_device(struct spmi_device *spmidev) struct device *dev = get_valid_device(spmidev); if (!dev) { - pr_err("%s: invalid SPMI device\n", __func__); + pr_err("invalid SPMI device\n"); return -EINVAL; } diff --git a/include/linux/spmi.h b/include/linux/spmi.h index 25d937a720a..e8e932eb066 100644 --- a/include/linux/spmi.h +++ b/include/linux/spmi.h @@ -54,6 +54,7 @@ struct spmi_device; struct spmi_controller { struct device dev; unsigned int nr; + struct completion dev_released; int (*cmd)(struct spmi_controller *, u8 opcode, u8 sid); int (*read_cmd)(struct spmi_controller *, u8 opcode, u8 sid, u16 addr, u8 bc, u8 *buf);