Commit | Line | Data |
---|---|---|
7ceaa40b PLB |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // Copyright(c) 2019-2020 Intel Corporation. | |
3 | ||
4 | #include <linux/device.h> | |
5 | #include <linux/acpi.h> | |
26d97022 | 6 | #include <linux/pm_runtime.h> |
7ceaa40b PLB |
7 | #include <linux/soundwire/sdw.h> |
8 | #include <linux/soundwire/sdw_type.h> | |
9 | #include "bus.h" | |
10 | ||
e04e60fc PLB |
11 | /* |
12 | * The 3s value for autosuspend will only be used if there are no | |
13 | * devices physically attached on a bus segment. In practice enabling | |
14 | * the bus operation will result in children devices become active and | |
15 | * the master device will only suspend when all its children are no | |
16 | * longer active. | |
17 | */ | |
18 | #define SDW_MASTER_SUSPEND_DELAY_MS 3000 | |
19 | ||
c5778ca4 PLB |
20 | /* |
21 | * The sysfs for properties reflects the MIPI description as given | |
22 | * in the MIPI DisCo spec | |
23 | * | |
24 | * Base file is: | |
25 | * sdw-master-N | |
26 | * |---- revision | |
27 | * |---- clk_stop_modes | |
28 | * |---- max_clk_freq | |
29 | * |---- clk_freq | |
30 | * |---- clk_gears | |
31 | * |---- default_row | |
32 | * |---- default_col | |
33 | * |---- dynamic_shape | |
34 | * |---- err_threshold | |
35 | */ | |
36 | ||
37 | #define sdw_master_attr(field, format_string) \ | |
38 | static ssize_t field##_show(struct device *dev, \ | |
39 | struct device_attribute *attr, \ | |
40 | char *buf) \ | |
41 | { \ | |
42 | struct sdw_master_device *md = dev_to_sdw_master_device(dev); \ | |
43 | return sprintf(buf, format_string, md->bus->prop.field); \ | |
44 | } \ | |
45 | static DEVICE_ATTR_RO(field) | |
46 | ||
47 | sdw_master_attr(revision, "0x%x\n"); | |
48 | sdw_master_attr(clk_stop_modes, "0x%x\n"); | |
49 | sdw_master_attr(max_clk_freq, "%d\n"); | |
50 | sdw_master_attr(default_row, "%d\n"); | |
51 | sdw_master_attr(default_col, "%d\n"); | |
52 | sdw_master_attr(default_frame_rate, "%d\n"); | |
53 | sdw_master_attr(dynamic_frame, "%d\n"); | |
54 | sdw_master_attr(err_threshold, "%d\n"); | |
55 | ||
56 | static ssize_t clock_frequencies_show(struct device *dev, | |
57 | struct device_attribute *attr, char *buf) | |
58 | { | |
59 | struct sdw_master_device *md = dev_to_sdw_master_device(dev); | |
60 | ssize_t size = 0; | |
61 | int i; | |
62 | ||
63 | for (i = 0; i < md->bus->prop.num_clk_freq; i++) | |
64 | size += sprintf(buf + size, "%8d ", | |
65 | md->bus->prop.clk_freq[i]); | |
66 | size += sprintf(buf + size, "\n"); | |
67 | ||
68 | return size; | |
69 | } | |
70 | static DEVICE_ATTR_RO(clock_frequencies); | |
71 | ||
72 | static ssize_t clock_gears_show(struct device *dev, | |
73 | struct device_attribute *attr, char *buf) | |
74 | { | |
75 | struct sdw_master_device *md = dev_to_sdw_master_device(dev); | |
76 | ssize_t size = 0; | |
77 | int i; | |
78 | ||
79 | for (i = 0; i < md->bus->prop.num_clk_gears; i++) | |
80 | size += sprintf(buf + size, "%8d ", | |
81 | md->bus->prop.clk_gears[i]); | |
82 | size += sprintf(buf + size, "\n"); | |
83 | ||
84 | return size; | |
85 | } | |
86 | static DEVICE_ATTR_RO(clock_gears); | |
87 | ||
88 | static struct attribute *master_node_attrs[] = { | |
89 | &dev_attr_revision.attr, | |
90 | &dev_attr_clk_stop_modes.attr, | |
91 | &dev_attr_max_clk_freq.attr, | |
92 | &dev_attr_default_row.attr, | |
93 | &dev_attr_default_col.attr, | |
94 | &dev_attr_default_frame_rate.attr, | |
95 | &dev_attr_dynamic_frame.attr, | |
96 | &dev_attr_err_threshold.attr, | |
97 | &dev_attr_clock_frequencies.attr, | |
98 | &dev_attr_clock_gears.attr, | |
99 | NULL, | |
100 | }; | |
101 | ATTRIBUTE_GROUPS(master_node); | |
102 | ||
7ceaa40b PLB |
103 | static void sdw_master_device_release(struct device *dev) |
104 | { | |
105 | struct sdw_master_device *md = dev_to_sdw_master_device(dev); | |
106 | ||
107 | kfree(md); | |
108 | } | |
109 | ||
26d97022 BL |
110 | static const struct dev_pm_ops master_dev_pm = { |
111 | SET_RUNTIME_PM_OPS(pm_generic_runtime_suspend, | |
112 | pm_generic_runtime_resume, NULL) | |
113 | }; | |
114 | ||
7ceaa40b PLB |
115 | struct device_type sdw_master_type = { |
116 | .name = "soundwire_master", | |
117 | .release = sdw_master_device_release, | |
26d97022 | 118 | .pm = &master_dev_pm, |
7ceaa40b PLB |
119 | }; |
120 | ||
121 | /** | |
122 | * sdw_master_device_add() - create a Linux Master Device representation. | |
123 | * @bus: SDW bus instance | |
124 | * @parent: parent device | |
125 | * @fwnode: firmware node handle | |
126 | */ | |
127 | int sdw_master_device_add(struct sdw_bus *bus, struct device *parent, | |
128 | struct fwnode_handle *fwnode) | |
129 | { | |
130 | struct sdw_master_device *md; | |
131 | int ret; | |
132 | ||
133 | if (!parent) | |
134 | return -EINVAL; | |
135 | ||
136 | md = kzalloc(sizeof(*md), GFP_KERNEL); | |
137 | if (!md) | |
138 | return -ENOMEM; | |
139 | ||
140 | md->dev.bus = &sdw_bus_type; | |
141 | md->dev.type = &sdw_master_type; | |
142 | md->dev.parent = parent; | |
c5778ca4 | 143 | md->dev.groups = master_node_groups; |
7ceaa40b PLB |
144 | md->dev.of_node = parent->of_node; |
145 | md->dev.fwnode = fwnode; | |
146 | md->dev.dma_mask = parent->dma_mask; | |
147 | ||
148 | dev_set_name(&md->dev, "sdw-master-%d", bus->id); | |
149 | ||
150 | ret = device_register(&md->dev); | |
151 | if (ret) { | |
152 | dev_err(parent, "Failed to add master: ret %d\n", ret); | |
153 | /* | |
154 | * On err, don't free but drop ref as this will be freed | |
155 | * when release method is invoked. | |
156 | */ | |
157 | put_device(&md->dev); | |
158 | goto device_register_err; | |
159 | } | |
160 | ||
161 | /* add shortcuts to improve code readability/compactness */ | |
162 | md->bus = bus; | |
163 | bus->dev = &md->dev; | |
164 | bus->md = md; | |
165 | ||
e04e60fc PLB |
166 | pm_runtime_set_autosuspend_delay(&bus->md->dev, SDW_MASTER_SUSPEND_DELAY_MS); |
167 | pm_runtime_use_autosuspend(&bus->md->dev); | |
168 | pm_runtime_mark_last_busy(&bus->md->dev); | |
169 | pm_runtime_set_active(&bus->md->dev); | |
bd84256e | 170 | pm_runtime_enable(&bus->md->dev); |
e04e60fc | 171 | pm_runtime_idle(&bus->md->dev); |
7ceaa40b PLB |
172 | device_register_err: |
173 | return ret; | |
174 | } | |
175 | ||
176 | /** | |
177 | * sdw_master_device_del() - delete a Linux Master Device representation. | |
178 | * @bus: bus handle | |
179 | * | |
180 | * This function is the dual of sdw_master_device_add() | |
181 | */ | |
182 | int sdw_master_device_del(struct sdw_bus *bus) | |
183 | { | |
bd84256e | 184 | pm_runtime_disable(&bus->md->dev); |
7ceaa40b PLB |
185 | device_unregister(bus->dev); |
186 | ||
187 | return 0; | |
188 | } |