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");  |