1 /* 2 * Simple Power-Managed Bus Driver 3 * 4 * Copyright (C) 2014-2015 Glider bvba 5 * 6 * This file is subject to the terms and conditions of the GNU General Public 7 * License. See the file "COPYING" in the main directory of this archive 8 * for more details. 9 */ 10 11 #include <linux/clk.h> 12 #include <linux/module.h> 13 #include <linux/of_platform.h> 14 #include <linux/platform_device.h> 15 #include <linux/pm_runtime.h> 16 17 struct simple_pm_bus { 18 struct clk_bulk_data *clks; 19 int num_clks; 20 }; 21 22 static int simple_pm_bus_probe(struct platform_device *pdev) 23 { 24 const struct device *dev = &pdev->dev; 25 const struct of_dev_auxdata *lookup = dev_get_platdata(dev); 26 struct device_node *np = dev->of_node; 27 const struct of_device_id *match; 28 struct simple_pm_bus *bus; 29 30 /* 31 * Allow user to use driver_override to bind this driver to a 32 * transparent bus device which has a different compatible string 33 * that's not listed in simple_pm_bus_of_match. We don't want to do any 34 * of the simple-pm-bus tasks for these devices, so return early. 35 */ 36 if (pdev->driver_override) 37 return 0; 38 39 match = of_match_device(dev->driver->of_match_table, dev); 40 /* 41 * These are transparent bus devices (not simple-pm-bus matches) that 42 * have their child nodes populated automatically. So, don't need to 43 * do anything more. We only match with the device if this driver is 44 * the most specific match because we don't want to incorrectly bind to 45 * a device that has a more specific driver. 46 */ 47 if (match && match->data) { 48 if (of_property_match_string(np, "compatible", match->compatible) == 0) 49 return 0; 50 else 51 return -ENODEV; 52 } 53 54 bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL); 55 if (!bus) 56 return -ENOMEM; 57 58 bus->num_clks = devm_clk_bulk_get_all(&pdev->dev, &bus->clks); 59 if (bus->num_clks < 0) 60 return dev_err_probe(&pdev->dev, bus->num_clks, "failed to get clocks\n"); 61 62 dev_set_drvdata(&pdev->dev, bus); 63 64 dev_dbg(&pdev->dev, "%s\n", __func__); 65 66 pm_runtime_enable(&pdev->dev); 67 68 if (np) 69 of_platform_populate(np, NULL, lookup, &pdev->dev); 70 71 return 0; 72 } 73 74 static int simple_pm_bus_remove(struct platform_device *pdev) 75 { 76 const void *data = of_device_get_match_data(&pdev->dev); 77 78 if (pdev->driver_override || data) 79 return 0; 80 81 dev_dbg(&pdev->dev, "%s\n", __func__); 82 83 pm_runtime_disable(&pdev->dev); 84 return 0; 85 } 86 87 static int simple_pm_bus_runtime_suspend(struct device *dev) 88 { 89 struct simple_pm_bus *bus = dev_get_drvdata(dev); 90 91 clk_bulk_disable_unprepare(bus->num_clks, bus->clks); 92 93 return 0; 94 } 95 96 static int simple_pm_bus_runtime_resume(struct device *dev) 97 { 98 struct simple_pm_bus *bus = dev_get_drvdata(dev); 99 int ret; 100 101 ret = clk_bulk_prepare_enable(bus->num_clks, bus->clks); 102 if (ret) { 103 dev_err(dev, "failed to enable clocks: %d\n", ret); 104 return ret; 105 } 106 107 return 0; 108 } 109 110 static const struct dev_pm_ops simple_pm_bus_pm_ops = { 111 SET_RUNTIME_PM_OPS(simple_pm_bus_runtime_suspend, 112 simple_pm_bus_runtime_resume, NULL) 113 SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, 114 pm_runtime_force_resume) 115 }; 116 117 #define ONLY_BUS ((void *) 1) /* Match if the device is only a bus. */ 118 119 static const struct of_device_id simple_pm_bus_of_match[] = { 120 { .compatible = "simple-pm-bus", }, 121 { .compatible = "simple-bus", .data = ONLY_BUS }, 122 { .compatible = "simple-mfd", .data = ONLY_BUS }, 123 { .compatible = "isa", .data = ONLY_BUS }, 124 { .compatible = "arm,amba-bus", .data = ONLY_BUS }, 125 { /* sentinel */ } 126 }; 127 MODULE_DEVICE_TABLE(of, simple_pm_bus_of_match); 128 129 static struct platform_driver simple_pm_bus_driver = { 130 .probe = simple_pm_bus_probe, 131 .remove = simple_pm_bus_remove, 132 .driver = { 133 .name = "simple-pm-bus", 134 .of_match_table = simple_pm_bus_of_match, 135 .pm = &simple_pm_bus_pm_ops, 136 }, 137 }; 138 139 module_platform_driver(simple_pm_bus_driver); 140 141 MODULE_DESCRIPTION("Simple Power-Managed Bus Driver"); 142 MODULE_AUTHOR("Geert Uytterhoeven <geert+renesas@glider.be>"); 143 MODULE_LICENSE("GPL v2"); 144