blob: d7c69dba31e43c66316cfc6430b634e79034a9ad [file] [log] [blame]
/* Copyright (c) 2009-2010, 2013 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only 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.
*/
/*
* Bluetooth Power Switch Module
* controls power to external Bluetooth device
* with interface to power management device
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/rfkill.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
static struct of_device_id ar3002_match_table[] = {
{ .compatible = "qca,ar3002" },
{}
};
static int bt_reset_gpio;
static bool previous;
static int bluetooth_power(int on)
{
int rc;
pr_debug("%s bt_gpio= %d\n", __func__, bt_reset_gpio);
if (on) {
rc = gpio_direction_output(bt_reset_gpio, 1);
if (rc) {
pr_err("%s: Unable to set direction\n", __func__);
return rc;
}
msleep(100);
} else {
gpio_set_value(bt_reset_gpio, 0);
rc = gpio_direction_input(bt_reset_gpio);
if (rc) {
pr_err("%s: Unable to set direction\n", __func__);
return rc;
}
msleep(100);
}
return 0;
}
static int bluetooth_toggle_radio(void *data, bool blocked)
{
int ret = 0;
int (*power_control)(int enable);
power_control = data;
if (previous != blocked)
ret = (*power_control)(!blocked);
if (!ret)
previous = blocked;
return ret;
}
static const struct rfkill_ops bluetooth_power_rfkill_ops = {
.set_block = bluetooth_toggle_radio,
};
static int bluetooth_power_rfkill_probe(struct platform_device *pdev)
{
struct rfkill *rfkill;
int ret;
rfkill = rfkill_alloc("bt_power", &pdev->dev, RFKILL_TYPE_BLUETOOTH,
&bluetooth_power_rfkill_ops,
pdev->dev.platform_data);
if (!rfkill) {
dev_err(&pdev->dev, "rfkill allocate failed\n");
return -ENOMEM;
}
/* force Bluetooth off during init to allow for user control */
rfkill_init_sw_state(rfkill, 1);
previous = 1;
ret = rfkill_register(rfkill);
if (ret) {
dev_err(&pdev->dev, "rfkill register failed=%d\n", ret);
rfkill_destroy(rfkill);
return ret;
}
platform_set_drvdata(pdev, rfkill);
return 0;
}
static void bluetooth_power_rfkill_remove(struct platform_device *pdev)
{
struct rfkill *rfkill;
dev_dbg(&pdev->dev, "%s\n", __func__);
rfkill = platform_get_drvdata(pdev);
if (rfkill)
rfkill_unregister(rfkill);
rfkill_destroy(rfkill);
platform_set_drvdata(pdev, NULL);
}
static int __devinit bt_power_probe(struct platform_device *pdev)
{
int ret = 0;
dev_dbg(&pdev->dev, "%s\n", __func__);
if (!pdev->dev.platform_data) {
/* Update the platform data if the
device node exists as part of device tree.*/
if (pdev->dev.of_node) {
pdev->dev.platform_data = bluetooth_power;
} else {
dev_err(&pdev->dev, "device node not set\n");
return -ENOSYS;
}
}
if (pdev->dev.of_node) {
bt_reset_gpio = of_get_named_gpio(pdev->dev.of_node,
"qca,bt-reset-gpio", 0);
if (bt_reset_gpio < 0) {
pr_err("bt-reset-gpio not available");
return bt_reset_gpio;
}
}
ret = gpio_request(bt_reset_gpio, "bt sys_rst_n");
if (ret) {
pr_err("%s: unable to request gpio %d (%d)\n",
__func__, bt_reset_gpio, ret);
return ret;
}
/* When booting up, de-assert BT reset pin */
ret = gpio_direction_output(bt_reset_gpio, 0);
if (ret) {
pr_err("%s: Unable to set direction\n", __func__);
return ret;
}
ret = bluetooth_power_rfkill_probe(pdev);
return ret;
}
static int __devexit bt_power_remove(struct platform_device *pdev)
{
dev_dbg(&pdev->dev, "%s\n", __func__);
bluetooth_power_rfkill_remove(pdev);
return 0;
}
static struct platform_driver bt_power_driver = {
.probe = bt_power_probe,
.remove = __devexit_p(bt_power_remove),
.driver = {
.name = "bt_power",
.owner = THIS_MODULE,
.of_match_table = ar3002_match_table,
},
};
static int __init bluetooth_power_init(void)
{
int ret;
ret = platform_driver_register(&bt_power_driver);
return ret;
}
static void __exit bluetooth_power_exit(void)
{
platform_driver_unregister(&bt_power_driver);
}
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("MSM Bluetooth power control driver");
MODULE_VERSION("1.40");
module_init(bluetooth_power_init);
module_exit(bluetooth_power_exit);