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