blob: bd3aa84210d032aaab40794c7d5af20f5cad0e72 [file] [log] [blame]
/*
* DHD BT WiFi Coex RegON Coordinator
*
* Copyright (C) 2020, Broadcom.
*
* Unless you and Broadcom execute a separate written software license
* agreement governing use of this software, this software is licensed to you
* under the terms of the GNU General Public License version 2 (the "GPL"),
* available at http://www.broadcom.com/licenses/GPLv2.php, with the
* following added to such license:
*
* As a special exception, the copyright holders of this software give you
* permission to link this software with independent modules, and to copy and
* distribute the resulting executable under terms of your choice, provided that
* you also meet, for each linked independent module, the terms and conditions of
* the license of that module. An independent module is a module which is not
* derived from this software. The special exception does not apply to any
* modifications of the software.
*
*
* <<Broadcom-WL-IPTag/Open:>>
*
*/
#include <linux/cdev.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
#include <linux/eventpoll.h>
#include <linux/version.h>
#define DESCRIPTION "Broadcom WiFi BT Regon coordinator Driver"
#define AUTHOR "Broadcom Corporation"
#define DEVICE_NAME "wbrc"
#define CLASS_NAME "bcm"
#ifndef TRUE
#define TRUE (1)
#endif
#ifndef FALSE
#define FALSE (0)
#endif
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0))
typedef unsigned int __poll_t;
#endif
/*
* 4 byte message to sync with BT stack.
* Byte 0 - header
* Byte 1 - length of LTV now fixed to 2
* Byte 2 - type
* Byte 3 - command value
*/
#define WBRC_MSG_LEN 4u
/* Below defines to be mapped in the user space. */
/* TODO have these as enums and define new structure with members */
/* Byte 0 - Define header for direction of command */
#define HEADER_DIR_WL2BT 0x01
#define HEADER_DIR_BT2WL 0x02
/*
* Byte 2 - Define Type of Command (Followed LTV format)
* wifi/bt, signal/ack types
*/
#define TYPE_WIFI_CMD 0x01
#define TYPE_WIFI_ACK 0x02
#define TYPE_BT_CMD 0x03
#define TYPE_BT_ACK 0x04
/* Byte 3 - Define Value field: commands/acks */
#define CMD_RESET_WIFI 0x40
#define CMD_RESET_WIFI_WITH_ACK 0x41
#define CMD_RESET_BT 0x42
#define CMD_RESET_BT_WITH_ACK 0x43
#define ACK_RESET_WIFI_COMPLETE 0x80
#define ACK_RESET_BT_COMPLETE 0x81
struct wbrc_pvt_data {
int wbrc_bt_dev_major_number; /* BT char dev major number */
struct class *wbrc_bt_dev_class; /* BT char dev class */
struct device *wbrc_bt_dev_device; /* BT char dev */
struct mutex wbrc_mutex; /* mutex to synchronise */
bool bt_dev_opened; /* To check if bt dev open is called */
wait_queue_head_t bt_reset_waitq; /* waitq to wait till bt reset is done */
unsigned int bt_reset_ack; /* condition variable to be check for bt reset */
wait_queue_head_t wlan_reset_waitq; /* waitq to wait till wlan reset is done */
unsigned int wlan_reset_ack; /* condition variable to be check for wlan reset */
wait_queue_head_t outmsg_waitq; /* wait queue for poll */
char wl2bt_message[WBRC_MSG_LEN]; /* message to communicate with Bt stack */
bool read_data_available; /* condition to check if read data is present */
};
static struct wbrc_pvt_data *g_wbrc_data;
#define WBRC_LOCK(wbrc_data) {if (wbrc_data) mutex_lock(&(wbrc_data)->wbrc_mutex);}
#define WBRC_UNLOCK(wbrc_data) {if (wbrc_data) mutex_unlock(&(wbrc_data)->wbrc_mutex);}
int wbrc_wl2bt_reset(void);
int wbrc_bt_reset_ack(struct wbrc_pvt_data *wbrc_data);
int wbrc_bt2wl_reset(void);
int wbrc_wl_reset_ack(struct wbrc_pvt_data *wbrc_data);
static int wbrc_bt_dev_open(struct inode *, struct file *);
static int wbrc_bt_dev_release(struct inode *, struct file *);
static ssize_t wbrc_bt_dev_read(struct file *, char *, size_t, loff_t *);
static ssize_t wbrc_bt_dev_write(struct file *, const char *, size_t, loff_t *);
static __poll_t wbrc_bt_dev_poll(struct file *filep, poll_table *wait);
static struct file_operations wbrc_bt_dev_fops = {
.open = wbrc_bt_dev_open,
.read = wbrc_bt_dev_read,
.write = wbrc_bt_dev_write,
.release = wbrc_bt_dev_release,
.poll = wbrc_bt_dev_poll,
};
static ssize_t wbrc_bt_dev_read(struct file *filep, char *buffer, size_t len,
loff_t *offset)
{
struct wbrc_pvt_data *wbrc_data = filep->private_data;
int err_count = 0;
int ret = 0;
WBRC_LOCK(wbrc_data);
pr_info("%s\n", __func__);
if (wbrc_data->read_data_available == FALSE) {
goto exit;
}
if (len < WBRC_MSG_LEN) {
pr_err("%s: invalid length:%d\n", __func__, (int)len);
ret = -EFAULT;
goto exit;
}
err_count = copy_to_user(buffer, &wbrc_data->wl2bt_message,
sizeof(wbrc_data->wl2bt_message));
if (err_count == 0) {
pr_info("Sent %d bytes\n",
(int)sizeof(wbrc_data->wl2bt_message));
err_count = sizeof(wbrc_data->wl2bt_message);
} else {
pr_err("Failed to send %d bytes\n", err_count);
ret = -EFAULT;
}
wbrc_data->read_data_available = FALSE;
exit:
WBRC_UNLOCK(wbrc_data);
return ret;
}
static ssize_t wbrc_bt_dev_write(struct file *filep, const char *buffer,
size_t len, loff_t *offset)
{
struct wbrc_pvt_data *wbrc_data = filep->private_data;
int err_count = 0;
int ret = 0;
char message[WBRC_MSG_LEN] = {};
WBRC_LOCK(wbrc_data);
pr_info("%s Received %zu bytes\n", __func__, len);
if (len < WBRC_MSG_LEN) {
pr_err("%s: Received malformed packet:%d\n", __func__, (int)len);
ret = -EFAULT;
goto exit;
}
err_count = copy_from_user(message, buffer, len);
if (err_count) {
pr_err("%s: copy_from_user failed:%d\n", __func__, err_count);
ret = -EFAULT;
goto exit;
}
if (message[0] != HEADER_DIR_BT2WL) {
pr_err("%s: invalid header:%d\n", __func__, message[0]);
ret = -EFAULT;
goto exit;
}
if (message[2] == TYPE_BT_CMD) {
switch (message[3]) {
case CMD_RESET_WIFI:
pr_info("RCVD CMD_RESET_WIFI\n");
break;
case CMD_RESET_WIFI_WITH_ACK:
pr_info("RCVD CMD_RESET_WIFI_WITH_ACK\n");
break;
}
}
if (message[2] == TYPE_BT_ACK && message[3] == ACK_RESET_BT_COMPLETE) {
pr_info("RCVD ACK_RESET_BT_COMPLETE");
wbrc_bt_reset_ack(wbrc_data);
}
exit:
WBRC_UNLOCK(wbrc_data);
return ret;
}
static __poll_t wbrc_bt_dev_poll(struct file *filep, poll_table *wait)
{
struct wbrc_pvt_data *wbrc_data = filep->private_data;
__poll_t mask = 0;
poll_wait(filep, &wbrc_data->outmsg_waitq, wait);
if (wbrc_data->read_data_available)
mask |= EPOLLIN | EPOLLRDNORM;
if (!wbrc_data->bt_dev_opened)
mask |= EPOLLHUP;
return mask;
}
static int wbrc_bt_dev_open(struct inode *inodep, struct file *filep)
{
struct wbrc_pvt_data *wbrc_data = g_wbrc_data;
int ret = 0;
WBRC_LOCK(wbrc_data);
if (wbrc_data->bt_dev_opened) {
pr_err("%s already opened\n", __func__);
ret = -EFAULT;
goto exit;
}
wbrc_data->bt_dev_opened = TRUE;
pr_info("%s Device opened %d time(s)\n", __func__,
wbrc_data->bt_dev_opened);
filep->private_data = wbrc_data;
exit:
WBRC_UNLOCK(wbrc_data);
return ret;
}
static int wbrc_bt_dev_release(struct inode *inodep, struct file *filep)
{
struct wbrc_pvt_data *wbrc_data = filep->private_data;
WBRC_LOCK(wbrc_data);
pr_info("%s Device closed %d\n", __func__, wbrc_data->bt_dev_opened);
wbrc_data->bt_dev_opened = FALSE;
WBRC_UNLOCK(wbrc_data);
wake_up_interruptible(&wbrc_data->outmsg_waitq);
return 0;
}
void wbrc_signal_bt_reset(struct wbrc_pvt_data *wbrc_data)
{
pr_info("%s\n", __func__);
/* Below message will be read by userspace using .read */
wbrc_data->wl2bt_message[0] = HEADER_DIR_WL2BT; // Minimal Header
wbrc_data->wl2bt_message[1] = 2; // Length
wbrc_data->wl2bt_message[2] = TYPE_WIFI_CMD; // Type
wbrc_data->wl2bt_message[3] = CMD_RESET_BT_WITH_ACK; // Value
wbrc_data->read_data_available = TRUE;
smp_wmb();
wake_up_interruptible(&wbrc_data->outmsg_waitq);
}
int wbrc_init(void)
{
int err = 0;
struct wbrc_pvt_data *wbrc_data;
pr_info("%s\n", __func__);
wbrc_data = kzalloc(sizeof(struct wbrc_pvt_data), GFP_KERNEL);
if (wbrc_data == NULL) {
return -ENOMEM;
}
mutex_init(&wbrc_data->wbrc_mutex);
init_waitqueue_head(&wbrc_data->bt_reset_waitq);
init_waitqueue_head(&wbrc_data->wlan_reset_waitq);
init_waitqueue_head(&wbrc_data->outmsg_waitq);
g_wbrc_data = wbrc_data;
wbrc_data->wbrc_bt_dev_major_number = register_chrdev(0, DEVICE_NAME, &wbrc_bt_dev_fops);
err = wbrc_data->wbrc_bt_dev_major_number;
if (wbrc_data->wbrc_bt_dev_major_number < 0) {
pr_alert("wbrc_sequencer failed to register a major number\n");
goto err_register;
}
wbrc_data->wbrc_bt_dev_class = class_create(THIS_MODULE, CLASS_NAME);
err = PTR_ERR(wbrc_data->wbrc_bt_dev_class);
if (IS_ERR(wbrc_data->wbrc_bt_dev_class)) {
pr_alert("Failed to register device class\n");
goto err_class;
}
wbrc_data->wbrc_bt_dev_device = device_create(
wbrc_data->wbrc_bt_dev_class, NULL, MKDEV(wbrc_data->wbrc_bt_dev_major_number, 0),
NULL, DEVICE_NAME);
err = PTR_ERR(wbrc_data->wbrc_bt_dev_device);
if (IS_ERR(wbrc_data->wbrc_bt_dev_device)) {
pr_alert("Failed to create the device\n");
goto err_device;
}
pr_info("device class created correctly\n");
return 0;
err_device:
class_destroy(wbrc_data->wbrc_bt_dev_class);
err_class:
unregister_chrdev(wbrc_data->wbrc_bt_dev_major_number, DEVICE_NAME);
err_register:
kfree(wbrc_data);
g_wbrc_data = NULL;
return err;
}
void wbrc_exit(void)
{
struct wbrc_pvt_data *wbrc_data = g_wbrc_data;
pr_info("%s\n", __func__);
wake_up_interruptible(&wbrc_data->outmsg_waitq);
device_destroy(wbrc_data->wbrc_bt_dev_class, MKDEV(wbrc_data->wbrc_bt_dev_major_number, 0));
class_destroy(wbrc_data->wbrc_bt_dev_class);
unregister_chrdev(wbrc_data->wbrc_bt_dev_major_number, DEVICE_NAME);
kfree(wbrc_data);
g_wbrc_data = NULL;
}
#ifndef BCMDHD_MODULAR
/* Required only for Built-in DHD */
module_init(wbrc_init);
module_exit(wbrc_exit);
#endif /* BOARD_MODULAR */
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION(DESCRIPTION);
MODULE_AUTHOR(AUTHOR);
/*
* Wait until the condition *var == condition is met.
* Returns 0 if the @condition evaluated to false after the timeout elapsed
* Returns 1 if the @condition evaluated to true
*/
#define WBRC_RESET_WAIT_TIMEOUT 4000
int
wbrc_reset_wait_on_condition(wait_queue_head_t *reset_waitq, uint *var, uint condition)
{
int timeout;
/* Convert timeout in millsecond to jiffies */
timeout = msecs_to_jiffies(WBRC_RESET_WAIT_TIMEOUT);
timeout = wait_event_timeout(*reset_waitq, (*var == condition), timeout);
return timeout;
}
/* WBRC_LOCK should be held from caller */
int wbrc_bt_reset_ack(struct wbrc_pvt_data *wbrc_data)
{
pr_info("%s\n", __func__);
wbrc_data->bt_reset_ack = TRUE;
smp_wmb();
wake_up(&wbrc_data->bt_reset_waitq);
return 0;
}
int wbrc_wl2bt_reset(void)
{
int ret = 0;
struct wbrc_pvt_data *wbrc_data = g_wbrc_data;
pr_info("%s\n", __func__);
WBRC_LOCK(wbrc_data);
if (!wbrc_data->bt_dev_opened) {
pr_info("%s: no BT\n", __func__);
WBRC_UNLOCK(wbrc_data);
return ret;
}
wbrc_data->bt_reset_ack = FALSE;
wbrc_signal_bt_reset(wbrc_data);
WBRC_UNLOCK(wbrc_data);
/* Wait till BT reset is done */
wbrc_reset_wait_on_condition(&wbrc_data->bt_reset_waitq,
&wbrc_data->bt_reset_ack, TRUE);
if (wbrc_data->bt_reset_ack == FALSE) {
pr_err("%s: BT reset timeout\n", __func__);
ret = -1;
}
return ret;
}
EXPORT_SYMBOL(wbrc_wl2bt_reset);
int wbrc_signal_wlan_reset(struct wbrc_pvt_data *wbrc_data)
{
/* TODO call dhd reset, right now just send ack from here */
wbrc_wl_reset_ack(wbrc_data);
return 0;
}
/* WBRC_LOCK should be held from caller, this will be called from DHD */
int wbrc_wl_reset_ack(struct wbrc_pvt_data *wbrc_data)
{
pr_info("%s\n", __func__);
wbrc_data->wlan_reset_ack = TRUE;
smp_wmb();
wake_up(&wbrc_data->wlan_reset_waitq);
return 0;
}
EXPORT_SYMBOL(wbrc_wl_reset_ack);
int wbrc_bt2wl_reset(void)
{
int ret = 0;
struct wbrc_pvt_data *wbrc_data = g_wbrc_data;
pr_info("%s\n", __func__);
WBRC_LOCK(wbrc_data);
wbrc_data->wlan_reset_ack = FALSE;
wbrc_signal_wlan_reset(wbrc_data);
/* Wait till WLAN reset is done */
wbrc_reset_wait_on_condition(&wbrc_data->wlan_reset_waitq,
&wbrc_data->wlan_reset_ack, TRUE);
if (wbrc_data->wlan_reset_ack == FALSE) {
pr_err("%s: WLAN reset timeout\n", __func__);
ret = -1;
}
WBRC_UNLOCK(wbrc_data);
return ret;
}
EXPORT_SYMBOL(wbrc_bt2wl_reset);