1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2016 Free Electrons |
4 | * |
5 | * Maxime Ripard <maxime.ripard@free-electrons.com> |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/component.h> |
10 | #include <linux/module.h> |
11 | #include <linux/mod_devicetable.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/regmap.h> |
14 | #include <linux/reset.h> |
15 | |
16 | struct sun6i_drc { |
17 | struct clk *bus_clk; |
18 | struct clk *mod_clk; |
19 | struct reset_control *reset; |
20 | }; |
21 | |
22 | static int sun6i_drc_bind(struct device *dev, struct device *master, |
23 | void *data) |
24 | { |
25 | struct sun6i_drc *drc; |
26 | int ret; |
27 | |
28 | drc = devm_kzalloc(dev, size: sizeof(*drc), GFP_KERNEL); |
29 | if (!drc) |
30 | return -ENOMEM; |
31 | dev_set_drvdata(dev, data: drc); |
32 | |
33 | drc->reset = devm_reset_control_get(dev, NULL); |
34 | if (IS_ERR(ptr: drc->reset)) { |
35 | dev_err(dev, "Couldn't get our reset line\n" ); |
36 | return PTR_ERR(ptr: drc->reset); |
37 | } |
38 | |
39 | ret = reset_control_deassert(rstc: drc->reset); |
40 | if (ret) { |
41 | dev_err(dev, "Couldn't deassert our reset line\n" ); |
42 | return ret; |
43 | } |
44 | |
45 | drc->bus_clk = devm_clk_get(dev, id: "ahb" ); |
46 | if (IS_ERR(ptr: drc->bus_clk)) { |
47 | dev_err(dev, "Couldn't get our bus clock\n" ); |
48 | ret = PTR_ERR(ptr: drc->bus_clk); |
49 | goto err_assert_reset; |
50 | } |
51 | clk_prepare_enable(clk: drc->bus_clk); |
52 | |
53 | drc->mod_clk = devm_clk_get(dev, id: "mod" ); |
54 | if (IS_ERR(ptr: drc->mod_clk)) { |
55 | dev_err(dev, "Couldn't get our mod clock\n" ); |
56 | ret = PTR_ERR(ptr: drc->mod_clk); |
57 | goto err_disable_bus_clk; |
58 | } |
59 | |
60 | ret = clk_set_rate_exclusive(clk: drc->mod_clk, rate: 300000000); |
61 | if (ret) { |
62 | dev_err(dev, "Couldn't set the module clock frequency\n" ); |
63 | goto err_disable_bus_clk; |
64 | } |
65 | |
66 | clk_prepare_enable(clk: drc->mod_clk); |
67 | |
68 | return 0; |
69 | |
70 | err_disable_bus_clk: |
71 | clk_disable_unprepare(clk: drc->bus_clk); |
72 | err_assert_reset: |
73 | reset_control_assert(rstc: drc->reset); |
74 | return ret; |
75 | } |
76 | |
77 | static void sun6i_drc_unbind(struct device *dev, struct device *master, |
78 | void *data) |
79 | { |
80 | struct sun6i_drc *drc = dev_get_drvdata(dev); |
81 | |
82 | clk_rate_exclusive_put(clk: drc->mod_clk); |
83 | clk_disable_unprepare(clk: drc->mod_clk); |
84 | clk_disable_unprepare(clk: drc->bus_clk); |
85 | reset_control_assert(rstc: drc->reset); |
86 | } |
87 | |
88 | static const struct component_ops sun6i_drc_ops = { |
89 | .bind = sun6i_drc_bind, |
90 | .unbind = sun6i_drc_unbind, |
91 | }; |
92 | |
93 | static int sun6i_drc_probe(struct platform_device *pdev) |
94 | { |
95 | return component_add(&pdev->dev, &sun6i_drc_ops); |
96 | } |
97 | |
98 | static void sun6i_drc_remove(struct platform_device *pdev) |
99 | { |
100 | component_del(&pdev->dev, &sun6i_drc_ops); |
101 | } |
102 | |
103 | static const struct of_device_id sun6i_drc_of_table[] = { |
104 | { .compatible = "allwinner,sun6i-a31-drc" }, |
105 | { .compatible = "allwinner,sun6i-a31s-drc" }, |
106 | { .compatible = "allwinner,sun8i-a23-drc" }, |
107 | { .compatible = "allwinner,sun8i-a33-drc" }, |
108 | { .compatible = "allwinner,sun9i-a80-drc" }, |
109 | { } |
110 | }; |
111 | MODULE_DEVICE_TABLE(of, sun6i_drc_of_table); |
112 | |
113 | static struct platform_driver sun6i_drc_platform_driver = { |
114 | .probe = sun6i_drc_probe, |
115 | .remove_new = sun6i_drc_remove, |
116 | .driver = { |
117 | .name = "sun6i-drc" , |
118 | .of_match_table = sun6i_drc_of_table, |
119 | }, |
120 | }; |
121 | module_platform_driver(sun6i_drc_platform_driver); |
122 | |
123 | MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>" ); |
124 | MODULE_DESCRIPTION("Allwinner A31 Dynamic Range Control (DRC) Driver" ); |
125 | MODULE_LICENSE("GPL" ); |
126 | |