// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #define CREATE_TRACE_POINTS #include static bool is_registered; 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_free(&ctrl_ida, ctrl->nr); kfree(ctrl); } static const struct device_type spmi_ctrl_type = { .release = spmi_ctrl_release, }; 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) { int ret; if (!ctrl || !ctrl->cmd || ctrl->dev.type != &spmi_ctrl_type) return -EINVAL; ret = ctrl->cmd(ctrl, opcode, sid); trace_spmi_cmd(opcode, sid, ret); return ret; } static inline int spmi_read_cmd(struct spmi_controller *ctrl, u8 opcode, u8 sid, u16 addr, u8 *buf, size_t len) { int ret; if (!ctrl || !ctrl->read_cmd || ctrl->dev.type != &spmi_ctrl_type) return -EINVAL; trace_spmi_read_begin(opcode, sid, addr); ret = ctrl->read_cmd(ctrl, opcode, sid, addr, buf, len); trace_spmi_read_end(opcode, sid, addr, ret, len, buf); return ret; } static inline int spmi_write_cmd(struct spmi_controller *ctrl, u8 opcode, u8 sid, u16 addr, const u8 *buf, size_t len) { int ret; if (!ctrl || !ctrl->write_cmd || ctrl->dev.type != &spmi_ctrl_type) return -EINVAL; trace_spmi_write_begin(opcode, sid, addr, len, buf); ret = ctrl->write_cmd(ctrl, opcode, sid, addr, buf, len); trace_spmi_write_end(opcode, sid, addr, ret); return ret; } /** * 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; 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); return err; } static void spmi_drv_remove(struct device *dev) { const struct spmi_driver *sdrv = to_spmi_driver(dev->driver); pm_runtime_get_sync(dev); if (sdrv->remove) 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); } static void spmi_drv_shutdown(struct device *dev) { const struct spmi_driver *sdrv = to_spmi_driver(dev->driver); if (sdrv && sdrv->shutdown) sdrv->shutdown(to_spmi_device(dev)); } static int spmi_drv_uevent(const struct device *dev, struct kobj_uevent_env *env) { int ret; ret = of_device_uevent_modalias(dev, env); if (ret != -ENODEV) return ret; return 0; } static struct bus_type spmi_bus_type = { .name = "spmi", .match = spmi_device_match, .probe = spmi_drv_probe, .remove = spmi_drv_remove, .shutdown = spmi_drv_shutdown, .uevent = spmi_drv_uevent, }; /** * spmi_device_from_of() - get the associated SPMI device from a device node * * @np: device node * * Returns the struct spmi_device associated with a device node or NULL. */ struct spmi_device *spmi_device_from_of(struct device_node *np) { struct device *dev = bus_find_device_by_of_node(&spmi_bus_type, np); if (dev) return to_spmi_device(dev); return NULL; } EXPORT_SYMBOL_GPL(spmi_device_from_of); /** * 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_alloc(&ctrl_ida, 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 %pOF\n", node); err = of_property_read_u32_array(node, "reg", reg, 2); if (err) { dev_err(&ctrl->dev, "node %pOF err (%d) does not have 'reg' property\n", node, err); continue; } if (reg[1] != SPMI_USID) { dev_err(&ctrl->dev, "node %pOF contains unsupported 'reg' entry\n", node); continue; } if (reg[0] >= SPMI_MAX_SLAVE_ID) { dev_err(&ctrl->dev, "invalid usid on node %pOF\n", node); 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(!is_registered)) 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) { if (!ctrl) return; 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, struct module *owner) { sdrv->driver.bus = &spmi_bus_type; sdrv->driver.owner = owner; 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) { int ret; ret = bus_register(&spmi_bus_type); if (ret) return ret; is_registered = true; return 0; } postcore_initcall(spmi_init); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("SPMI module"); MODULE_ALIAS("platform:spmi");