diff options
Diffstat (limited to 'net/rfkill/rfkill-wl18xx.c')
| -rw-r--r-- | net/rfkill/rfkill-wl18xx.c | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/net/rfkill/rfkill-wl18xx.c b/net/rfkill/rfkill-wl18xx.c new file mode 100644 index 00000000000..1318e1ed869 --- /dev/null +++ b/net/rfkill/rfkill-wl18xx.c @@ -0,0 +1,364 @@ +/* + * Bluetooth TI wl18xx rfkill power control via GPIO + * + * Copyright (C) 2014 Motorola, Inc. + * Copyright (C) 2008 Texas Instruments + * Initial code: Pavan Savoy <pavan.savoy@gmail.com> (wl18xx_power.c) + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/gpio.h> +#include <linux/rfkill.h> +#include <linux/platform_device.h> +#include <linux/rfkill-wl18xx.h> +#include <linux/delay.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> + +enum wl18xx_devices { + WL18XX_BLUETOOTH = 0, + WL18XX_FM, + WL18XX_MAX_DEV, +}; + +struct wl18xx_rfkill_driver_data { + struct wl18xx_rfkill_platform_data *pdata; + struct rfkill *rfkill[WL18XX_MAX_DEV]; +}; + +#define WL18XX_BT_SUPPORTED(x) ((x)->bt_enable_gpio >= 0) +#define WL18XX_FM_SUPPORTED(x) ((x)->fm_enable_gpio >= 0) + +static void wl18xx_rfkill_destroy(struct wl18xx_rfkill_driver_data *p_drvdata); + +static int wl18xx_bt_rfkill_set_power(void *data, bool blocked) +{ + struct wl18xx_rfkill_platform_data *pdata = + (struct wl18xx_rfkill_platform_data *)data; + + if (blocked) + gpio_set_value(pdata->bt_enable_gpio, 0); + else + gpio_set_value(pdata->bt_enable_gpio, 1); + + return 0; +} + +static int wl18xx_fm_rfkill_set_power(void *data, bool blocked) +{ + struct wl18xx_rfkill_platform_data *pdata = + (struct wl18xx_rfkill_platform_data *)data; + + if (blocked) + gpio_set_value(pdata->fm_enable_gpio, 0); + else + gpio_set_value(pdata->fm_enable_gpio, 1); + + return 0; +} + +static const struct rfkill_ops wl18xx_bt_rfkill_ops = { + .set_block = wl18xx_bt_rfkill_set_power, +}; + +static const struct rfkill_ops wl18xx_fm_rfkill_ops = { + .set_block = wl18xx_fm_rfkill_set_power, +}; + +/** +Added to reset the BT chip after Ram download +*/ +static ssize_t reset_wl18xx_chip(struct device *dev, + struct device_attribute + *attr, const char *buf, size_t size) +{ + struct wl18xx_rfkill_platform_data *pd = dev->platform_data; + + BUG_ON(!pd); + if (WL18XX_BT_SUPPORTED(pd)) { + gpio_set_value(pd->bt_enable_gpio, 0); + msleep(5); + gpio_set_value(pd->bt_enable_gpio, 1); + } + return size; +} +static DEVICE_ATTR(reset_vio, 0200, NULL, reset_wl18xx_chip); + +#ifdef CONFIG_OF +static int wl18xx_rfkill_of_init( + struct wl18xx_rfkill_platform_data *pdata, + struct device_node *dt_node) +{ + int rc; + int bt_fm_support = 0; + int gpio; + + /* Set default values */ + pdata->bt_enable_gpio = -1; + pdata->fm_enable_gpio = -1; + + /* Set default values */ + rc = of_property_read_u32(dt_node, "mot,bt_fm", &bt_fm_support); + if (!rc) { + if (bt_fm_support & 0x01) { + gpio = of_get_named_gpio_flags(dt_node, + "mot,bt_en-gpio", 0, NULL); + pdata->bt_enable_gpio = (gpio < 0) ? -1 : gpio; + } else { + pr_err("wl18xx_rfkill: BT support disabled"); + } + + if (bt_fm_support & 0x02) { + gpio = of_get_named_gpio_flags(dt_node, + "mot,fm_en-gpio", 0, NULL); + pdata->fm_enable_gpio = (gpio < 0) ? -1 : gpio; + } else { + pr_err("wl18xx_rfkill: FM support disabled"); + } + } else { + pr_err("%s: failed to read device tree", __func__); + } + + return rc; +} +#endif + +static int wl18xx_rfkill_gpio_init(struct wl18xx_rfkill_platform_data *pdata) +{ + int rc = 0; + struct gpio wl18xx_rfkill_bt_gpio[] = { + {pdata->bt_enable_gpio, GPIOF_OUT_INIT_LOW, "wl18xx_bt_en"}, + }; + struct gpio wl18xx_rfkill_fm_gpio[] = { + {pdata->fm_enable_gpio, GPIOF_OUT_INIT_LOW, "wl18xx_fm_en"}, + }; + + if (WL18XX_BT_SUPPORTED(pdata)) { + rc = gpio_request_array(wl18xx_rfkill_bt_gpio, + ARRAY_SIZE(wl18xx_rfkill_bt_gpio)); + if (rc) + pr_err("%s: Failed to request BT GPIOs\n", __func__); + } + + if (WL18XX_FM_SUPPORTED(pdata)) { + rc = gpio_request_array(wl18xx_rfkill_fm_gpio, + ARRAY_SIZE(wl18xx_rfkill_fm_gpio)); + if (rc) + pr_err("%s: Failed to request FM GPIOs\n", __func__); + } + + return rc; +} + +static int wl18xx_rfkill_probe(struct platform_device *pdev) +{ + int rc = 0; + struct wl18xx_rfkill_driver_data *dd = NULL; + struct wl18xx_rfkill_platform_data *pd = NULL; + + pr_info("%s\n", __func__); + + dd = kzalloc(sizeof(struct wl18xx_rfkill_driver_data), GFP_KERNEL); + if (dd == NULL) { + pr_err("%s: memory allocation failure\n", __func__); + rc = -ENOMEM; + goto wl18xx_probe_fail; + } + + /* set platform data */ + if (pdev->dev.of_node) { + pd = devm_kzalloc(&pdev->dev, + sizeof(struct wl18xx_rfkill_platform_data), + GFP_KERNEL); + if (!pd) { + pr_err("%s: memory allocation failure\n", __func__); + rc = -ENOMEM; + goto wl18xx_probe_fail; + } + + rc = wl18xx_rfkill_of_init(pd, pdev->dev.of_node); + if (rc) + goto wl18xx_probe_fail; + pdev->dev.platform_data = pd; + } else { + pd = pdev->dev.platform_data; + } + dd->pdata = pd; + + /* set driver data */ + dev_set_drvdata(&pdev->dev, dd); + + /* verify pdata */ + if (!WL18XX_BT_SUPPORTED(pd) && WL18XX_FM_SUPPORTED(pd)) { + pr_err("%s: invalid configuration\n", __func__); + rc = -EINVAL; + goto wl18xx_probe_fail; + } + + /* requesting gpios */ + rc = wl18xx_rfkill_gpio_init(pd); + if (rc) + goto gpio_err; + + if (WL18XX_BT_SUPPORTED(pd)) { + /* init rfkill for BT */ + dd->rfkill[WL18XX_BLUETOOTH] = rfkill_alloc( + "wl18xx Bluetooth", + &pdev->dev, + RFKILL_TYPE_BLUETOOTH, + &wl18xx_bt_rfkill_ops, + (void *)pd); + if (unlikely(!dd->rfkill[WL18XX_BLUETOOTH])) { + pr_err("%s: memory allocation failure\n", __func__); + rc = -ENOMEM; + goto err_rfkill_register; + } + + rfkill_set_states(dd->rfkill[WL18XX_BLUETOOTH], + true, false); + rc = rfkill_register(dd->rfkill[WL18XX_BLUETOOTH]); + if (unlikely(rc)) { + pr_err("%s: failed to register BT rfkill\n", __func__); + goto err_rfkill_register; + } + } + + if (WL18XX_FM_SUPPORTED(pd)) { + /* init rfkill for FM */ + dd->rfkill[WL18XX_FM] = rfkill_alloc( + "wl18xx FM Radio", &pdev->dev, + RFKILL_TYPE_FM, &wl18xx_fm_rfkill_ops, + (void *)pd); + if (unlikely(!dd->rfkill[WL18XX_FM])) { + pr_err("%s: memory allocation failure\n", __func__); + rc = -ENOMEM; + goto err_rfkill_register; + } + + rfkill_set_states(dd->rfkill[WL18XX_FM], + true, false); + rc = rfkill_register(dd->rfkill[WL18XX_FM]); + if (unlikely(rc)) { + pr_err("%s: failed to register BT rfkill\n", __func__); + goto err_rfkill_register; + } + } + + /* Create device file to expose interface to user space to + reset the vio of BT, this should be done independent of whether + BT/FM is initialised as both run on the same w18xx chip + */ + rc = device_create_file(&pdev->dev, &dev_attr_reset_vio); + if (rc) { + pr_err("%s: failed to create sys entry\n", __func__); + goto err_rfkill_register; + } + + goto done; + + /* Clean up for BT generic registration */ +err_rfkill_register: + wl18xx_rfkill_destroy(dd); + +gpio_err: + if (pd->bt_enable_gpio > 0) + gpio_free(pd->bt_enable_gpio); + if (pd->fm_enable_gpio > 0) + gpio_free(pd->fm_enable_gpio); + +wl18xx_probe_fail: + kfree(dd); + + pr_err("%s: failed with error = %d\n", __func__, rc); + +done: + return rc; +} + +static void wl18xx_rfkill_destroy(struct wl18xx_rfkill_driver_data *p_drvdata) +{ + BUG_ON(!p_drvdata); + + if (p_drvdata->rfkill[WL18XX_BLUETOOTH]) { + rfkill_unregister(p_drvdata->rfkill[WL18XX_BLUETOOTH]); + rfkill_destroy(p_drvdata->rfkill[WL18XX_BLUETOOTH]); + } + + if (p_drvdata->rfkill[WL18XX_FM]) { + rfkill_unregister(p_drvdata->rfkill[WL18XX_FM]); + rfkill_destroy(p_drvdata->rfkill[WL18XX_FM]); + } +} + +static int wl18xx_rfkill_remove(struct platform_device *pdev) +{ + struct wl18xx_rfkill_driver_data *dd; + struct wl18xx_rfkill_platform_data *pd; + + dd = dev_get_drvdata(&pdev->dev); + pd = pdev->dev.platform_data; + + /* remove the sys fs file created as part of probe*/ + device_remove_file(&pdev->dev, &dev_attr_reset_vio); + + if (pd->bt_enable_gpio > 0) + gpio_free(pd->bt_enable_gpio); + if (pd->fm_enable_gpio > 0) + gpio_free(pd->fm_enable_gpio); + kfree(pd); + + return 0; +} + +#ifdef CONFIG_OF +static struct of_device_id mot_wl18xx_rfkill_table[] = { + { .compatible = "mot,wl18xx-rfkill",}, + { }, +}; +#else +#define mot_wl18xx_rfkill_table NULL +#endif + +static struct platform_driver wl18xx_rfkill_platform_driver = { + .probe = wl18xx_rfkill_probe, + .remove = wl18xx_rfkill_remove, + .driver = { + .name = "wl18xx-rfkill", + .owner = THIS_MODULE, + .of_match_table = mot_wl18xx_rfkill_table, + }, +}; + +static int __init wl18xx_rfkill_init(void) +{ + return platform_driver_register(&wl18xx_rfkill_platform_driver); +} + +static void __exit wl18xx_rfkill_exit(void) +{ + platform_driver_unregister(&wl18xx_rfkill_platform_driver); +} + +module_init(wl18xx_rfkill_init); +module_exit(wl18xx_rfkill_exit); + +MODULE_ALIAS("platform:wl18xx"); +MODULE_DESCRIPTION("wl18xx-rfkill"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); |