// SPDX-License-Identifier: GPL-2.0-only // Copyright (c) 2018-2021 Intel Corporation #include #include #include #include #include #include #include #include #include #include #include "internal.h" static DEFINE_IDA(peci_controller_ida); static void peci_controller_dev_release(struct device *dev) { struct peci_controller *controller = to_peci_controller(dev); mutex_destroy(&controller->bus_lock); ida_free(&peci_controller_ida, controller->id); kfree(controller); } struct device_type peci_controller_type = { .release = peci_controller_dev_release, }; int peci_controller_scan_devices(struct peci_controller *controller) { int ret; u8 addr; for (addr = PECI_BASE_ADDR; addr < PECI_BASE_ADDR + PECI_DEVICE_NUM_MAX; addr++) { ret = peci_device_create(controller, addr); if (ret) return ret; } return 0; } static struct peci_controller *peci_controller_alloc(struct device *dev, const struct peci_controller_ops *ops) { struct peci_controller *controller; int ret; if (!ops->xfer) return ERR_PTR(-EINVAL); controller = kzalloc(sizeof(*controller), GFP_KERNEL); if (!controller) return ERR_PTR(-ENOMEM); ret = ida_alloc_max(&peci_controller_ida, U8_MAX, GFP_KERNEL); if (ret < 0) goto err; controller->id = ret; controller->ops = ops; controller->dev.parent = dev; controller->dev.bus = &peci_bus_type; controller->dev.type = &peci_controller_type; device_initialize(&controller->dev); mutex_init(&controller->bus_lock); return controller; err: kfree(controller); return ERR_PTR(ret); } static int unregister_child(struct device *dev, void *dummy) { peci_device_destroy(to_peci_device(dev)); return 0; } static void unregister_controller(void *_controller) { struct peci_controller *controller = _controller; /* * Detach any active PECI devices. This can't fail, thus we do not * check the returned value. */ device_for_each_child_reverse(&controller->dev, NULL, unregister_child); device_unregister(&controller->dev); fwnode_handle_put(controller->dev.fwnode); pm_runtime_disable(&controller->dev); } /** * devm_peci_controller_add() - add PECI controller * @dev: device for devm operations * @ops: pointer to controller specific methods * * In final stage of its probe(), peci_controller driver calls * devm_peci_controller_add() to register itself with the PECI bus. * * Return: Pointer to the newly allocated controller or ERR_PTR() in case of failure. */ struct peci_controller *devm_peci_controller_add(struct device *dev, const struct peci_controller_ops *ops) { struct peci_controller *controller; int ret; controller = peci_controller_alloc(dev, ops); if (IS_ERR(controller)) return controller; ret = dev_set_name(&controller->dev, "peci-%d", controller->id); if (ret) goto err_put; pm_runtime_no_callbacks(&controller->dev); pm_suspend_ignore_children(&controller->dev, true); pm_runtime_enable(&controller->dev); device_set_node(&controller->dev, fwnode_handle_get(dev_fwnode(dev))); ret = device_add(&controller->dev); if (ret) goto err_fwnode; ret = devm_add_action_or_reset(dev, unregister_controller, controller); if (ret) return ERR_PTR(ret); /* * Ignoring retval since failures during scan are non-critical for * controller itself. */ peci_controller_scan_devices(controller); return controller; err_fwnode: fwnode_handle_put(controller->dev.fwnode); pm_runtime_disable(&controller->dev); err_put: put_device(&controller->dev); return ERR_PTR(ret); } EXPORT_SYMBOL_NS_GPL(devm_peci_controller_add, PECI); static const struct peci_device_id * peci_bus_match_device_id(const struct peci_device_id *id, struct peci_device *device) { while (id->family != 0) { if (id->family == device->info.family && id->model == device->info.model) return id; id++; } return NULL; } static int peci_bus_device_match(struct device *dev, struct device_driver *drv) { struct peci_device *device = to_peci_device(dev); struct peci_driver *peci_drv = to_peci_driver(drv); if (dev->type != &peci_device_type) return 0; return !!peci_bus_match_device_id(peci_drv->id_table, device); } static int peci_bus_device_probe(struct device *dev) { struct peci_device *device = to_peci_device(dev); struct peci_driver *driver = to_peci_driver(dev->driver); return driver->probe(device, peci_bus_match_device_id(driver->id_table, device)); } static void peci_bus_device_remove(struct device *dev) { struct peci_device *device = to_peci_device(dev); struct peci_driver *driver = to_peci_driver(dev->driver); if (driver->remove) driver->remove(device); } struct bus_type peci_bus_type = { .name = "peci", .match = peci_bus_device_match, .probe = peci_bus_device_probe, .remove = peci_bus_device_remove, .bus_groups = peci_bus_groups, }; static int __init peci_init(void) { int ret; ret = bus_register(&peci_bus_type); if (ret < 0) { pr_err("peci: failed to register PECI bus type!\n"); return ret; } return 0; } module_init(peci_init); static void __exit peci_exit(void) { bus_unregister(&peci_bus_type); } module_exit(peci_exit); MODULE_AUTHOR("Jason M Bills "); MODULE_AUTHOR("Jae Hyun Yoo "); MODULE_AUTHOR("Iwona Winiarska "); MODULE_DESCRIPTION("PECI bus core module"); MODULE_LICENSE("GPL");