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 "wlcore.h"
23#include "debug.h"
24#include "ps.h"
25#include "sysfs.h"
26
27static ssize_t wl1271_sysfs_show_bt_coex_state(struct device *dev,
28					       struct device_attribute *attr,
29					       char *buf)
30{
31	struct wl1271 *wl = dev_get_drvdata(dev);
32	ssize_t len;
33
34	len = PAGE_SIZE;
35
36	mutex_lock(&wl->mutex);
37	len = snprintf(buf, len, "%d\n\n0 - off\n1 - on\n",
38		       wl->sg_enabled);
39	mutex_unlock(&wl->mutex);
40
41	return len;
42
43}
44
45static ssize_t wl1271_sysfs_store_bt_coex_state(struct device *dev,
46						struct device_attribute *attr,
47						const char *buf, size_t count)
48{
49	struct wl1271 *wl = dev_get_drvdata(dev);
50	unsigned long res;
51	int ret;
52
53	ret = kstrtoul(buf, 10, &res);
54	if (ret < 0) {
55		wl1271_warning("incorrect value written to bt_coex_mode");
56		return count;
57	}
58
59	mutex_lock(&wl->mutex);
60
61	res = !!res;
62
63	if (res == wl->sg_enabled)
64		goto out;
65
66	wl->sg_enabled = res;
67
68	if (unlikely(wl->state != WLCORE_STATE_ON))
69		goto out;
70
71	ret = wl1271_ps_elp_wakeup(wl);
72	if (ret < 0)
73		goto out;
74
75	wl1271_acx_sg_enable(wl, wl->sg_enabled);
76	wl1271_ps_elp_sleep(wl);
77
78 out:
79	mutex_unlock(&wl->mutex);
80	return count;
81}
82
83static DEVICE_ATTR(bt_coex_state, S_IRUGO | S_IWUSR,
84		   wl1271_sysfs_show_bt_coex_state,
85		   wl1271_sysfs_store_bt_coex_state);
86
87static ssize_t wl1271_sysfs_show_hw_pg_ver(struct device *dev,
88					   struct device_attribute *attr,
89					   char *buf)
90{
91	struct wl1271 *wl = dev_get_drvdata(dev);
92	ssize_t len;
93
94	len = PAGE_SIZE;
95
96	mutex_lock(&wl->mutex);
97	if (wl->hw_pg_ver >= 0)
98		len = snprintf(buf, len, "%d\n", wl->hw_pg_ver);
99	else
100		len = snprintf(buf, len, "n/a\n");
101	mutex_unlock(&wl->mutex);
102
103	return len;
104}
105
106static DEVICE_ATTR(hw_pg_ver, S_IRUGO,
107		   wl1271_sysfs_show_hw_pg_ver, NULL);
108
109static ssize_t wl1271_sysfs_read_fwlog(struct file *filp, struct kobject *kobj,
110				       struct bin_attribute *bin_attr,
111				       char *buffer, loff_t pos, size_t count)
112{
113	struct device *dev = container_of(kobj, struct device, kobj);
114	struct wl1271 *wl = dev_get_drvdata(dev);
115	ssize_t len;
116	int ret;
117
118	ret = mutex_lock_interruptible(&wl->mutex);
119	if (ret < 0)
120		return -ERESTARTSYS;
121
122	/* Let only one thread read the log at a time, blocking others */
123	while (wl->fwlog_size == 0) {
124		DEFINE_WAIT(wait);
125
126		prepare_to_wait_exclusive(&wl->fwlog_waitq,
127					  &wait,
128					  TASK_INTERRUPTIBLE);
129
130		if (wl->fwlog_size != 0) {
131			finish_wait(&wl->fwlog_waitq, &wait);
132			break;
133		}
134
135		mutex_unlock(&wl->mutex);
136
137		schedule();
138		finish_wait(&wl->fwlog_waitq, &wait);
139
140		if (signal_pending(current))
141			return -ERESTARTSYS;
142
143		ret = mutex_lock_interruptible(&wl->mutex);
144		if (ret < 0)
145			return -ERESTARTSYS;
146	}
147
148	/* Check if the fwlog is still valid */
149	if (wl->fwlog_size < 0) {
150		mutex_unlock(&wl->mutex);
151		return 0;
152	}
153
154	/* Seeking is not supported - old logs are not kept. Disregard pos. */
155	len = min_t(size_t, count, wl->fwlog_size);
156	wl->fwlog_size -= len;
157	memcpy(buffer, wl->fwlog, len);
158
159	/* Make room for new messages */
160	memmove(wl->fwlog, wl->fwlog + len, wl->fwlog_size);
161
162	mutex_unlock(&wl->mutex);
163
164	return len;
165}
166
167static struct bin_attribute fwlog_attr = {
168	.attr = {.name = "fwlog", .mode = S_IRUSR},
169	.read = wl1271_sysfs_read_fwlog,
170};
171
172int wlcore_sysfs_init(struct wl1271 *wl)
173{
174	int ret;
175
176	/* Create sysfs file to control bt coex state */
177	ret = device_create_file(wl->dev, &dev_attr_bt_coex_state);
178	if (ret < 0) {
179		wl1271_error("failed to create sysfs file bt_coex_state");
180		goto out;
181	}
182
183	/* Create sysfs file to get HW PG version */
184	ret = device_create_file(wl->dev, &dev_attr_hw_pg_ver);
185	if (ret < 0) {
186		wl1271_error("failed to create sysfs file hw_pg_ver");
187		goto out_bt_coex_state;
188	}
189
190	/* Create sysfs file for the FW log */
191	ret = device_create_bin_file(wl->dev, &fwlog_attr);
192	if (ret < 0) {
193		wl1271_error("failed to create sysfs file fwlog");
194		goto out_hw_pg_ver;
195	}
196
197	goto out;
198
199out_hw_pg_ver:
200	device_remove_file(wl->dev, &dev_attr_hw_pg_ver);
201
202out_bt_coex_state:
203	device_remove_file(wl->dev, &dev_attr_bt_coex_state);
204
205out:
206	return ret;
207}
208
209void wlcore_sysfs_free(struct wl1271 *wl)
210{
211	device_remove_bin_file(wl->dev, &fwlog_attr);
212
213	device_remove_file(wl->dev, &dev_attr_hw_pg_ver);
214
215	device_remove_file(wl->dev, &dev_attr_bt_coex_state);
216}
217