diff options
author | Chaitanya Bandi <bandik@nvidia.com> | 2012-10-17 14:58:30 +0530 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2013-09-14 12:48:49 -0700 |
commit | a11f86eed184c84f0d09065c3ef5b751fa8ac919 (patch) | |
tree | a57ddeb93aa0c466cd32255a32c667d0343d4c60 /drivers/mipi_bif | |
parent | 6ccfde531faf507edf1d9d280efbe2fe8b0196f6 (diff) |
mipi_bif: Add mipi_bif core driver
Bug 1022139
Change-Id: I8627ae0f3412b22136f27faf70018d8ebaf04172
Signed-off-by: Chaitanya Bandi <bandik@nvidia.com>
Reviewed-on: http://git-master/r/174393
Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>
Diffstat (limited to 'drivers/mipi_bif')
-rw-r--r-- | drivers/mipi_bif/Kconfig | 7 | ||||
-rw-r--r-- | drivers/mipi_bif/Makefile | 3 | ||||
-rw-r--r-- | drivers/mipi_bif/mipi-bif-core.c | 742 |
3 files changed, 752 insertions, 0 deletions
diff --git a/drivers/mipi_bif/Kconfig b/drivers/mipi_bif/Kconfig new file mode 100644 index 000000000000..581e24f80389 --- /dev/null +++ b/drivers/mipi_bif/Kconfig @@ -0,0 +1,7 @@ +menuconfig MIPI_BIF + tristate "MIPI_BIF support" + depends on HAS_IOMEM + select RT_MUTEXES + ---help--- + Say y here if you want MIPI_BIF support to be + enabled. diff --git a/drivers/mipi_bif/Makefile b/drivers/mipi_bif/Makefile new file mode 100644 index 000000000000..616fd2c4eb3f --- /dev/null +++ b/drivers/mipi_bif/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_MIPI_BIF) += mipi-bif-core.o + +CFLAGS_mipi-bif-core.o := -Wno-deprecated-declarations diff --git a/drivers/mipi_bif/mipi-bif-core.c b/drivers/mipi_bif/mipi-bif-core.c new file mode 100644 index 000000000000..a7efb53b8716 --- /dev/null +++ b/drivers/mipi_bif/mipi-bif-core.c @@ -0,0 +1,742 @@ +/* + * Copyright (c) 2012-2013, NVIDIA CORPORATION. All rights reserved. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/idr.h> +#include <linux/mutex.h> +#include <linux/completion.h> +#include <linux/hardirq.h> +#include <linux/irqflags.h> +#include <linux/mipi-bif.h> + +static DEFINE_MUTEX(core_lock); +static DEFINE_IDR(mipi_bif_adapter_idr); + +int __mipi_bif_first_dynamic_bus_num; + +#ifdef CONFIG_MIPI_BIF_COMPAT +static struct class_compat *mipi_bif_adapter_compat_class; +#endif + +static struct device_type mipi_bif_client_type; + +static ssize_t +show_name(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", dev->type == &mipi_bif_client_type ? + to_mipi_bif_client(dev)->name : to_mipi_bif_adapter(dev)->name); +} + +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + +static struct attribute *mipi_bif_adapter_attrs[] = { + &dev_attr_name.attr, + NULL +}; + +static struct attribute_group mipi_bif_adapter_attr_group = { + .attrs = mipi_bif_adapter_attrs, +}; + +static const struct attribute_group *mipi_bif_adapter_attr_groups[] = { + &mipi_bif_adapter_attr_group, + NULL +}; + +static void mipi_bif_adapter_dev_release(struct device *dev) +{ + struct mipi_bif_adapter *adap = to_mipi_bif_adapter(dev); + complete(&adap->dev_released); +} + +struct device_type mipi_bif_adapter_type = { + .groups = mipi_bif_adapter_attr_groups, + .release = mipi_bif_adapter_dev_release, +}; + +static struct attribute *mipi_bif_client_attrs[] = { + &dev_attr_name.attr, + NULL +}; + +static struct attribute_group mipi_bif_client_attr_group = { + .attrs = mipi_bif_client_attrs, +}; + +static const struct attribute_group *mipi_bif_client_attr_groups[] = { + &mipi_bif_client_attr_group, + NULL +}; + +static void mipi_bif_client_dev_release(struct device *dev) +{ + kfree(to_mipi_bif_client(dev)); +} + +static struct device_type mipi_bif_client_type = { + .groups = mipi_bif_client_attr_groups, + .release = mipi_bif_client_dev_release, +}; + +static const struct mipi_bif_device_id *mipi_bif_match_id( + const struct mipi_bif_device_id *id, + const struct mipi_bif_client *client) +{ + while (id->name[0]) { + if (strcmp(client->name, id->name) == 0) + return id; + id++; + } + return NULL; +} + +static int mipi_bif_device_match(struct device *dev, struct device_driver *drv) +{ + if (dev->type == &mipi_bif_client_type) { + + struct mipi_bif_client *client = to_mipi_bif_client(dev); + struct mipi_bif_driver *driver = to_mipi_bif_driver(drv); + if (driver->id_table) + return mipi_bif_match_id(driver->id_table, + client) != NULL; + } + return 0; +} + +static int mipi_bif_device_probe(struct device *dev) +{ + int status = 0; + if (dev->type == &mipi_bif_client_type) { + + struct mipi_bif_client *client = to_mipi_bif_client(dev); + struct mipi_bif_driver *driver; + + if (!client) + return 0; + + driver = to_mipi_bif_driver(dev->driver); + if (!driver->probe || !driver->id_table) + return -ENODEV; + client->driver = driver; + + dev_dbg(dev, "probe\n"); + + status = driver->probe(client, + mipi_bif_match_id(driver->id_table, client)); + if (status) { + client->driver = NULL; + mipi_bif_set_clientdata(client, NULL); + } + } + return status; +} + +static int mipi_bif_device_remove(struct device *dev) +{ + int status = 0; + if (dev->type == &mipi_bif_client_type) { + struct mipi_bif_client *client = to_mipi_bif_client(dev); + struct mipi_bif_driver *driver; + + if (!client || !dev->driver) + return 0; + + driver = to_mipi_bif_driver(dev->driver); + if (driver->remove) { + dev_dbg(dev, "remove\n"); + status = driver->remove(client); + } else { + dev->driver = NULL; + status = 0; + } + if (status == 0) { + client->driver = NULL; + mipi_bif_set_clientdata(client, NULL); + } + } + return status; +} + +static void mipi_bif_device_shutdown(struct device *dev) +{ + if (dev->type == &mipi_bif_client_type) { + + struct mipi_bif_client *client = to_mipi_bif_client(dev); + struct mipi_bif_driver *driver; + + if (!client || !dev->driver) + return; + driver = to_mipi_bif_driver(dev->driver); + if (driver->shutdown) + driver->shutdown(client); + + } +} +#ifdef CONFIG_PM_SLEEP +static int mipi_bif_legacy_suspend(struct device *dev, pm_message_t mesg) +{ + if (dev->type == &mipi_bif_client_type) { + + struct mipi_bif_client *client = to_mipi_bif_client(dev); + + struct mipi_bif_driver *driver; + + if (!client || !dev->driver) + return 0; + driver = to_mipi_bif_driver(dev->driver); + if (!driver->suspend) + return 0; + return driver->suspend(client, mesg); + + } + return 0; +} + +static int mipi_bif_legacy_resume(struct device *dev) +{ + if (dev->type == &mipi_bif_client_type) { + + struct mipi_bif_client *client = to_mipi_bif_client(dev); + struct mipi_bif_driver *driver; + + if (!client || !dev->driver) + return 0; + driver = to_mipi_bif_driver(dev->driver); + if (!driver->resume) + return 0; + return driver->resume(client); + } + return 0; +} + +static int mipi_bif_device_pm_suspend(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + if (pm) + return pm_generic_suspend(dev); + else + return mipi_bif_legacy_suspend(dev, PMSG_SUSPEND); +} + +static int mipi_bif_device_pm_resume(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + if (pm) + return pm_generic_resume(dev); + else + return mipi_bif_legacy_resume(dev); +} + +static int mipi_bif_device_pm_freeze(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + if (pm) + return pm_generic_freeze(dev); + else + return mipi_bif_legacy_suspend(dev, PMSG_FREEZE); +} + +static int mipi_bif_device_pm_thaw(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + if (pm) + return pm_generic_thaw(dev); + else + return mipi_bif_legacy_resume(dev); +} + +static int mipi_bif_device_pm_poweroff(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + if (pm) + return pm_generic_poweroff(dev); + else + return mipi_bif_legacy_suspend(dev, PMSG_HIBERNATE); +} + +static int mipi_bif_device_pm_restore(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + if (pm) + return pm_generic_restore(dev); + else + return mipi_bif_legacy_resume(dev); +} +#else /* !CONFIG_PM_SLEEP */ +#define mipi_bif_device_pm_suspend NULL +#define mipi_bif_device_pm_resume NULL +#define mipi_bif_device_pm_freeze NULL +#define mipi_bif_device_pm_thaw NULL +#define mipi_bif_device_pm_poweroff NULL +#define mipi_bif_device_pm_restore NULL +#endif /* !CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops mipi_bif_device_pm_ops = { + .suspend = mipi_bif_device_pm_suspend, + .resume = mipi_bif_device_pm_resume, + .freeze = mipi_bif_device_pm_freeze, + .thaw = mipi_bif_device_pm_thaw, + .poweroff = mipi_bif_device_pm_poweroff, + .restore = mipi_bif_device_pm_restore, + SET_RUNTIME_PM_OPS( + pm_generic_runtime_suspend, + pm_generic_runtime_resume, + pm_generic_runtime_idle + ) +}; + +struct bus_type mipi_bif_bus_type = { + .name = "mipi_bif", + .match = mipi_bif_device_match, + .probe = mipi_bif_device_probe, + .remove = mipi_bif_device_remove, + .shutdown = mipi_bif_device_shutdown, + .pm = &mipi_bif_device_pm_ops, +}; +EXPORT_SYMBOL_GPL(mipi_bif_bus_type); + +void mipi_bif_lock_adapter(struct mipi_bif_adapter *adapter) +{ + struct mipi_bif_adapter *parent = + mipi_bif_parent_is_mipi_bif_adapter(adapter); + + if (parent) + mipi_bif_lock_adapter(parent); + else + rt_mutex_lock(&adapter->bus_lock); +} +EXPORT_SYMBOL_GPL(mipi_bif_lock_adapter); + +static int mipi_bif_trylock_adapter(struct mipi_bif_adapter *adapter) +{ + struct mipi_bif_adapter *parent = + mipi_bif_parent_is_mipi_bif_adapter(adapter); + + if (parent) + return mipi_bif_trylock_adapter(parent); + else + return rt_mutex_trylock(&adapter->bus_lock); +} + +void mipi_bif_unlock_adapter(struct mipi_bif_adapter *adapter) +{ + struct mipi_bif_adapter *parent = + mipi_bif_parent_is_mipi_bif_adapter(adapter); + + if (parent) + mipi_bif_unlock_adapter(parent); + else + rt_mutex_unlock(&adapter->bus_lock); +} +EXPORT_SYMBOL_GPL(mipi_bif_unlock_adapter); + +static int mipi_bif_register_adapter(struct mipi_bif_adapter *adap) +{ + int res = 0; + + /* Can't register until after driver model init */ + if (unlikely(WARN_ON(!mipi_bif_bus_type.p))) { + res = -EAGAIN; + goto out_list; + } + + if (unlikely(adap->name[0] == '\0')) { + pr_err("mipi-bif-core:Attempt to add adapter without name\n"); + return -EINVAL; + } + if (unlikely(!adap->algo)) { + pr_err("mipi-bif-core:Attempt to add adapter %s without algo\n", + adap->name); + return -EINVAL; + } + + rt_mutex_init(&adap->bus_lock); + + if (adap->timeout == 0) + adap->timeout = HZ; + + dev_set_name(&adap->dev, "mipi-bif.%d", adap->nr); + adap->dev.bus = &mipi_bif_bus_type; + adap->dev.type = &mipi_bif_adapter_type; + res = device_register(&adap->dev); + if (res) + goto out_list; + + dev_dbg(&adap->dev, "adapter %s registered\n", adap->name); + +#ifdef CONFIG_MIPI_BIF_COMPAT + res = class_compat_create_link(mipi_bif_adapter_compat_class, + &adap->dev, adap->dev.parent); + if (res) + dev_warn(&adap->dev, + "Failed to create compatibility class link\n"); +#endif + + return 0; + +out_list: + mutex_lock(&core_lock); + idr_remove(&mipi_bif_adapter_idr, adap->nr); + mutex_unlock(&core_lock); + return res; +} + +/** + * mipi_bif_add_adapter - declare mipi_bif adapter, use dynamic bus number + * @adapter: the adapter to add + * Context: can sleep + * + * When this returns zero, a new bus number was allocated and stored + * in adap->nr, and the specified adapter became available for clients. + * Otherwise, a negative errno value is returned. + */ +int mipi_bif_add_adapter(struct mipi_bif_adapter *adapter) +{ + int id, res = 0; + +retry: + if (idr_pre_get(&mipi_bif_adapter_idr, GFP_KERNEL) == 0) + return -ENOMEM; + + mutex_lock(&core_lock); + /* "above" here means "above or equal to", sigh */ + res = idr_get_new_above(&mipi_bif_adapter_idr, adapter, + __mipi_bif_first_dynamic_bus_num, &id); + mutex_unlock(&core_lock); + + if (res < 0) { + if (res == -EAGAIN) + goto retry; + return res; + } + + adapter->nr = id; + return mipi_bif_register_adapter(adapter); +} +EXPORT_SYMBOL_GPL(mipi_bif_add_adapter); + +/* + * mipi_bif_add_numbered_adapter - declare mipi_bif adapter with static bus num + * @adap: the adapter to register (with adap->nr initialized) + * Context: can sleep + */ +int mipi_bif_add_numbered_adapter(struct mipi_bif_adapter *adap) +{ + int id; + int status; + + if (adap->nr == -1) /* -1 means dynamically assign bus id */ + return mipi_bif_add_adapter(adap); + if (adap->nr & ~MAX_IDR_MASK) + return -EINVAL; + +retry: + if (idr_pre_get(&mipi_bif_adapter_idr, GFP_KERNEL) == 0) + return -ENOMEM; + + mutex_lock(&core_lock); + + status = idr_get_new_above(&mipi_bif_adapter_idr, adap, adap->nr, &id); + if (status == 0 && id != adap->nr) { + status = -EBUSY; + idr_remove(&mipi_bif_adapter_idr, id); + } + mutex_unlock(&core_lock); + if (status == -EAGAIN) + goto retry; + + if (status == 0) + status = mipi_bif_register_adapter(adap); + + return status; +} +EXPORT_SYMBOL_GPL(mipi_bif_add_numbered_adapter); + +void mipi_bif_unregister_device(struct mipi_bif_client *client) +{ + device_unregister(&client->dev); +} + +static int mipi_bif_do_del_adapter(struct mipi_bif_driver *driver, + struct mipi_bif_adapter *adapter) +{ + int res; + if (!driver->detach_adapter) + return 0; + dev_warn(&adapter->dev, "%s: detach_adapter method is deprecated\n", + driver->driver.name); + + res = driver->detach_adapter(adapter); + if (res) + dev_err(&adapter->dev, "detach_adapter failed (%d)"\ + "for driver %s\n", res, driver->driver.name); + return res; +} + +static int __process_removed_adapter(struct device_driver *d, void *data) +{ + return mipi_bif_do_del_adapter(to_mipi_bif_driver(d), data); +} + +int mipi_bif_del_adapter(struct mipi_bif_adapter *adap) +{ + int res = 0; + struct mipi_bif_adapter *found; + + /* First make sure that this adapter was ever added */ + mutex_lock(&core_lock); + found = idr_find(&mipi_bif_adapter_idr, adap->nr); + mutex_unlock(&core_lock); + if (found != adap) { + pr_debug("mipi-bif-core: attempting to delete unregistered "\ + "adapter %s\n", adap->name); + return -EINVAL; + } + + /* Tell drivers about this removal */ + mutex_lock(&core_lock); + res = bus_for_each_drv(&mipi_bif_bus_type, NULL, adap, + __process_removed_adapter); + mutex_unlock(&core_lock); + if (res) + return res; + +#ifdef CONFIG_MIPI_BIF_COMPAT + class_compat_remove_link(mipi_bif_adapter_compat_class, &adap->dev, + adap->dev.parent); +#endif + dev_dbg(&adap->dev, "adapter %s unregistered\n", adap->name); + + init_completion(&adap->dev_released); + device_unregister(&adap->dev); + /* wait for sysfs to drop all references */ + wait_for_completion(&adap->dev_released); + + /* free bus id */ + mutex_lock(&core_lock); + idr_remove(&mipi_bif_adapter_idr, adap->nr); + mutex_unlock(&core_lock); + + memset(&adap->dev, 0, sizeof(adap->dev)); + + return 0; +} +EXPORT_SYMBOL_GPL(mipi_bif_del_adapter); + +struct mipi_bif_client * + mipi_bif_new_device(struct mipi_bif_adapter *adap, + struct mipi_bif_board_info const *info) +{ + struct mipi_bif_client *client; + int status; + + client = kzalloc(sizeof *client, GFP_KERNEL); + if (!client) + return NULL; + + client->adapter = adap; + client->addr = info->addr; + client->dev.platform_data = info->platform_data; + + if (info->archdata) + client->dev.archdata = *info->archdata; + + strlcpy(client->name, info->type, sizeof(client->name)); + + client->dev.parent = &client->adapter->dev; + client->dev.bus = &mipi_bif_bus_type; + client->dev.type = &mipi_bif_client_type; + + dev_set_name(&client->dev, "%d-%04x", + mipi_bif_adapter_id(adap), client->addr); + + status = device_register(&client->dev); + if (status) + goto out_err; + + dev_dbg(&adap->dev, "client %s registered with bus id %s\n", + client->name, dev_name(&client->dev)); + + return client; + +out_err: + dev_err(&adap->dev, "Failed to register mipi_bif client %s at 0x%02x "\ + "(%d)\n", client->name, client->addr, status); + kfree(client); + return NULL; +} +EXPORT_SYMBOL_GPL(mipi_bif_new_device); + +int mipi_bif_register_driver(struct module *owner, + struct mipi_bif_driver *driver) +{ + int res; + + /* Can't register until after driver model init */ + if (unlikely(WARN_ON(!mipi_bif_bus_type.p))) + return -EAGAIN; + + /* add the driver to the list of mipi_bif drivers in the driver core */ + driver->driver.owner = owner; + driver->driver.bus = &mipi_bif_bus_type; + + /* When registration returns, the driver core + * will have called probe() for all matching-but-unbound devices. + */ + res = driver_register(&driver->driver); + if (res) + return res; + + /* Drivers should switch to dev_pm_ops instead. */ + if (driver->suspend) + pr_warn("mipi-bif-core: driver %s using legacy suspend\n", + driver->driver.name); + if (driver->resume) + pr_warn("mipi-bif-core: driver %s using legacy resume\n", + driver->driver.name); + + pr_debug("mipi-bif-core: driver %s registered\n", driver->driver.name); + + INIT_LIST_HEAD(&driver->clients); + return 0; +} +EXPORT_SYMBOL_GPL(mipi_bif_register_driver); + +int mipi_bif_for_each_dev(void *data, int (*fn)(struct device *, void *)) +{ + int res; + + mutex_lock(&core_lock); + res = bus_for_each_dev(&mipi_bif_bus_type, NULL, data, fn); + mutex_unlock(&core_lock); + + return res; +} + +static int __process_removed_driver(struct device *dev, void *data) +{ + if (dev->type != &mipi_bif_adapter_type) + return 0; + return mipi_bif_do_del_adapter(data, to_mipi_bif_adapter(dev)); +} + +void mipi_bif_del_driver(struct mipi_bif_driver *driver) +{ + mipi_bif_for_each_dev(driver, __process_removed_driver); + + driver_unregister(&driver->driver); + pr_debug("mipi-bif-core:driver %s unregistered\n", driver->driver.name); +} +EXPORT_SYMBOL_GPL(mipi_bif_del_driver); + +struct mipi_bif_adapter *mipi_bif_get_adapter(int nr) +{ + struct mipi_bif_adapter *adapter; + + mutex_lock(&core_lock); + adapter = idr_find(&mipi_bif_adapter_idr, nr); + if (adapter && !try_module_get(adapter->owner)) + adapter = NULL; + + mutex_unlock(&core_lock); + return adapter; +} +EXPORT_SYMBOL_GPL(mipi_bif_get_adapter); + +int mipi_bif_transfer(struct mipi_bif_adapter *adap, struct mipi_bif_msg *msg) +{ + unsigned long orig_jiffies; + int ret, try; + + if (adap->algo->master_xfer) { + + if (in_atomic() || irqs_disabled()) { + ret = mipi_bif_trylock_adapter(adap); + if (!ret) + /* mipi_bif activity is ongoing. */ + return -EAGAIN; + } else { + mipi_bif_lock_adapter(adap); + } + + /* Retry automatically on arbitration loss */ + orig_jiffies = jiffies; + for (ret = 0, try = 0; try <= adap->retries; try++) { + ret = adap->algo->master_xfer(adap, msg); + if (ret != -EAGAIN) + break; + if (time_after(jiffies, orig_jiffies + adap->timeout)) + break; + } + mipi_bif_unlock_adapter(adap); + + return ret; + } else { + dev_dbg(&adap->dev, "mipi_bif level transfers not supported\n"); + return -EOPNOTSUPP; + } +} +EXPORT_SYMBOL_GPL(mipi_bif_transfer); + +static int __init mipi_bif_init(void) +{ + int retval; + + retval = bus_register(&mipi_bif_bus_type); + if (retval) + return retval; +#ifdef CONFIG_MIPI_BIF_COMPAT + mipi_bif_adapter_compat_class = + class_compat_register("mipi_bif_adapter"); + if (!mipi_bif_adapter_compat_class) { + retval = -ENOMEM; + goto bus_err; + } +#endif + return 0; + +#ifdef CONFIG_MIPI_BIF_COMPAT + class_compat_unregister(mipi_bif_adapter_compat_class); +bus_err: +#endif + bus_unregister(&mipi_bif_bus_type); + return retval; +} + +static void __exit mipi_bif_exit(void) +{ +#ifdef CONFIG_MIPI_BIF_COMPAT + class_compat_unregister(mipi_bif_adapter_compat_class); +#endif + bus_unregister(&mipi_bif_bus_type); +} + +postcore_initcall(mipi_bif_init); +module_exit(mipi_bif_exit); + +MODULE_AUTHOR("Chaitanya Bandi<bandik@nvidia.com>"); +MODULE_DESCRIPTION("MIPI BIF core driver module"); +MODULE_LICENSE("GPL"); |