1 | // SPDX-License-Identifier: MIT |
2 | /* |
3 | * clock framework for AMD FCH controller block |
4 | * |
5 | * Copyright 2018 Advanced Micro Devices, Inc. |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/clkdev.h> |
10 | #include <linux/clk-provider.h> |
11 | #include <linux/pci.h> |
12 | #include <linux/platform_data/clk-fch.h> |
13 | #include <linux/platform_device.h> |
14 | |
15 | /* Clock Driving Strength 2 register */ |
16 | #define CLKDRVSTR2 0x28 |
17 | /* Clock Control 1 register */ |
18 | #define MISCCLKCNTL1 0x40 |
19 | /* Auxiliary clock1 enable bit */ |
20 | #define OSCCLKENB 2 |
21 | /* 25Mhz auxiliary output clock freq bit */ |
22 | #define OSCOUT1CLK25MHZ 16 |
23 | |
24 | #define ST_CLK_48M 0 |
25 | #define ST_CLK_25M 1 |
26 | #define ST_CLK_MUX 2 |
27 | #define ST_CLK_GATE 3 |
28 | #define ST_MAX_CLKS 4 |
29 | |
30 | #define CLK_48M_FIXED 0 |
31 | #define CLK_GATE_FIXED 1 |
32 | #define CLK_MAX_FIXED 2 |
33 | |
34 | /* List of supported CPU ids for clk mux with 25Mhz clk support */ |
35 | #define AMD_CPU_ID_ST 0x1576 |
36 | |
37 | static const char * const clk_oscout1_parents[] = { "clk48MHz" , "clk25MHz" }; |
38 | static struct clk_hw *hws[ST_MAX_CLKS]; |
39 | |
40 | static const struct pci_device_id fch_pci_ids[] = { |
41 | { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_ST) }, |
42 | { } |
43 | }; |
44 | |
45 | static int fch_clk_probe(struct platform_device *pdev) |
46 | { |
47 | struct fch_clk_data *fch_data; |
48 | struct pci_dev *rdev; |
49 | |
50 | fch_data = dev_get_platdata(dev: &pdev->dev); |
51 | if (!fch_data || !fch_data->base) |
52 | return -EINVAL; |
53 | |
54 | rdev = pci_get_domain_bus_and_slot(domain: 0, bus: 0, PCI_DEVFN(0, 0)); |
55 | if (!rdev) { |
56 | dev_err(&pdev->dev, "FCH device not found\n" ); |
57 | return -ENODEV; |
58 | } |
59 | |
60 | if (pci_match_id(ids: fch_pci_ids, dev: rdev)) { |
61 | hws[ST_CLK_48M] = clk_hw_register_fixed_rate(NULL, "clk48MHz" , |
62 | NULL, 0, 48000000); |
63 | hws[ST_CLK_25M] = clk_hw_register_fixed_rate(NULL, "clk25MHz" , |
64 | NULL, 0, 25000000); |
65 | |
66 | hws[ST_CLK_MUX] = clk_hw_register_mux(NULL, "oscout1_mux" , |
67 | clk_oscout1_parents, ARRAY_SIZE(clk_oscout1_parents), |
68 | 0, fch_data->base + CLKDRVSTR2, OSCOUT1CLK25MHZ, 3, 0, |
69 | NULL); |
70 | |
71 | clk_set_parent(clk: hws[ST_CLK_MUX]->clk, parent: hws[ST_CLK_48M]->clk); |
72 | |
73 | hws[ST_CLK_GATE] = clk_hw_register_gate(NULL, "oscout1" , |
74 | "oscout1_mux" , 0, fch_data->base + MISCCLKCNTL1, |
75 | OSCCLKENB, CLK_GATE_SET_TO_DISABLE, NULL); |
76 | |
77 | devm_clk_hw_register_clkdev(dev: &pdev->dev, hw: hws[ST_CLK_GATE], |
78 | con_id: fch_data->name, NULL); |
79 | } else { |
80 | hws[CLK_48M_FIXED] = clk_hw_register_fixed_rate(NULL, "clk48MHz" , |
81 | NULL, 0, 48000000); |
82 | |
83 | hws[CLK_GATE_FIXED] = clk_hw_register_gate(NULL, "oscout1" , |
84 | "clk48MHz" , 0, fch_data->base + MISCCLKCNTL1, |
85 | OSCCLKENB, 0, NULL); |
86 | |
87 | devm_clk_hw_register_clkdev(dev: &pdev->dev, hw: hws[CLK_GATE_FIXED], |
88 | con_id: fch_data->name, NULL); |
89 | } |
90 | |
91 | pci_dev_put(dev: rdev); |
92 | return 0; |
93 | } |
94 | |
95 | static void fch_clk_remove(struct platform_device *pdev) |
96 | { |
97 | int i, clks; |
98 | struct pci_dev *rdev; |
99 | |
100 | rdev = pci_get_domain_bus_and_slot(domain: 0, bus: 0, PCI_DEVFN(0, 0)); |
101 | if (!rdev) |
102 | return; |
103 | |
104 | clks = pci_match_id(ids: fch_pci_ids, dev: rdev) ? CLK_MAX_FIXED : ST_MAX_CLKS; |
105 | |
106 | for (i = 0; i < clks; i++) |
107 | clk_hw_unregister(hw: hws[i]); |
108 | |
109 | pci_dev_put(dev: rdev); |
110 | } |
111 | |
112 | static struct platform_driver fch_clk_driver = { |
113 | .driver = { |
114 | .name = "clk-fch" , |
115 | .suppress_bind_attrs = true, |
116 | }, |
117 | .probe = fch_clk_probe, |
118 | .remove_new = fch_clk_remove, |
119 | }; |
120 | builtin_platform_driver(fch_clk_driver); |
121 | |