/* * drivers/extcon/devres.c - EXTCON device's resource management * * Copyright (C) 2016 Samsung Electronics * Author: Chanwoo Choi * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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 static int devm_extcon_dev_match(struct device *dev, void *res, void *data) { struct extcon_dev **r = res; if (WARN_ON(!r || !*r)) return 0; return *r == data; } static void devm_extcon_dev_release(struct device *dev, void *res) { extcon_dev_free(*(struct extcon_dev **)res); } static void devm_extcon_dev_unreg(struct device *dev, void *res) { extcon_dev_unregister(*(struct extcon_dev **)res); } struct extcon_dev_notifier_devres { struct extcon_dev *edev; unsigned int id; struct notifier_block *nb; }; static void devm_extcon_dev_notifier_unreg(struct device *dev, void *res) { struct extcon_dev_notifier_devres *this = res; extcon_unregister_notifier(this->edev, this->id, this->nb); } /** * devm_extcon_dev_allocate - Allocate managed extcon device * @dev: device owning the extcon device being created * @supported_cable: Array of supported extcon ending with EXTCON_NONE. * If supported_cable is NULL, cable name related APIs * are disabled. * * This function manages automatically the memory of extcon device using device * resource management and simplify the control of freeing the memory of extcon * device. * * Returns the pointer memory of allocated extcon_dev if success * or ERR_PTR(err) if fail */ struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, const unsigned int *supported_cable) { struct extcon_dev **ptr, *edev; ptr = devres_alloc(devm_extcon_dev_release, sizeof(*ptr), GFP_KERNEL); if (!ptr) return ERR_PTR(-ENOMEM); edev = extcon_dev_allocate(supported_cable); if (IS_ERR(edev)) { devres_free(ptr); return edev; } edev->dev.parent = dev; *ptr = edev; devres_add(dev, ptr); return edev; } EXPORT_SYMBOL_GPL(devm_extcon_dev_allocate); /** * devm_extcon_dev_free() - Resource-managed extcon_dev_unregister() * @dev: device the extcon belongs to * @edev: the extcon device to unregister * * Free the memory that is allocated with devm_extcon_dev_allocate() * function. */ void devm_extcon_dev_free(struct device *dev, struct extcon_dev *edev) { WARN_ON(devres_release(dev, devm_extcon_dev_release, devm_extcon_dev_match, edev)); } EXPORT_SYMBOL_GPL(devm_extcon_dev_free); /** * devm_extcon_dev_register() - Resource-managed extcon_dev_register() * @dev: device to allocate extcon device * @edev: the new extcon device to register * * Managed extcon_dev_register() function. If extcon device is attached with * this function, that extcon device is automatically unregistered on driver * detach. Internally this function calls extcon_dev_register() function. * To get more information, refer that function. * * If extcon device is registered with this function and the device needs to be * unregistered separately, devm_extcon_dev_unregister() should be used. * * Returns 0 if success or negaive error number if failure. */ int devm_extcon_dev_register(struct device *dev, struct extcon_dev *edev) { struct extcon_dev **ptr; int ret; ptr = devres_alloc(devm_extcon_dev_unreg, sizeof(*ptr), GFP_KERNEL); if (!ptr) return -ENOMEM; ret = extcon_dev_register(edev); if (ret) { devres_free(ptr); return ret; } *ptr = edev; devres_add(dev, ptr); return 0; } EXPORT_SYMBOL_GPL(devm_extcon_dev_register); /** * devm_extcon_dev_unregister() - Resource-managed extcon_dev_unregister() * @dev: device the extcon belongs to * @edev: the extcon device to unregister * * Unregister extcon device that is registered with devm_extcon_dev_register() * function. */ void devm_extcon_dev_unregister(struct device *dev, struct extcon_dev *edev) { WARN_ON(devres_release(dev, devm_extcon_dev_unreg, devm_extcon_dev_match, edev)); } EXPORT_SYMBOL_GPL(devm_extcon_dev_unregister); /** * devm_extcon_register_notifier() - Resource-managed extcon_register_notifier() * @dev: device to allocate extcon device * @edev: the extcon device that has the external connecotr. * @id: the unique id of each external connector in extcon enumeration. * @nb: a notifier block to be registered. * * This function manages automatically the notifier of extcon device using * device resource management and simplify the control of unregistering * the notifier of extcon device. * * Note that the second parameter given to the callback of nb (val) is * "old_state", not the current state. The current state can be retrieved * by looking at the third pameter (edev pointer)'s state value. * * Returns 0 if success or negaive error number if failure. */ int devm_extcon_register_notifier(struct device *dev, struct extcon_dev *edev, unsigned int id, struct notifier_block *nb) { struct extcon_dev_notifier_devres *ptr; int ret; ptr = devres_alloc(devm_extcon_dev_notifier_unreg, sizeof(*ptr), GFP_KERNEL); if (!ptr) return -ENOMEM; ret = extcon_register_notifier(edev, id, nb); if (ret) { devres_free(ptr); return ret; } ptr->edev = edev; ptr->id = id; ptr->nb = nb; devres_add(dev, ptr); return 0; } EXPORT_SYMBOL(devm_extcon_register_notifier); /** * devm_extcon_unregister_notifier() - Resource-managed extcon_unregister_notifier() * @dev: device to allocate extcon device * @edev: the extcon device that has the external connecotr. * @id: the unique id of each external connector in extcon enumeration. * @nb: a notifier block to be registered. */ void devm_extcon_unregister_notifier(struct device *dev, struct extcon_dev *edev, unsigned int id, struct notifier_block *nb) { WARN_ON(devres_release(dev, devm_extcon_dev_notifier_unreg, devm_extcon_dev_match, edev)); } EXPORT_SYMBOL(devm_extcon_unregister_notifier);