blob: b553dc0f6eb2ec3f543695fc7c319a16e22759aa [file] [log] [blame]
/*
* rfkill power contorl for wlan/bt on ASR platform
*
* Copyright (C) 2018 ASR, Inc.
*
* Authors: Yi Zhang <yizhang@marvell.com>
* 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/debugfs.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/suspend.h>
#include <linux/clk.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#include <linux/edge_wakeup_mmp.h>
#define ASR_DEV_NAME "asr-gnss"
#define GPS_STATUS_LENS 16
struct asr_gnss_platform_data {
char chip_status[GPS_STATUS_LENS];
int gpio_reset;
int gpio_edge_wakeup;
int gpio_wakeup_device;
struct clk *clk_26m_out;
};
static DEFINE_MUTEX(asr_gnss_opt_mutex);
static void gnss_edge_wakeup(int irq, void *p_rsv)
{
pr_debug("gnss_edge_wakeup event +++++\n");
}
static void asr_gnss_platform_data_init(
struct asr_gnss_platform_data *pdata)
{
/* all intems are invalid just after alloc */
pdata->gpio_reset = -1;
pdata->gpio_edge_wakeup = -1;
pdata->gpio_wakeup_device = -1;
memset(pdata->chip_status, 0, GPS_STATUS_LENS);
}
static struct asr_gnss_platform_data
*asr_gnss_platform_data_alloc(struct platform_device *pdev)
{
struct asr_gnss_platform_data *pdata;
/* create a new one and init it */
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (pdata) {
asr_gnss_platform_data_init(pdata);
return pdata;
}
return NULL;
}
/* GPS: power on/off control */
static void gps_power_on(struct device *dev)
{
struct asr_gnss_platform_data *info = dev->platform_data;
if (info->gpio_edge_wakeup >= 0) {
request_mfp_edge_wakeup(info->gpio_edge_wakeup,
gnss_edge_wakeup, NULL, dev);
}
pr_debug("gps chip powered on\n");
return;
}
static void gps_power_off(struct asr_gnss_platform_data *info)
{
pr_debug("gps chip powered off\n");
return;
}
static int gps_reset(struct asr_gnss_platform_data *info, int flag)
{
int gpio_reset = info->gpio_reset;
if (gpio_reset < 0)
return -1;
if (gpio_request(gpio_reset, "asr5311 rst")) {
pr_info("gpio %d request failed\n", gpio_reset);
return -1;
}
gpio_direction_output(gpio_reset, flag);
gpio_free(gpio_reset);
return 0;
}
static int gps_wakeup(struct asr_gnss_platform_data *info, int flag)
{
int host_wakeup = info->gpio_wakeup_device;
if (host_wakeup < 0)
return -1;
if (gpio_request(host_wakeup, "host wakeup asr5311")) {
pr_info("gpio %d request failed\n", host_wakeup);
return -1;
}
gpio_direction_output(host_wakeup, flag);
gpio_free(host_wakeup);
return 0;
}
static ssize_t gps_ctrl(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
static char msg[256];
int flag, ret;
struct asr_gnss_platform_data *info = dev->platform_data;
count = (count > 255) ? 255 : count;
memset(msg, 0, count);
sscanf(buf, "%s", info->chip_status);
if (!strncmp(buf, "off", 3)) {
strncpy(info->chip_status, "off", 4);
gps_power_off(info);
} else if (!strncmp(buf, "on", 2)) {
strncpy(info->chip_status, "on", 3);
gps_power_on(dev);
} else if (!strncmp(buf, "reset", 5)) {
strncpy(info->chip_status, "reset", 6);
ret = sscanf(buf, "%s %d", msg, &flag);
if (ret == 2)
gps_reset(info, flag);
} else if (!strncmp(buf, "wakeup", 6)) {
strncpy(info->chip_status, "wakeup", 7);
ret = sscanf(buf, "%s %d", msg, &flag);
if (ret == 2)
gps_wakeup(info, flag);
} else
pr_err("usage wrong\n");
return count;
}
static ssize_t gps_status(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct asr_gnss_platform_data *info = dev->platform_data;
return sprintf(buf, "status: %s\n", info->chip_status);
}
static DEVICE_ATTR(ctrl, S_IWUSR, gps_status, gps_ctrl);
static DEVICE_ATTR(status, S_IRUSR, gps_status, NULL);
static const struct attribute *gps_attrs[] = {
&dev_attr_ctrl.attr,
&dev_attr_status.attr,
NULL,
};
static const struct attribute_group gps_attr_group = {
.attrs = (struct attribute **)gps_attrs,
};
#ifdef CONFIG_OF
static const struct of_device_id asr_gnss_of_match[] = {
{
.compatible = "asr,asr-gnss",
},
{},
};
MODULE_DEVICE_TABLE(of, asr_gnss_of_match);
static int asr_gnss_probe_dt(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
int gpio = 0;
struct asr_gnss_platform_data *pdata = pdev->dev.platform_data;
/* get gpios from dt */
gpio = of_get_named_gpio(np, "edge-wakeup-gpio", 0);
if (unlikely(gpio < 0)) {
dev_warn(&pdev->dev, "edge-wakeup-gpio undefined\n");
pdata->gpio_edge_wakeup = -1;
} else {
pdata->gpio_edge_wakeup = gpio;
}
gpio = of_get_named_gpio(np, "rst-gpio", 0);
if (unlikely(gpio < 0)) {
dev_err(&pdev->dev, "rst-gpio undefined\n");
pdata->gpio_reset = -1;
} else {
pdata->gpio_reset = gpio;
}
gpio = of_get_named_gpio(np, "host-wakeup-gnss-gpio", 0);
if (unlikely(gpio < 0)) {
dev_err(&pdev->dev, "host-wakeup-wlan-gpio undefined\n");
} else {
pdata->gpio_wakeup_device = gpio;
pr_info("%s: host_wakeup_wlan_gpio %d\n", __func__, gpio);
}
return 0;
}
#else
static int asr_gnss_probe_dt(struct platform_device *pdev)
{
return 0;
}
#endif
static int asr_gnss_probe(struct platform_device *pdev)
{
struct asr_gnss_platform_data *pdata = NULL;
/* flag: whether pdata is passed from platfrom_data */
int pdata_passed = 1;
struct device *dev = &pdev->dev;
const struct of_device_id *match = NULL;
int ret = -1;
pr_err("asr_gnss_probe!!!!");
/* make sure asr_rfkill_platform_data is valid */
pdata = pdev->dev.platform_data;
if (!pdata) {
/* if platfrom data do not pass the struct to us */
pdata_passed = 0;
pdata = asr_gnss_platform_data_alloc(pdev);
if (!pdata) {
pr_err("can't get asr_gnss_platform_data struct during probe\n");
goto err_pdata;
}
pdev->dev.platform_data = pdata;
}
pdata->clk_26m_out = devm_clk_get(dev, "gnss_26m");
if (!IS_ERR(pdata->clk_26m_out)) {
dev_info(dev, "enable 26M output for wifi\n");
clk_prepare_enable(pdata->clk_26m_out);
}
/* set value to asr_rfkill_platform_data if DT pass them to us */
#ifdef CONFIG_OF
match = of_match_device(of_match_ptr(asr_gnss_of_match),
&pdev->dev);
#endif
if (match) {
ret = asr_gnss_probe_dt(pdev);
if (ret)
goto err_dt;
}
ret = sysfs_create_group(&pdev->dev.kobj, &gps_attr_group);
if (ret) {
dev_err(&pdev->dev, "GPS create sysfs fail!\n");
return ret;
}
return 0;
err_dt:
if (!pdata_passed)
pdev->dev.platform_data = NULL;
err_pdata:
return ret;
}
static int asr_gnss_remove(struct platform_device *pdev)
{
struct asr_gnss_platform_data *pdata;
pdata = pdev->dev.platform_data;
if (!IS_ERR(pdata->clk_26m_out))
clk_disable_unprepare(pdata->clk_26m_out);
return 0;
}
static int asr_gnss_suspend(struct platform_device *pdev,
pm_message_t pm_state)
{
return 0;
}
static int asr_gnss_resume(struct platform_device *pdev)
{
return 0;
}
static struct platform_driver asr_gnss_platform_driver = {
.probe = asr_gnss_probe,
.remove = asr_gnss_remove,
.driver = {
.name = ASR_DEV_NAME,
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = asr_gnss_of_match,
#endif
},
.suspend = asr_gnss_suspend,
.resume = asr_gnss_resume,
};
static int __init asr_gnss_init(void)
{
return platform_driver_register(&asr_gnss_platform_driver);
}
static void __exit asr_gnss_exit(void)
{
platform_driver_unregister(&asr_gnss_platform_driver);
}
module_init(asr_gnss_init);
module_exit(asr_gnss_exit);
MODULE_ALIAS("platform:asr_gps");
MODULE_DESCRIPTION("asr_gps");
MODULE_AUTHOR("ASR");
MODULE_LICENSE("GPL V2");