// SPDX-License-Identifier: GPL-2.0-only /* * This file is part of wlcore * * Copyright (C) 2013 Texas Instruments Inc. */ #include #include "acx.h" #include "wlcore.h" #include "debug.h" #include "sysfs.h" static ssize_t bt_coex_state_show(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 bt_coex_state_store(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 = pm_runtime_resume_and_get(wl->dev); if (ret < 0) goto out; wl1271_acx_sg_enable(wl, wl->sg_enabled); pm_runtime_mark_last_busy(wl->dev); pm_runtime_put_autosuspend(wl->dev); out: mutex_unlock(&wl->mutex); return count; } static DEVICE_ATTR_RW(bt_coex_state); static ssize_t hw_pg_ver_show(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_RO(hw_pg_ver); 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 = kobj_to_dev(kobj); struct wl1271 *wl = dev_get_drvdata(dev); ssize_t len; int ret; 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 const struct bin_attribute fwlog_attr = { .attr = { .name = "fwlog", .mode = 0400 }, .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); }