1 /*
2  * This file is part of wlcore
3  *
4  * Copyright (C) 2013 Texas Instruments Inc.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * version 2 as published by the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18  * 02110-1301 USA
19  *
20  */
21 
22 #include <linux/pm_runtime.h>
23 
24 #include "acx.h"
25 #include "wlcore.h"
26 #include "debug.h"
27 #include "sysfs.h"
28 
29 static ssize_t wl1271_sysfs_show_bt_coex_state(struct device *dev,
30 					       struct device_attribute *attr,
31 					       char *buf)
32 {
33 	struct wl1271 *wl = dev_get_drvdata(dev);
34 	ssize_t len;
35 
36 	len = PAGE_SIZE;
37 
38 	mutex_lock(&wl->mutex);
39 	len = snprintf(buf, len, "%d\n\n0 - off\n1 - on\n",
40 		       wl->sg_enabled);
41 	mutex_unlock(&wl->mutex);
42 
43 	return len;
44 
45 }
46 
47 static ssize_t wl1271_sysfs_store_bt_coex_state(struct device *dev,
48 						struct device_attribute *attr,
49 						const char *buf, size_t count)
50 {
51 	struct wl1271 *wl = dev_get_drvdata(dev);
52 	unsigned long res;
53 	int ret;
54 
55 	ret = kstrtoul(buf, 10, &res);
56 	if (ret < 0) {
57 		wl1271_warning("incorrect value written to bt_coex_mode");
58 		return count;
59 	}
60 
61 	mutex_lock(&wl->mutex);
62 
63 	res = !!res;
64 
65 	if (res == wl->sg_enabled)
66 		goto out;
67 
68 	wl->sg_enabled = res;
69 
70 	if (unlikely(wl->state != WLCORE_STATE_ON))
71 		goto out;
72 
73 	ret = pm_runtime_get_sync(wl->dev);
74 	if (ret < 0) {
75 		pm_runtime_put_noidle(wl->dev);
76 		goto out;
77 	}
78 
79 	wl1271_acx_sg_enable(wl, wl->sg_enabled);
80 	pm_runtime_mark_last_busy(wl->dev);
81 	pm_runtime_put_autosuspend(wl->dev);
82 
83  out:
84 	mutex_unlock(&wl->mutex);
85 	return count;
86 }
87 
88 static DEVICE_ATTR(bt_coex_state, 0644,
89 		   wl1271_sysfs_show_bt_coex_state,
90 		   wl1271_sysfs_store_bt_coex_state);
91 
92 static ssize_t wl1271_sysfs_show_hw_pg_ver(struct device *dev,
93 					   struct device_attribute *attr,
94 					   char *buf)
95 {
96 	struct wl1271 *wl = dev_get_drvdata(dev);
97 	ssize_t len;
98 
99 	len = PAGE_SIZE;
100 
101 	mutex_lock(&wl->mutex);
102 	if (wl->hw_pg_ver >= 0)
103 		len = snprintf(buf, len, "%d\n", wl->hw_pg_ver);
104 	else
105 		len = snprintf(buf, len, "n/a\n");
106 	mutex_unlock(&wl->mutex);
107 
108 	return len;
109 }
110 
111 static DEVICE_ATTR(hw_pg_ver, 0444, wl1271_sysfs_show_hw_pg_ver, NULL);
112 
113 static ssize_t wl1271_sysfs_read_fwlog(struct file *filp, struct kobject *kobj,
114 				       struct bin_attribute *bin_attr,
115 				       char *buffer, loff_t pos, size_t count)
116 {
117 	struct device *dev = container_of(kobj, struct device, kobj);
118 	struct wl1271 *wl = dev_get_drvdata(dev);
119 	ssize_t len;
120 	int ret;
121 
122 	ret = mutex_lock_interruptible(&wl->mutex);
123 	if (ret < 0)
124 		return -ERESTARTSYS;
125 
126 	/* Check if the fwlog is still valid */
127 	if (wl->fwlog_size < 0) {
128 		mutex_unlock(&wl->mutex);
129 		return 0;
130 	}
131 
132 	/* Seeking is not supported - old logs are not kept. Disregard pos. */
133 	len = min_t(size_t, count, wl->fwlog_size);
134 	wl->fwlog_size -= len;
135 	memcpy(buffer, wl->fwlog, len);
136 
137 	/* Make room for new messages */
138 	memmove(wl->fwlog, wl->fwlog + len, wl->fwlog_size);
139 
140 	mutex_unlock(&wl->mutex);
141 
142 	return len;
143 }
144 
145 static const struct bin_attribute fwlog_attr = {
146 	.attr = { .name = "fwlog", .mode = 0400 },
147 	.read = wl1271_sysfs_read_fwlog,
148 };
149 
150 int wlcore_sysfs_init(struct wl1271 *wl)
151 {
152 	int ret;
153 
154 	/* Create sysfs file to control bt coex state */
155 	ret = device_create_file(wl->dev, &dev_attr_bt_coex_state);
156 	if (ret < 0) {
157 		wl1271_error("failed to create sysfs file bt_coex_state");
158 		goto out;
159 	}
160 
161 	/* Create sysfs file to get HW PG version */
162 	ret = device_create_file(wl->dev, &dev_attr_hw_pg_ver);
163 	if (ret < 0) {
164 		wl1271_error("failed to create sysfs file hw_pg_ver");
165 		goto out_bt_coex_state;
166 	}
167 
168 	/* Create sysfs file for the FW log */
169 	ret = device_create_bin_file(wl->dev, &fwlog_attr);
170 	if (ret < 0) {
171 		wl1271_error("failed to create sysfs file fwlog");
172 		goto out_hw_pg_ver;
173 	}
174 
175 	goto out;
176 
177 out_hw_pg_ver:
178 	device_remove_file(wl->dev, &dev_attr_hw_pg_ver);
179 
180 out_bt_coex_state:
181 	device_remove_file(wl->dev, &dev_attr_bt_coex_state);
182 
183 out:
184 	return ret;
185 }
186 
187 void wlcore_sysfs_free(struct wl1271 *wl)
188 {
189 	device_remove_bin_file(wl->dev, &fwlog_attr);
190 
191 	device_remove_file(wl->dev, &dev_attr_hw_pg_ver);
192 
193 	device_remove_file(wl->dev, &dev_attr_bt_coex_state);
194 }
195