blob: b2076224bc415b5891cc50d8103b7d6645b34932 [file] [log] [blame]
/*
* drivers/amlogic/bluetooth/bt_device.c
*
* Copyright (C) 2017 Amlogic, Inc. 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 as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/leds.h>
#include <linux/gpio.h>
#include <linux/rfkill.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/pinctrl/consumer.h>
#include <linux/amlogic/aml_gpio_consumer.h>
#include <linux/of_gpio.h>
#include <linux/amlogic/cpu_version.h>
#include <linux/amlogic/iomap.h>
#include <linux/io.h>
#include <linux/amlogic/bt_device.h>
#include <linux/random.h>
#ifdef CONFIG_AM_WIFI_SD_MMC
#include <linux/amlogic/wifi_dt.h>
#endif
#include "../../gpio/gpiolib.h"
#include <linux/interrupt.h>
#include <linux/pm_wakeup.h>
#include <linux/pm_wakeirq.h>
#include <linux/irq.h>
#include <linux/input.h>
#ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND
#include <linux/amlogic/pm.h>
static struct early_suspend bt_early_suspend;
#endif
#define BT_RFKILL "bt_rfkill"
#define BT_DBG 0
#if BT_DBG
#define BT_INFO(fmt, args...) \
pr_info("[%s] " fmt, __func__, ##args)
#else
#define BT_INFO(fmt, args...)
#endif
#define BT_ERR(fmt, args...) \
pr_info("[%s] " fmt, __func__, ##args)
char bt_addr[18] = "";
static struct class *bt_addr_class;
static int btwake_evt;
static int btirq_flag;
static int btpower_evt;
static int flag_n;
static int flag_p;
static int cnt;
static ssize_t bt_addr_show(struct class *cls,
struct class_attribute *attr, char *_buf)
{
char local_addr[6];
if (!_buf)
return -EINVAL;
if (strlen(bt_addr) == 0) {
local_addr[0] = 0x22;
local_addr[1] = 0x22;
local_addr[2] = prandom_u32();
local_addr[3] = prandom_u32();
local_addr[4] = prandom_u32();
local_addr[5] = prandom_u32();
sprintf(bt_addr, "%02x:%02x:%02x:%02x:%02x:%02x",
local_addr[0], local_addr[1], local_addr[2],
local_addr[3], local_addr[4], local_addr[5]);
}
return sprintf(_buf, "%s\n", bt_addr);
}
static ssize_t bt_addr_store(struct class *cls,
struct class_attribute *attr, const char __user *buf, size_t count)
{
int ret = -EINVAL;
if (!buf)
return ret;
snprintf(bt_addr, sizeof(bt_addr), "%s", buf);
if (bt_addr[strlen(bt_addr)-1] == '\n')
bt_addr[strlen(bt_addr)-1] = '\0';
BT_INFO("bt_addr=%s\n", bt_addr);
return count;
}
static CLASS_ATTR(value, 0644, bt_addr_show, bt_addr_store);
struct bt_dev_runtime_data {
struct rfkill *bt_rfk;
struct bt_dev_data *pdata;
};
static void bt_device_off(struct bt_dev_data *pdata)
{
#if 0
if (pdata->power_down_disable == 0) {
if ((btpower_evt == 1) && (pdata->gpio_reset > 0)) {
if ((pdata->power_on_pin_OD)
&& (pdata->power_low_level)) {
gpio_direction_input(pdata->gpio_reset);
} else {
gpio_direction_output(pdata->gpio_reset,
pdata->power_low_level);
}
}
if ((btpower_evt == 1) && (pdata->gpio_en > 0)) {
if ((pdata->power_on_pin_OD)
&& (pdata->power_low_level)) {
gpio_direction_input(pdata->gpio_en);
} else {
gpio_direction_output(pdata->gpio_en,
pdata->power_low_level);
}
}
if (btpower_evt == 2)
set_usb_bt_power(0);
if ((btpower_evt == 0) && (pdata->gpio_reset > 0)) {
if ((pdata->power_on_pin_OD)
&& (pdata->power_low_level)) {
gpio_direction_input(pdata->gpio_reset);
} else {
gpio_direction_output(pdata->gpio_reset,
pdata->power_low_level);
}
}
if ((btpower_evt == 0) && (pdata->gpio_en > 0)) {
if ((pdata->power_on_pin_OD)
&& (pdata->power_low_level)) {
gpio_direction_input(pdata->gpio_en);
} else
set_usb_bt_power(0);
}
msleep(20);
}
#endif
}
static void bt_device_init(struct bt_dev_data *pdata)
{
int tmp = 0;
btpower_evt = 0;
btirq_flag = 0;
if (pdata->gpio_reset > 0)
gpio_request(pdata->gpio_reset, BT_RFKILL);
if (pdata->gpio_en > 0)
gpio_request(pdata->gpio_en, BT_RFKILL);
if (pdata->gpio_hostwake > 0) {
gpio_request(pdata->gpio_hostwake, BT_RFKILL);
gpio_direction_output(pdata->gpio_hostwake, 1);
}
if (pdata->gpio_btwakeup > 0) {
gpio_request(pdata->gpio_btwakeup, BT_RFKILL);
gpio_direction_input(pdata->gpio_btwakeup);
}
tmp = pdata->power_down_disable;
pdata->power_down_disable = 0;
bt_device_off(pdata);
pdata->power_down_disable = tmp;
}
static void bt_device_deinit(struct bt_dev_data *pdata)
{
if (pdata->gpio_reset > 0)
gpio_free(pdata->gpio_reset);
if (pdata->gpio_en > 0)
gpio_free(pdata->gpio_en);
btpower_evt = 0;
if (pdata->gpio_hostwake > 0)
gpio_free(pdata->gpio_hostwake);
}
static void bt_device_on(struct bt_dev_data *pdata)
{
if (pdata->power_down_disable == 0) {
if ((btpower_evt == 1) && (pdata->gpio_reset > 0)) {
if ((pdata->power_on_pin_OD)
&& (pdata->power_low_level)) {
gpio_direction_input(pdata->gpio_reset);
} else {
gpio_direction_output(pdata->gpio_reset,
pdata->power_low_level);
}
}
if ((btpower_evt == 1) && (pdata->gpio_en > 0)) {
if ((pdata->power_on_pin_OD)
&& (pdata->power_low_level)) {
gpio_direction_input(pdata->gpio_en);
} else {
gpio_direction_output(pdata->gpio_en,
pdata->power_low_level);
}
}
if (btpower_evt == 2) {
set_usb_bt_power(0);
}
if ((btpower_evt == 0) && (pdata->gpio_reset > 0)) {
if ((pdata->power_on_pin_OD)
&& (pdata->power_low_level)) {
gpio_direction_input(pdata->gpio_reset);
} else {
gpio_direction_output(pdata->gpio_reset,
pdata->power_low_level);
}
}
if ((btpower_evt == 0) && (pdata->gpio_en > 0)) {
if ((pdata->power_on_pin_OD)
&& (pdata->power_low_level)) {
gpio_direction_input(pdata->gpio_en);
} else {
set_usb_bt_power(0);
}
}
msleep(200);
}
if ((btpower_evt == 1) && (pdata->gpio_reset > 0)) {
if ((pdata->power_on_pin_OD)
&& (!pdata->power_low_level)) {
gpio_direction_input(pdata->gpio_reset);
} else {
gpio_direction_output(pdata->gpio_reset,
!pdata->power_low_level);
}
}
if ((btpower_evt == 1) && (pdata->gpio_en > 0)) {
if ((pdata->power_on_pin_OD)
&& (!pdata->power_low_level)) {
gpio_direction_input(pdata->gpio_en);
} else {
gpio_direction_output(pdata->gpio_en,
!pdata->power_low_level);
}
}
if (btpower_evt == 2) {
set_usb_bt_power(1);
}
if ((btpower_evt == 0) && (pdata->gpio_reset > 0)) {
if ((pdata->power_on_pin_OD)
&& (!pdata->power_low_level)) {
gpio_direction_input(pdata->gpio_reset);
} else {
gpio_direction_output(pdata->gpio_reset,
!pdata->power_low_level);
}
}
if ((btpower_evt == 0) && (pdata->gpio_en > 0)) {
if ((pdata->power_on_pin_OD)
&& (!pdata->power_low_level)) {
gpio_direction_input(pdata->gpio_en);
} else {
set_usb_bt_power(1);
}
}
msleep(200);
}
/*The system calls this function when GPIOC_14 interrupt occurs*/
static irqreturn_t bt_interrupt(int irq, void *dev_id)
{
struct bt_dev_data *pdata = (struct bt_dev_data *) dev_id;
if (btirq_flag == 1) {
schedule_work(&pdata->btwakeup_work);
BT_INFO("freeze: test BT IRQ\n");
}
return IRQ_HANDLED;
}
static enum hrtimer_restart btwakeup_timer_handler(struct hrtimer *timer)
{
struct bt_dev_data *pdata = container_of(timer,
struct bt_dev_data, timer);
if (!gpio_get_value(pdata->gpio_btwakeup) && cnt < 5)
cnt++;
if (cnt >= 5 && cnt < 15) {
if (gpio_get_value(pdata->gpio_btwakeup))
flag_p++;
else if (!gpio_get_value(pdata->gpio_btwakeup))
flag_n++;
cnt++;
}
BT_INFO("%s power: %d,netflix:%d\n", __func__, flag_p, flag_n);
if (flag_p >= 7) {
BT_INFO("%s power: %d\n", __func__, flag_p);
btwake_evt = 2;
cnt = 0;
flag_p = 0;
btirq_flag = 0;
input_event(pdata->input_dev,
EV_KEY, KEY_POWER, 1);
input_sync(pdata->input_dev);
input_event(pdata->input_dev,
EV_KEY, KEY_POWER, 0);
input_sync(pdata->input_dev);
} else if (flag_n >= 7) {
BT_INFO("%s netflix: %d\n", __func__, flag_n);
btwake_evt = 2;
cnt = 0;
flag_n = 0;
btirq_flag = 0;
input_event(pdata->input_dev, EV_KEY, 133, 1);
input_sync(pdata->input_dev);
input_event(pdata->input_dev, EV_KEY, 133, 0);
input_sync(pdata->input_dev);
}
if (btwake_evt != 2 && cnt != 0)
hrtimer_start(&pdata->timer,
ktime_set(0, 20*1000000), HRTIMER_MODE_REL);
return HRTIMER_NORESTART;
}
static void get_btwakeup_irq_work(struct work_struct *work)
{
struct bt_dev_data *pdata = container_of(work,
struct bt_dev_data, btwakeup_work);
if (btwake_evt == 2)
return;
BT_INFO("%s", __func__);
hrtimer_start(&pdata->timer,
ktime_set(0, 100*1000000), HRTIMER_MODE_REL);
}
static int bt_set_block(void *data, bool blocked)
{
struct bt_dev_data *pdata = data;
BT_INFO("BT_RADIO going: %s\n", blocked ? "off" : "on");
if (!blocked) {
BT_INFO("AML_BT: going ON,btpower_evt=%d\n", btpower_evt);
bt_device_on(pdata);
} else {
BT_INFO("AML_BT: going OFF,btpower_evt=%d\n", btpower_evt);
bt_device_off(pdata);
}
return 0;
}
static const struct rfkill_ops bt_rfkill_ops = {
.set_block = bt_set_block,
};
#ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND
static void bt_earlysuspend(struct early_suspend *h)
{
}
static void bt_lateresume(struct early_suspend *h)
{
}
#endif
static int bt_suspend(struct platform_device *pdev,
pm_message_t state)
{
struct bt_dev_data *pdata = platform_get_drvdata(pdev);
btwake_evt = 0;
BT_INFO("bt suspend\n");
disable_irq(pdata->irqno_wakeup);
return 0;
}
static int bt_resume(struct platform_device *pdev)
{
int event = 0;
struct bt_dev_data *pdata = platform_get_drvdata(pdev);
BT_INFO("bt resume\n");
enable_irq(pdata->irqno_wakeup);
btwake_evt = 0;
if ((get_resume_method() == RTC_WAKEUP) ||
(get_resume_method() == AUTO_WAKEUP)) {
btwake_evt = 1;
btirq_flag = 1;
flag_n = 0;
flag_p = 0;
cnt = 0;
}
event = sdio_get_vendor();
BT_INFO("event = %d\n", event);
if ((event != 625) && get_resume_method() == BT_WAKEUP) {
input_event(pdata->input_dev,
EV_KEY, KEY_POWER, 1);
input_sync(pdata->input_dev);
input_event(pdata->input_dev,
EV_KEY, KEY_POWER, 0);
input_sync(pdata->input_dev);
}
return 0;
}
static int bt_probe(struct platform_device *pdev)
{
int ret = 0;
const void *prop;
struct rfkill *bt_rfk;
struct bt_dev_data *pdata = NULL;
struct bt_dev_runtime_data *prdata;
struct input_dev *input_dev;
#ifdef CONFIG_OF
if (pdev && pdev->dev.of_node) {
const char *str;
struct gpio_desc *desc;
BT_INFO("enter bt_probe of_node\n");
pdata = kzalloc(sizeof(struct bt_dev_data), GFP_KERNEL);
ret = of_property_read_string(pdev->dev.of_node,
"gpio_reset", &str);
if (ret) {
pr_warn("not get gpio_reset\n");
pdata->gpio_reset = 0;
} else {
desc = of_get_named_gpiod_flags(pdev->dev.of_node,
"gpio_reset", 0, NULL);
pdata->gpio_reset = desc_to_gpio(desc);
}
ret = of_property_read_string(pdev->dev.of_node,
"gpio_en", &str);
if (ret) {
pr_warn("not get gpio_en\n");
pdata->gpio_en = 0;
} else {
desc = of_get_named_gpiod_flags(pdev->dev.of_node,
"gpio_en", 0, NULL);
pdata->gpio_en = desc_to_gpio(desc);
}
ret = of_property_read_string(pdev->dev.of_node,
"gpio_hostwake", &str);
if (ret) {
pr_warn("not get gpio_hostwake\n");
pdata->gpio_hostwake = 0;
} else {
desc = of_get_named_gpiod_flags(pdev->dev.of_node,
"gpio_hostwake", 0, NULL);
pdata->gpio_hostwake = desc_to_gpio(desc);
}
/*gpio_btwakeup = BT_WAKE_HOST*/
ret = of_property_read_string(pdev->dev.of_node,
"gpio_btwakeup", &str);
if (ret) {
pr_warn("not get gpio_btwakeup\n");
pdata->gpio_btwakeup = 0;
} else {
desc = of_get_named_gpiod_flags(pdev->dev.of_node,
"gpio_btwakeup", 0, NULL);
pdata->gpio_btwakeup = desc_to_gpio(desc);
}
prop = of_get_property(pdev->dev.of_node,
"power_low_level", NULL);
if (prop) {
BT_INFO("power on valid level is low");
pdata->power_low_level = 1;
} else {
BT_INFO("power on valid level is high");
pdata->power_low_level = 0;
pdata->power_on_pin_OD = 0;
}
ret = of_property_read_u32(pdev->dev.of_node,
"power_on_pin_OD", &pdata->power_on_pin_OD);
if (ret)
pdata->power_on_pin_OD = 0;
BT_INFO("bt: power_on_pin_OD = %d;\n", pdata->power_on_pin_OD);
ret = of_property_read_u32(pdev->dev.of_node,
"power_off_flag", &pdata->power_off_flag);
if (ret)
pdata->power_off_flag = 1;/*bt poweroff*/
BT_INFO("bt: power_off_flag = %d;\n", pdata->power_off_flag);
ret = of_property_read_u32(pdev->dev.of_node,
"power_down_disable", &pdata->power_down_disable);
if (ret)
pdata->power_down_disable = 0;
BT_INFO("dis power down = %d;\n", pdata->power_down_disable);
} else if (pdev) {
pdata = (struct bt_dev_data *)(pdev->dev.platform_data);
} else {
ret = -ENOENT;
goto err_res;
}
#else
pdata = (struct bt_dev_data *)(pdev->dev.platform_data);
#endif
bt_addr_class = class_create(THIS_MODULE, "bt_addr");
ret = class_create_file(bt_addr_class, &class_attr_value);
bt_device_init(pdata);
if (pdata->power_down_disable == 1) {
pdata->power_down_disable = 0;
bt_device_on(pdata);
pdata->power_down_disable = 1;
}
/* default to bluetooth off */
/* rfkill_switch_all(RFKILL_TYPE_BLUETOOTH, 1); */
/* bt_device_off(pdata); */
bt_rfk = rfkill_alloc("bt-dev", &pdev->dev,
RFKILL_TYPE_BLUETOOTH,
&bt_rfkill_ops, pdata);
if (!bt_rfk) {
BT_ERR("rfk alloc fail\n");
ret = -ENOMEM;
goto err_rfk_alloc;
}
rfkill_init_sw_state(bt_rfk, false);
ret = rfkill_register(bt_rfk);
if (ret) {
pr_err("rfkill_register fail\n");
goto err_rfkill;
}
prdata = kmalloc(sizeof(struct bt_dev_runtime_data),
GFP_KERNEL);
if (!prdata)
goto err_rfkill;
prdata->bt_rfk = bt_rfk;
prdata->pdata = pdata;
platform_set_drvdata(pdev, prdata);
#ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND
bt_early_suspend.level =
EARLY_SUSPEND_LEVEL_DISABLE_FB;
bt_early_suspend.suspend = bt_earlysuspend;
bt_early_suspend.resume = bt_lateresume;
bt_early_suspend.param = pdev;
register_early_suspend(&bt_early_suspend);
#endif
platform_set_drvdata(pdev, pdata);
/*1.Set BT_WAKE_HOST to the input state;*/
/*2.Get interrupt number(irqno_wakeup).*/
pdata->irqno_wakeup = gpio_to_irq(pdata->gpio_btwakeup);
/*Register interrupt service function*/
ret = request_irq(pdata->irqno_wakeup, bt_interrupt,
IRQF_TRIGGER_FALLING, "bt-irq", (void *)pdata);
if (ret < 0)
pr_err("request_irq error ret=%d\n", ret);
ret = device_init_wakeup(&pdev->dev, 1);
if (ret)
pr_err("device_init_wakeup failed: %d\n", ret);
/*Wake up the interrupt*/
ret = dev_pm_set_wake_irq(&pdev->dev, pdata->irqno_wakeup);
if (ret)
pr_err("dev_pm_set_wake_irq failed: %d\n", ret);
INIT_WORK(&pdata->btwakeup_work, get_btwakeup_irq_work);
//input
input_dev = input_allocate_device();
if (!input_dev) {
pr_err("[abner test]input_allocate_device failed: %d\n", ret);
return -EINVAL;
}
set_bit(EV_KEY, input_dev->evbit);
set_bit(KEY_POWER, input_dev->keybit);
set_bit(133, input_dev->keybit);
input_dev->name = "input_btrcu";
input_dev->phys = "input_btrcu/input0";
input_dev->dev.parent = &pdev->dev;
input_dev->id.bustype = BUS_ISA;
input_dev->id.vendor = 0x0001;
input_dev->id.product = 0x0001;
input_dev->id.version = 0x0100;
input_dev->rep[REP_DELAY] = 0xffffffff;
input_dev->rep[REP_PERIOD] = 0xffffffff;
input_dev->keycodesize = sizeof(unsigned short);
input_dev->keycodemax = 0x1ff;
ret = input_register_device(input_dev);
if (ret < 0) {
pr_err("[abner test]input_register_device failed: %d\n", ret);
input_free_device(input_dev);
return -EINVAL;
}
pdata->input_dev = input_dev;
hrtimer_init(&pdata->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
pdata->timer.function = btwakeup_timer_handler;
return 0;
err_rfkill:
rfkill_destroy(bt_rfk);
err_rfk_alloc:
bt_device_deinit(pdata);
err_res:
return ret;
}
static int bt_remove(struct platform_device *pdev)
{
struct bt_dev_runtime_data *prdata =
platform_get_drvdata(pdev);
struct rfkill *rfk = NULL;
struct bt_dev_data *pdata = NULL;
platform_set_drvdata(pdev, NULL);
if (prdata) {
rfk = prdata->bt_rfk;
pdata = prdata->pdata;
}
if (pdata) {
bt_device_deinit(pdata);
kfree(pdata);
}
if (rfk) {
rfkill_unregister(rfk);
rfkill_destroy(rfk);
}
rfk = NULL;
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id bt_dev_dt_match[] = {
{ .compatible = "amlogic, bt-dev",
},
{},
};
#else
#define bt_dev_dt_match NULL
#endif
static struct platform_driver bt_driver = {
.driver = {
.name = "bt-dev",
.of_match_table = bt_dev_dt_match,
},
.probe = bt_probe,
.remove = bt_remove,
.suspend = bt_suspend,
.resume = bt_resume,
};
static int __init bt_init(void)
{
BT_INFO("amlogic rfkill init\n");
return platform_driver_register(&bt_driver);
}
static void __exit bt_exit(void)
{
platform_driver_unregister(&bt_driver);
}
module_param(btpower_evt, int, 0664);
MODULE_PARM_DESC(btpower_evt, "btpower_evt");
module_param(btwake_evt, int, 0664);
MODULE_PARM_DESC(btwake_evt, "btwake_evt");
module_init(bt_init);
module_exit(bt_exit);
MODULE_DESCRIPTION("bt rfkill");
MODULE_AUTHOR("");
MODULE_LICENSE("GPL");
/**************** bt mac *****************/
static int __init mac_addr_set(char *line)
{
if (line) {
BT_INFO("try to read bt mac from emmc key!\n");
strncpy(bt_addr, line, sizeof(bt_addr)-1);
bt_addr[sizeof(bt_addr)-1] = '\0';
}
return 1;
}
__setup("mac_bt=", mac_addr_set);