1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | // Copyright 2020 Cerno |
3 | |
4 | #include <linux/clk-provider.h> |
5 | #include <linux/module.h> |
6 | #include <linux/platform_device.h> |
7 | #include <linux/reset-controller.h> |
8 | #include <linux/reset/reset-simple.h> |
9 | |
10 | #define DVP_HT_RPI_SW_INIT 0x04 |
11 | #define DVP_HT_RPI_MISC_CONFIG 0x08 |
12 | |
13 | #define NR_CLOCKS 2 |
14 | #define NR_RESETS 6 |
15 | |
16 | struct clk_dvp { |
17 | struct clk_hw_onecell_data *data; |
18 | struct reset_simple_data reset; |
19 | }; |
20 | |
21 | static const struct clk_parent_data clk_dvp_parent = { |
22 | .index = 0, |
23 | }; |
24 | |
25 | static int clk_dvp_probe(struct platform_device *pdev) |
26 | { |
27 | struct clk_hw_onecell_data *data; |
28 | struct clk_dvp *dvp; |
29 | void __iomem *base; |
30 | int ret; |
31 | |
32 | dvp = devm_kzalloc(dev: &pdev->dev, size: sizeof(*dvp), GFP_KERNEL); |
33 | if (!dvp) |
34 | return -ENOMEM; |
35 | platform_set_drvdata(pdev, data: dvp); |
36 | |
37 | dvp->data = devm_kzalloc(dev: &pdev->dev, |
38 | struct_size(dvp->data, hws, NR_CLOCKS), |
39 | GFP_KERNEL); |
40 | if (!dvp->data) |
41 | return -ENOMEM; |
42 | data = dvp->data; |
43 | |
44 | base = devm_platform_ioremap_resource(pdev, index: 0); |
45 | if (IS_ERR(ptr: base)) |
46 | return PTR_ERR(ptr: base); |
47 | |
48 | dvp->reset.rcdev.owner = THIS_MODULE; |
49 | dvp->reset.rcdev.nr_resets = NR_RESETS; |
50 | dvp->reset.rcdev.ops = &reset_simple_ops; |
51 | dvp->reset.rcdev.of_node = pdev->dev.of_node; |
52 | dvp->reset.membase = base + DVP_HT_RPI_SW_INIT; |
53 | spin_lock_init(&dvp->reset.lock); |
54 | |
55 | ret = devm_reset_controller_register(dev: &pdev->dev, rcdev: &dvp->reset.rcdev); |
56 | if (ret) |
57 | return ret; |
58 | |
59 | data->hws[0] = clk_hw_register_gate_parent_data(&pdev->dev, |
60 | "hdmi0-108MHz" , |
61 | &clk_dvp_parent, 0, |
62 | base + DVP_HT_RPI_MISC_CONFIG, 3, |
63 | CLK_GATE_SET_TO_DISABLE, |
64 | &dvp->reset.lock); |
65 | if (IS_ERR(ptr: data->hws[0])) |
66 | return PTR_ERR(ptr: data->hws[0]); |
67 | |
68 | data->hws[1] = clk_hw_register_gate_parent_data(&pdev->dev, |
69 | "hdmi1-108MHz" , |
70 | &clk_dvp_parent, 0, |
71 | base + DVP_HT_RPI_MISC_CONFIG, 4, |
72 | CLK_GATE_SET_TO_DISABLE, |
73 | &dvp->reset.lock); |
74 | if (IS_ERR(ptr: data->hws[1])) { |
75 | ret = PTR_ERR(ptr: data->hws[1]); |
76 | goto unregister_clk0; |
77 | } |
78 | |
79 | data->num = NR_CLOCKS; |
80 | ret = of_clk_add_hw_provider(np: pdev->dev.of_node, get: of_clk_hw_onecell_get, |
81 | data); |
82 | if (ret) |
83 | goto unregister_clk1; |
84 | |
85 | return 0; |
86 | |
87 | unregister_clk1: |
88 | clk_hw_unregister_gate(hw: data->hws[1]); |
89 | |
90 | unregister_clk0: |
91 | clk_hw_unregister_gate(hw: data->hws[0]); |
92 | return ret; |
93 | }; |
94 | |
95 | static void clk_dvp_remove(struct platform_device *pdev) |
96 | { |
97 | struct clk_dvp *dvp = platform_get_drvdata(pdev); |
98 | struct clk_hw_onecell_data *data = dvp->data; |
99 | |
100 | clk_hw_unregister_gate(hw: data->hws[1]); |
101 | clk_hw_unregister_gate(hw: data->hws[0]); |
102 | } |
103 | |
104 | static const struct of_device_id clk_dvp_dt_ids[] = { |
105 | { .compatible = "brcm,brcm2711-dvp" , }, |
106 | { /* sentinel */ } |
107 | }; |
108 | MODULE_DEVICE_TABLE(of, clk_dvp_dt_ids); |
109 | |
110 | static struct platform_driver clk_dvp_driver = { |
111 | .probe = clk_dvp_probe, |
112 | .remove_new = clk_dvp_remove, |
113 | .driver = { |
114 | .name = "brcm2711-dvp" , |
115 | .of_match_table = clk_dvp_dt_ids, |
116 | }, |
117 | }; |
118 | module_platform_driver(clk_dvp_driver); |
119 | |
120 | MODULE_AUTHOR("Maxime Ripard <maxime@cerno.tech>" ); |
121 | MODULE_DESCRIPTION("BCM2711 DVP clock driver" ); |
122 | MODULE_LICENSE("GPL" ); |
123 | |