| /* |
| * This file is part of wlcore |
| * |
| * Copyright (C) 2013 Texas Instruments Inc. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA |
| * 02110-1301 USA |
| * |
| */ |
| |
| #include "wlcore.h" |
| #include "debug.h" |
| #include "ps.h" |
| #include "sysfs.h" |
| |
| static ssize_t wl1271_sysfs_show_bt_coex_state(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct wl1271 *wl = dev_get_drvdata(dev); |
| ssize_t len; |
| |
| len = PAGE_SIZE; |
| |
| mutex_lock(&wl->mutex); |
| len = snprintf(buf, len, "%d\n\n0 - off\n1 - on\n", |
| wl->sg_enabled); |
| mutex_unlock(&wl->mutex); |
| |
| return len; |
| |
| } |
| |
| static ssize_t wl1271_sysfs_store_bt_coex_state(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct wl1271 *wl = dev_get_drvdata(dev); |
| unsigned long res; |
| int ret; |
| |
| ret = kstrtoul(buf, 10, &res); |
| if (ret < 0) { |
| wl1271_warning("incorrect value written to bt_coex_mode"); |
| return count; |
| } |
| |
| mutex_lock(&wl->mutex); |
| |
| res = !!res; |
| |
| if (res == wl->sg_enabled) |
| goto out; |
| |
| wl->sg_enabled = res; |
| |
| if (unlikely(wl->state != WLCORE_STATE_ON)) |
| goto out; |
| |
| ret = wl1271_ps_elp_wakeup(wl); |
| if (ret < 0) |
| goto out; |
| |
| wl1271_acx_sg_enable(wl, wl->sg_enabled); |
| wl1271_ps_elp_sleep(wl); |
| |
| out: |
| mutex_unlock(&wl->mutex); |
| return count; |
| } |
| |
| static DEVICE_ATTR(bt_coex_state, S_IRUGO | S_IWUSR, |
| wl1271_sysfs_show_bt_coex_state, |
| wl1271_sysfs_store_bt_coex_state); |
| |
| static ssize_t wl1271_sysfs_show_hw_pg_ver(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct wl1271 *wl = dev_get_drvdata(dev); |
| ssize_t len; |
| |
| len = PAGE_SIZE; |
| |
| mutex_lock(&wl->mutex); |
| if (wl->hw_pg_ver >= 0) |
| len = snprintf(buf, len, "%d\n", wl->hw_pg_ver); |
| else |
| len = snprintf(buf, len, "n/a\n"); |
| mutex_unlock(&wl->mutex); |
| |
| return len; |
| } |
| |
| static DEVICE_ATTR(hw_pg_ver, S_IRUGO, |
| wl1271_sysfs_show_hw_pg_ver, NULL); |
| |
| static ssize_t wl1271_sysfs_read_fwlog(struct file *filp, struct kobject *kobj, |
| struct bin_attribute *bin_attr, |
| char *buffer, loff_t pos, size_t count) |
| { |
| struct device *dev = container_of(kobj, struct device, kobj); |
| struct wl1271 *wl = dev_get_drvdata(dev); |
| ssize_t len; |
| int ret; |
| |
| ret = mutex_lock_interruptible(&wl->mutex); |
| if (ret < 0) |
| return -ERESTARTSYS; |
| |
| /* Let only one thread read the log at a time, blocking others */ |
| while (wl->fwlog_size == 0) { |
| DEFINE_WAIT(wait); |
| |
| prepare_to_wait_exclusive(&wl->fwlog_waitq, |
| &wait, |
| TASK_INTERRUPTIBLE); |
| |
| if (wl->fwlog_size != 0) { |
| finish_wait(&wl->fwlog_waitq, &wait); |
| break; |
| } |
| |
| mutex_unlock(&wl->mutex); |
| |
| schedule(); |
| finish_wait(&wl->fwlog_waitq, &wait); |
| |
| if (signal_pending(current)) |
| return -ERESTARTSYS; |
| |
| ret = mutex_lock_interruptible(&wl->mutex); |
| if (ret < 0) |
| return -ERESTARTSYS; |
| } |
| |
| /* Check if the fwlog is still valid */ |
| if (wl->fwlog_size < 0) { |
| mutex_unlock(&wl->mutex); |
| return 0; |
| } |
| |
| /* Seeking is not supported - old logs are not kept. Disregard pos. */ |
| len = min_t(size_t, count, wl->fwlog_size); |
| wl->fwlog_size -= len; |
| memcpy(buffer, wl->fwlog, len); |
| |
| /* Make room for new messages */ |
| memmove(wl->fwlog, wl->fwlog + len, wl->fwlog_size); |
| |
| mutex_unlock(&wl->mutex); |
| |
| return len; |
| } |
| |
| static struct bin_attribute fwlog_attr = { |
| .attr = {.name = "fwlog", .mode = S_IRUSR}, |
| .read = wl1271_sysfs_read_fwlog, |
| }; |
| |
| int wlcore_sysfs_init(struct wl1271 *wl) |
| { |
| int ret; |
| |
| /* Create sysfs file to control bt coex state */ |
| ret = device_create_file(wl->dev, &dev_attr_bt_coex_state); |
| if (ret < 0) { |
| wl1271_error("failed to create sysfs file bt_coex_state"); |
| goto out; |
| } |
| |
| /* Create sysfs file to get HW PG version */ |
| ret = device_create_file(wl->dev, &dev_attr_hw_pg_ver); |
| if (ret < 0) { |
| wl1271_error("failed to create sysfs file hw_pg_ver"); |
| goto out_bt_coex_state; |
| } |
| |
| /* Create sysfs file for the FW log */ |
| ret = device_create_bin_file(wl->dev, &fwlog_attr); |
| if (ret < 0) { |
| wl1271_error("failed to create sysfs file fwlog"); |
| goto out_hw_pg_ver; |
| } |
| |
| goto out; |
| |
| out_hw_pg_ver: |
| device_remove_file(wl->dev, &dev_attr_hw_pg_ver); |
| |
| out_bt_coex_state: |
| device_remove_file(wl->dev, &dev_attr_bt_coex_state); |
| |
| out: |
| return ret; |
| } |
| |
| void wlcore_sysfs_free(struct wl1271 *wl) |
| { |
| device_remove_bin_file(wl->dev, &fwlog_attr); |
| |
| device_remove_file(wl->dev, &dev_attr_hw_pg_ver); |
| |
| device_remove_file(wl->dev, &dev_attr_bt_coex_state); |
| } |