diff --git a/drivers/spmi/spmi-devres.c b/drivers/spmi/spmi-devres.c index 62c4b3f24d06..c3e889fe1b6e 100644 --- a/drivers/spmi/spmi-devres.c +++ b/drivers/spmi/spmi-devres.c @@ -60,5 +60,29 @@ int devm_spmi_controller_add(struct device *parent, struct spmi_controller *ctrl } EXPORT_SYMBOL_GPL(devm_spmi_controller_add); +static void devm_spmi_subdevice_remove(void *sub_sdev) +{ + spmi_subdevice_remove(sub_sdev); +} + +struct spmi_subdevice *devm_spmi_subdevice_alloc_and_add(struct device *dev, + struct spmi_device *sparent) +{ + struct spmi_subdevice *sub_sdev; + int ret; + + sub_sdev = spmi_subdevice_alloc_and_add(sparent); + if (IS_ERR(sub_sdev)) + return sub_sdev; + + ret = devm_add_action_or_reset(dev, devm_spmi_subdevice_remove, sub_sdev); + if (ret) + return ERR_PTR(ret); + + return sub_sdev; +} +EXPORT_SYMBOL_NS_GPL(devm_spmi_subdevice_alloc_and_add, "SPMI"); + MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("SPMI devres helpers"); +MODULE_IMPORT_NS("SPMI"); diff --git a/drivers/spmi/spmi.c b/drivers/spmi/spmi.c index ba4a980a35eb..bafb4c3e6c17 100644 --- a/drivers/spmi/spmi.c +++ b/drivers/spmi/spmi.c @@ -19,6 +19,7 @@ static bool is_registered; static DEFINE_IDA(ctrl_ida); +static DEFINE_IDA(spmi_subdevice_ida); static void spmi_dev_release(struct device *dev) { @@ -32,6 +33,19 @@ static const struct device_type spmi_dev_type = { .release = spmi_dev_release, }; +static void spmi_subdev_release(struct device *dev) +{ + struct spmi_device *sdev = to_spmi_device(dev); + struct spmi_subdevice *sub_sdev = container_of(sdev, struct spmi_subdevice, sdev); + + ida_free(&spmi_subdevice_ida, sub_sdev->devid); + kfree(sub_sdev); +} + +static const struct device_type spmi_subdev_type = { + .release = spmi_subdev_release, +}; + static void spmi_ctrl_release(struct device *dev) { struct spmi_controller *ctrl = to_spmi_controller(dev); @@ -88,6 +102,18 @@ void spmi_device_remove(struct spmi_device *sdev) } EXPORT_SYMBOL_GPL(spmi_device_remove); +/** + * spmi_subdevice_remove() - Remove an SPMI subdevice + * @sub_sdev: spmi_device to be removed + */ +void spmi_subdevice_remove(struct spmi_subdevice *sub_sdev) +{ + struct spmi_device *sdev = &sub_sdev->sdev; + + device_unregister(&sdev->dev); +} +EXPORT_SYMBOL_NS_GPL(spmi_subdevice_remove, "SPMI"); + static inline int spmi_cmd(struct spmi_controller *ctrl, u8 opcode, u8 sid) { @@ -429,6 +455,58 @@ struct spmi_device *spmi_device_alloc(struct spmi_controller *ctrl) } EXPORT_SYMBOL_GPL(spmi_device_alloc); +/** + * spmi_subdevice_alloc_and_add(): Allocate and add a new SPMI sub-device + * @sparent: SPMI parent device with previously registered SPMI controller + * + * Returns: + * Pointer to newly allocated SPMI sub-device for success or error pointer. + */ +struct spmi_subdevice *spmi_subdevice_alloc_and_add(struct spmi_device *sparent) +{ + struct spmi_subdevice *sub_sdev; + struct spmi_device *sdev; + int ret; + + sub_sdev = kzalloc_obj(*sub_sdev); + if (!sub_sdev) + return ERR_PTR(-ENOMEM); + + sdev = &sub_sdev->sdev; + sdev->ctrl = sparent->ctrl; + sdev->usid = sparent->usid; + + ret = ida_alloc(&spmi_subdevice_ida, GFP_KERNEL); + if (ret < 0) { + kfree(sub_sdev); + return ERR_PTR(ret); + } + sub_sdev->devid = ret; + + device_initialize(&sdev->dev); + sdev->dev.parent = &sparent->dev; + sdev->dev.bus = &spmi_bus_type; + sdev->dev.type = &spmi_subdev_type; + + ret = dev_set_name(&sdev->dev, "%u-%02x.%u.auto", + sdev->ctrl->nr, sdev->usid, sub_sdev->devid); + if (ret) + goto err_put_dev; + + ret = device_add(&sdev->dev); + if (ret) { + dev_err(&sdev->dev, "Can't add device, status %pe\n", ERR_PTR(ret)); + goto err_put_dev; + } + + return sub_sdev; + +err_put_dev: + put_device(&sdev->dev); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_NS_GPL(spmi_subdevice_alloc_and_add, "SPMI"); + /** * spmi_controller_alloc() - Allocate a new SPMI controller * @parent: parent device diff --git a/include/linux/spmi.h b/include/linux/spmi.h index 4eb9564a7fb3..a78a8924b2ac 100644 --- a/include/linux/spmi.h +++ b/include/linux/spmi.h @@ -69,6 +69,22 @@ int spmi_device_add(struct spmi_device *sdev); void spmi_device_remove(struct spmi_device *sdev); +/** + * struct spmi_subdevice - Basic representation of an SPMI sub-device + * @sdev: Sub-device representation of an SPMI device + * @devid: Platform Device ID of an SPMI sub-device + */ +struct spmi_subdevice { + struct spmi_device sdev; + unsigned int devid; +}; + +struct spmi_subdevice *spmi_subdevice_alloc_and_add(struct spmi_device *sparent); +void spmi_subdevice_remove(struct spmi_subdevice *sdev); + +struct spmi_subdevice *devm_spmi_subdevice_alloc_and_add(struct device *dev, + struct spmi_device *sparent); + /** * struct spmi_controller - interface to the SPMI master controller * @dev: Driver model representation of the device.