1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * This file is part of wlcore |
4 | * |
5 | * Copyright (C) 2013 Texas Instruments Inc. |
6 | */ |
7 | |
8 | #include <linux/pm_runtime.h> |
9 | |
10 | #include "acx.h" |
11 | #include "wlcore.h" |
12 | #include "debug.h" |
13 | #include "sysfs.h" |
14 | |
15 | static ssize_t bt_coex_state_show(struct device *dev, |
16 | struct device_attribute *attr, |
17 | char *buf) |
18 | { |
19 | struct wl1271 *wl = dev_get_drvdata(dev); |
20 | ssize_t len; |
21 | |
22 | len = PAGE_SIZE; |
23 | |
24 | mutex_lock(&wl->mutex); |
25 | len = snprintf(buf, size: len, fmt: "%d\n\n0 - off\n1 - on\n" , |
26 | wl->sg_enabled); |
27 | mutex_unlock(lock: &wl->mutex); |
28 | |
29 | return len; |
30 | |
31 | } |
32 | |
33 | static ssize_t bt_coex_state_store(struct device *dev, |
34 | struct device_attribute *attr, |
35 | const char *buf, size_t count) |
36 | { |
37 | struct wl1271 *wl = dev_get_drvdata(dev); |
38 | unsigned long res; |
39 | int ret; |
40 | |
41 | ret = kstrtoul(s: buf, base: 10, res: &res); |
42 | if (ret < 0) { |
43 | wl1271_warning("incorrect value written to bt_coex_mode" ); |
44 | return count; |
45 | } |
46 | |
47 | mutex_lock(&wl->mutex); |
48 | |
49 | res = !!res; |
50 | |
51 | if (res == wl->sg_enabled) |
52 | goto out; |
53 | |
54 | wl->sg_enabled = res; |
55 | |
56 | if (unlikely(wl->state != WLCORE_STATE_ON)) |
57 | goto out; |
58 | |
59 | ret = pm_runtime_resume_and_get(dev: wl->dev); |
60 | if (ret < 0) |
61 | goto out; |
62 | |
63 | wl1271_acx_sg_enable(wl, enable: wl->sg_enabled); |
64 | pm_runtime_mark_last_busy(dev: wl->dev); |
65 | pm_runtime_put_autosuspend(dev: wl->dev); |
66 | |
67 | out: |
68 | mutex_unlock(lock: &wl->mutex); |
69 | return count; |
70 | } |
71 | |
72 | static DEVICE_ATTR_RW(bt_coex_state); |
73 | |
74 | static ssize_t hw_pg_ver_show(struct device *dev, |
75 | struct device_attribute *attr, |
76 | char *buf) |
77 | { |
78 | struct wl1271 *wl = dev_get_drvdata(dev); |
79 | ssize_t len; |
80 | |
81 | len = PAGE_SIZE; |
82 | |
83 | mutex_lock(&wl->mutex); |
84 | if (wl->hw_pg_ver >= 0) |
85 | len = snprintf(buf, size: len, fmt: "%d\n" , wl->hw_pg_ver); |
86 | else |
87 | len = snprintf(buf, size: len, fmt: "n/a\n" ); |
88 | mutex_unlock(lock: &wl->mutex); |
89 | |
90 | return len; |
91 | } |
92 | |
93 | static DEVICE_ATTR_RO(hw_pg_ver); |
94 | |
95 | static ssize_t wl1271_sysfs_read_fwlog(struct file *filp, struct kobject *kobj, |
96 | struct bin_attribute *bin_attr, |
97 | char *buffer, loff_t pos, size_t count) |
98 | { |
99 | struct device *dev = kobj_to_dev(kobj); |
100 | struct wl1271 *wl = dev_get_drvdata(dev); |
101 | ssize_t len; |
102 | int ret; |
103 | |
104 | ret = mutex_lock_interruptible(&wl->mutex); |
105 | if (ret < 0) |
106 | return -ERESTARTSYS; |
107 | |
108 | /* Check if the fwlog is still valid */ |
109 | if (wl->fwlog_size < 0) { |
110 | mutex_unlock(lock: &wl->mutex); |
111 | return 0; |
112 | } |
113 | |
114 | /* Seeking is not supported - old logs are not kept. Disregard pos. */ |
115 | len = min_t(size_t, count, wl->fwlog_size); |
116 | wl->fwlog_size -= len; |
117 | memcpy(buffer, wl->fwlog, len); |
118 | |
119 | /* Make room for new messages */ |
120 | memmove(wl->fwlog, wl->fwlog + len, wl->fwlog_size); |
121 | |
122 | mutex_unlock(lock: &wl->mutex); |
123 | |
124 | return len; |
125 | } |
126 | |
127 | static const struct bin_attribute fwlog_attr = { |
128 | .attr = { .name = "fwlog" , .mode = 0400 }, |
129 | .read = wl1271_sysfs_read_fwlog, |
130 | }; |
131 | |
132 | int wlcore_sysfs_init(struct wl1271 *wl) |
133 | { |
134 | int ret; |
135 | |
136 | /* Create sysfs file to control bt coex state */ |
137 | ret = device_create_file(device: wl->dev, entry: &dev_attr_bt_coex_state); |
138 | if (ret < 0) { |
139 | wl1271_error("failed to create sysfs file bt_coex_state" ); |
140 | goto out; |
141 | } |
142 | |
143 | /* Create sysfs file to get HW PG version */ |
144 | ret = device_create_file(device: wl->dev, entry: &dev_attr_hw_pg_ver); |
145 | if (ret < 0) { |
146 | wl1271_error("failed to create sysfs file hw_pg_ver" ); |
147 | goto out_bt_coex_state; |
148 | } |
149 | |
150 | /* Create sysfs file for the FW log */ |
151 | ret = device_create_bin_file(dev: wl->dev, attr: &fwlog_attr); |
152 | if (ret < 0) { |
153 | wl1271_error("failed to create sysfs file fwlog" ); |
154 | goto out_hw_pg_ver; |
155 | } |
156 | |
157 | goto out; |
158 | |
159 | out_hw_pg_ver: |
160 | device_remove_file(dev: wl->dev, attr: &dev_attr_hw_pg_ver); |
161 | |
162 | out_bt_coex_state: |
163 | device_remove_file(dev: wl->dev, attr: &dev_attr_bt_coex_state); |
164 | |
165 | out: |
166 | return ret; |
167 | } |
168 | |
169 | void wlcore_sysfs_free(struct wl1271 *wl) |
170 | { |
171 | device_remove_bin_file(dev: wl->dev, attr: &fwlog_attr); |
172 | |
173 | device_remove_file(dev: wl->dev, attr: &dev_attr_hw_pg_ver); |
174 | |
175 | device_remove_file(dev: wl->dev, attr: &dev_attr_bt_coex_state); |
176 | } |
177 | |