blob: fbba4aedf37cac1fb68586ee1ffd123363a4d1f2 [file] [log] [blame]
/* Copyright (c) 2013-2014, 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.
*/
#define pr_fmt(fmt) "%s:%d] " fmt "\n", __func__, __LINE__
#include <linux/init.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/sysfs.h>
/* RTC clock name for lookup */
#define QCA1530_RTC_CLK_ID "qca,rtc_clk"
/* TCXO clock name for lookup */
#define QCA1530_TCXO_CLK_ID "qca,tcxo_clk"
/* SoC power regulator prefix for DTS */
#define QCA1530_OF_PWR_REG_NAME "qca,pwr"
/* SoC optional power regulator prefix for DTS */
#define QCA1530_OF_PWR_OPT_REG_NAME "qca,pwr2"
/* SoC power regulator pin for DTS */
#define QCA1530_OF_PWR_GPIO_NAME "qca,pwr-gpio"
/* Reset power regulator prefix for DTS */
#define QCA1530_OF_RESET_REG_NAME "qca,reset"
/* Reset pin name for DTS */
#define QCA1530_OF_RESET_GPIO_NAME "qca,reset-gpio"
/* Clock pin name for DTS */
#define QCA1530_OF_CLK_GPIO_NAME "qca,clk-gpio"
/* xLNA power regulator prefix for DTS */
#define QCA1530_OF_XLNA_REG_NAME "qca,xlna"
/* xLNA voltage property name in DTS */
#define QCA1530_OF_XLNA_POWER_VOLTAGE "qca,xlna-voltage-level"
/* xLNA current property name in DTS */
#define QCA1530_OF_XLNA_POWER_CURRENT "qca,xlna-current-level"
/* xLNA pin name for DTS */
#define QCA1530_OF_XLNA_GPIO_NAME "qca,xlna-gpio"
#define QCA1530_ALL_FLG 0x0f
#define QCA1530_POWER_FLG 0x01
#define QCA1530_XLNA_FLG 0x02
#define QCA1530_CLK_FLG 0x04
#define QCA1530_RESET_FLG 0x08
#define GPIO_OPTIONAL_FLG 0x01
#define GPIO_EXPORT_FLG 0x02
#define GPIO_OUTDIR_FLG 0x04
#define SYSFS_NODE_NAME "qca1530"
/**
* struct qca1530_static - keeps all driver instance variables
* @pdev: Platform device data
* @reset_gpio: Number of GPIO pin for reset control, or -1
* @reset_reg: Handle to power regulator for reset control, or NULL
* @rtc_clk: RTC clock handle (32KHz)
* @rtc_clk_gpio: RTC clock gppio, or -1
* @tcxo_clk: TCXO clock handle
* @pwr_reg: Main SoC power regulator handle, or NULL
* @pwr_gpio: Main SoC power regulator GPIO, or -1
* @xlna_reg: xLNA power regulator handle, or NULL
* @xlna_gpio: xLNA power GPIO, or -1
*
* The structure contains all driver instance variables.
*/
struct qca1530_static {
struct platform_device *pdev;
int reset_gpio;
struct regulator *reset_reg;
struct clk *rtc_clk;
int rtc_clk_gpio;
struct clk *tcxo_clk;
struct regulator *pwr_reg;
struct regulator *pwr_opt_reg;
int pwr_gpio;
struct regulator *xlna_reg;
int xlna_gpio;
int chip_state;
};
/*
* qca1530_data - driver instance data
*/
static struct qca1530_static qca1530_data = {
.reset_gpio = -1,
.rtc_clk_gpio = -1,
.pwr_gpio = -1,
.xlna_gpio = -1,
.chip_state = 0,
};
/**
* qca1530_deinit_gpio() - release GPIO resource
* @pdev: platform device data
* @pgpio: pointer to GPIO handle
* @flags: flags showing how gpio was initialized
*
* Function releases GPIO handle allocated by the driver. By default the
* GPIO pin is removed from user space, switched to input direction and
* then released.
*
* GPIO handle is set to -1.
*/
static void qca1530_deinit_gpio(struct platform_device *pdev, int *pgpio,
int flags)
{
if (*pgpio >= 0) {
pr_debug("Releasing GPIO: %d, %d", *pgpio, flags);
if (flags & GPIO_EXPORT_FLG)
gpio_unexport(*pgpio);
gpio_direction_input(*pgpio);
devm_gpio_free(&pdev->dev, *pgpio);
*pgpio = -1;
}
}
/**
* qca1530_init_gpio() - initialize GPIO resource
* @pdev: platform device data
* @pgpio: pointer to GPIO handle
* @pgio_name: name of gpio in device tree
* @flags: flags to control export, direction, etc
*
* Function initializes gpio and returns 0 on sucess.
*
*/
static int qca1530_init_gpio(struct platform_device *pdev, int *pgpio,
const char *gpio_name, int flags)
{
int ret;
pr_debug("Initializing gpio %s, flags %d", gpio_name, flags);
ret = of_get_named_gpio(pdev->dev.of_node, gpio_name, 0);
if (ret == -ENOENT && (flags & GPIO_OPTIONAL_FLG)) {
*pgpio = -1;
pr_debug("Optional GPIO is not defined");
return 0;
} else if (ret < 0) {
pr_err("Error getting GPIO from config: %d", ret);
goto err_gpio_init;
}
*pgpio = ret;
ret = devm_gpio_request(&pdev->dev, *pgpio, gpio_name);
if (ret < 0) {
pr_err("failed to request gpio %d, %d", *pgpio, ret);
goto err_gpio_init;
}
if (flags & GPIO_OUTDIR_FLG) {
ret = gpio_direction_output(*pgpio, 0);
if (ret < 0) {
pr_err("failed to change direction for gpio %d, %d",
*pgpio, ret);
goto err_gpio_set_dir;
}
}
if (flags & GPIO_EXPORT_FLG) {
ret = gpio_export(*pgpio, false);
if (ret < 0) {
pr_err("failed to export gpio %d for user, %d",
*pgpio, ret);
goto err_gpio_export;
}
}
pr_debug("Initialized gpio %s: %d", gpio_name, *pgpio);
return 0;
err_gpio_export:
gpio_direction_input(*pgpio);
err_gpio_set_dir:
devm_gpio_free(&pdev->dev, *pgpio);
err_gpio_init:
*pgpio = -1;
return ret;
}
/**
* qca1530_deinit_regulator() - release power regulator resource
* @ppwr: pointer to power regulator handle
*
* Function releases power regulator and sets handle to NULL.
*/
static void qca1530_deinit_regulator(struct regulator **ppwr)
{
if (*ppwr) {
devm_regulator_put(*ppwr);
*ppwr = NULL;
}
}
/**
* qca1530_clk_prepare() - prepare or unprepare clock
* @clk: clock handle
* @mode: 0 to unprepare, 1 to prepare
*
* Function prepares or unprepares clock and logs the result.
*
* Return: 0 on success, error code on failure
*/
static int qca1530_clk_prepare(struct clk *clk, int mode)
{
int ret = 0;
if (mode)
ret = clk_prepare_enable(clk);
else
clk_disable_unprepare(clk);
pr_debug("Configured clock (%p): mode=%d ret=%d", clk, mode, ret);
return ret;
}
/**
* qca1530_clk_set_gpio() - enable or disable RTC GPIO pin
* @mode: 1 to enable, 0 to disable
*
* Function enables or disable clock GPIO pin and logs the result.
*/
static void qca1530_clk_set_gpio(int mode)
{
gpio_set_value(qca1530_data.rtc_clk_gpio, mode ? 1 : 0);
pr_debug("Set clk GPIO (%d): mode=%d", qca1530_data.rtc_clk_gpio, mode);
}
/**
* qca1530_clk_set() - start/stop initialized clocks
* @mode: 1 to start clocks, 0 to stop
*
* Function turns clocks on or off. Clock configuration is optional, however
* when configured, the following order is used: RTC GPIO pin, RTC clock,
* TCXO clock. When switching off, the order is reveresed.
*
* Return: 0 on success, error code on failure.
*/
static int qca1530_clk_set(int mode)
{
int ret = 0;
if (qca1530_data.rtc_clk_gpio < 0 &&
!qca1530_data.rtc_clk &&
!qca1530_data.tcxo_clk) {
ret = -ENOSYS;
} else {
if (qca1530_data.rtc_clk_gpio >= 0)
qca1530_clk_set_gpio(mode);
if (qca1530_data.rtc_clk)
ret = qca1530_clk_prepare(qca1530_data.rtc_clk, mode);
if (!ret && qca1530_data.tcxo_clk)
ret = qca1530_clk_prepare(qca1530_data.tcxo_clk, mode);
}
pr_debug("Configured clk: mode=%d ret=%d", mode, ret);
return ret;
}
/**
* qca1530_clk_release_clock() - release clocks
* @pdev: platform device data
* @clk_name: clock name
* @clk: pointer to clock handle pointer
*
* Function releases initialized clocks and sets clock handle
* pointer to NULL.
*/
static void qca1530_clk_release_clock(
struct platform_device *pdev,
const char *clk_name,
struct clk **clk)
{
if (*clk) {
pr_debug("Unregistering CLK: name=%s", clk_name);
devm_clk_put(&pdev->dev, *clk);
*clk = NULL;
}
}
/**
* qca1530_clk_release_clocks() - release clocks
* @pdev: platform device data
*
* Function releases initialized clocks and sets clock handle
* pointers to NULL.
*/
static void qca1530_clk_release_clocks(struct platform_device *pdev)
{
qca1530_deinit_gpio(pdev, &qca1530_data.rtc_clk_gpio,
GPIO_OPTIONAL_FLG | GPIO_OUTDIR_FLG);
qca1530_clk_release_clock(pdev, QCA1530_TCXO_CLK_ID,
&qca1530_data.tcxo_clk);
qca1530_clk_release_clock(pdev, QCA1530_RTC_CLK_ID,
&qca1530_data.rtc_clk);
}
/**
* qca1530_clk_init() - allocate and initialize clock input resources
* @pdev: platform device data
*
* Function tries to connect to RTC and TCXO clocks and obtain clock GPIO
* pin.
*
* Return: 0 on success, error code on failure.
*/
static int qca1530_clk_init(struct platform_device *pdev)
{
int ret;
pr_debug("Initializing clock");
qca1530_data.rtc_clk = devm_clk_get(&pdev->dev, QCA1530_RTC_CLK_ID);
if (IS_ERR(qca1530_data.rtc_clk)) {
ret = PTR_ERR(qca1530_data.rtc_clk);
qca1530_data.rtc_clk = NULL;
if (ret == -ENOENT) {
pr_debug("No RTC clock controller");
} else {
pr_err("Clock init error: device=%s clock=%s ret=%d",
dev_name(&pdev->dev), QCA1530_RTC_CLK_ID,
ret);
goto err_0;
}
} else
pr_debug("Ref to clock %s obtained: %p",
QCA1530_RTC_CLK_ID, qca1530_data.rtc_clk);
qca1530_data.tcxo_clk = devm_clk_get(&pdev->dev, QCA1530_TCXO_CLK_ID);
if (IS_ERR(qca1530_data.tcxo_clk)) {
ret = PTR_ERR(qca1530_data.tcxo_clk);
qca1530_data.tcxo_clk = NULL;
if (ret == -ENOENT) {
pr_debug("No TCXO clock controller");
} else {
pr_err("Clock init error: device=%s clock=%s ret=%d",
dev_name(&pdev->dev), QCA1530_TCXO_CLK_ID,
ret);
goto err_1;
}
} else
pr_debug("Ref to clock %s obtained: %p",
QCA1530_TCXO_CLK_ID, qca1530_data.tcxo_clk);
ret = qca1530_init_gpio(pdev, &qca1530_data.rtc_clk_gpio,
QCA1530_OF_CLK_GPIO_NAME, GPIO_OPTIONAL_FLG | GPIO_OUTDIR_FLG);
if (ret)
goto err_1;
ret = qca1530_clk_set(1);
if (ret < 0) {
pr_err("Clock set error: ret=%d", ret);
goto err_1;
}
pr_debug("Clock init done: GPIO=%s RTC=%s TCXO=%s",
qca1530_data.rtc_clk_gpio >= 0 ? "ok" : "unused",
qca1530_data.rtc_clk ? "ok" : "unused",
qca1530_data.tcxo_clk ? "ok" : "unused");
return 0;
err_1:
qca1530_clk_release_clocks(pdev);
err_0:
pr_err("init error: ret=%d", ret);
return ret;
}
/**
* qca1530_clk_deinit() - release clock control resources
* @pdev: platform device data
*
* Function disables clock control and releases GPIO and clock resources.
*/
static void qca1530_clk_deinit(struct platform_device *pdev)
{
qca1530_clk_set(0);
qca1530_deinit_gpio(pdev, &qca1530_data.rtc_clk_gpio,
GPIO_OPTIONAL_FLG | GPIO_OUTDIR_FLG);
qca1530_clk_release_clocks(pdev);
}
/**
* qca1530_pwr_set_gpio() - set power GPIO line
* @mode: 1 to enable, 0 to disable
*
* Function sets GPIO pin value and logs the result.
*/
static void qca1530_pwr_set_gpio(int mode)
{
gpio_set_value(qca1530_data.pwr_gpio, mode ? 1 : 0);
pr_debug("Configuring power(GPIO): mode=%d", mode);
}
/**
* qca1530_pwr_set_regulator() - control regulator and log results
* @reg: regulator to control
* @mode: 1 to enable, 0 to disable
*
* Function controls power regulator and logs results.
*
* Return: 0 on success, error code otherwise
*/
static int qca1530_pwr_set_regulator(struct regulator *reg, int mode)
{
int ret;
pr_debug("Setting regulator: mode=%d regulator=%p", mode, reg);
if (mode) {
ret = regulator_enable(reg);
if (ret)
pr_err("Failed to enable regulator, rc=%d", ret);
else
pr_debug("Regulator %p was enabled (%d)", reg, ret);
} else {
if (!regulator_is_enabled(reg)) {
ret = 0;
} else {
ret = regulator_disable(reg);
if (ret)
pr_err("Failed to disable regulator, rc=%d",
ret);
else
pr_debug("Regulator %p was disabled (%d)", reg,
ret);
}
}
pr_debug("Regulator set result: regulator=%p mode=%d ret=%d", reg, mode,
ret);
return ret;
}
/**
* qca1530_pwr_init_regulator() - initialize power regulator if configured
* @pdev: platform device data
* @name: regulator name prefix in DTS file
* @ppwr: pointer to resulting variable
*
* Function initializes power regulator if DTS configuration is present.
*
* Return: 0 on success, error code otherwise
*/
static int qca1530_pwr_init_regulator(
struct platform_device *pdev,
const char *name, struct regulator **ppwr)
{
int ret;
struct regulator *pwr;
pwr = devm_regulator_get(&pdev->dev, name);
if (IS_ERR_OR_NULL(pwr)) {
ret = PTR_ERR(pwr);
*ppwr = NULL;
if (ret == -ENODEV) {
pr_debug("Power regulator %s is not defined",
name);
ret = 0;
} else
pr_err("Failed to get regulator, ret=%d", ret);
} else {
pr_debug("Ref to regulator %s obtained: %p", name, pwr);
*ppwr = pwr;
ret = 0;
}
return ret;
}
/**
* qca1530_power_set() - enable or disable SoC power
* @mode: 1 to enable, 0 to disable
*
* Function enables or disables SoC power line.
*
* Return: 0 on success, error code otherwise
*/
static int qca1530_pwr_set(int mode)
{
int ret = 0;
if (!qca1530_data.pwr_reg &&
!qca1530_data.pwr_opt_reg &&
qca1530_data.pwr_gpio < 0) {
ret = -ENOSYS;
} else {
if (qca1530_data.pwr_reg)
ret = qca1530_pwr_set_regulator(
qca1530_data.pwr_reg, mode);
if (!ret && qca1530_data.pwr_opt_reg)
ret = qca1530_pwr_set_regulator(
qca1530_data.pwr_opt_reg, mode);
if (!ret && qca1530_data.pwr_gpio >= 0)
qca1530_pwr_set_gpio(mode);
}
return ret;
}
/**
* qca1530_pwr_deinit() - release main SoC power control
* @pdev: platform device data
*
* Function releases power regulator and power GPIO pin.
*/
static void qca1530_pwr_deinit(struct platform_device *pdev)
{
qca1530_pwr_set(0);
qca1530_deinit_gpio(pdev, &qca1530_data.pwr_gpio,
GPIO_OPTIONAL_FLG | GPIO_OUTDIR_FLG);
qca1530_deinit_regulator(&qca1530_data.pwr_opt_reg);
qca1530_deinit_regulator(&qca1530_data.pwr_reg);
}
/**
* qca1530_pwr_init() - allocate SoC power control
* @pdev: platform device data
*
* Function allocates and enables SoC power control.
*
* Return: 0 on success, error code otherwise
*/
static int qca1530_pwr_init(struct platform_device *pdev)
{
int ret = 0;
pr_debug("Initializing power control");
ret = qca1530_pwr_init_regulator(
pdev, QCA1530_OF_PWR_REG_NAME, &qca1530_data.pwr_reg);
if (ret)
goto err_0;
ret = qca1530_pwr_init_regulator(
pdev, QCA1530_OF_PWR_OPT_REG_NAME, &qca1530_data.pwr_opt_reg);
if (ret)
goto err_0;
ret = qca1530_init_gpio(pdev, &qca1530_data.pwr_gpio,
QCA1530_OF_PWR_GPIO_NAME, GPIO_OPTIONAL_FLG | GPIO_OUTDIR_FLG);
if (ret)
goto err_0;
if (qca1530_data.pwr_reg ||
qca1530_data.pwr_gpio >= 0 ||
qca1530_data.pwr_opt_reg) {
ret = qca1530_pwr_set(1);
if (ret) {
pr_err("Failed to enable power, rc=%d", ret);
goto err_0;
}
pr_debug("Configured: reg=%p gpio=%d",
qca1530_data.pwr_reg,
qca1530_data.pwr_gpio);
} else {
pr_debug("Power control is not available");
}
return ret;
err_0:
qca1530_pwr_deinit(pdev);
return ret;
}
/**
* qca1530_reset_deinit() - release reset control resources
* @pdev: platform device data
*
* Function releases reset line GPIO and switches off and releases power
* regulator.
*/
static void qca1530_reset_deinit(struct platform_device *pdev)
{
pr_debug("Releasing reset control");
qca1530_deinit_gpio(pdev, &qca1530_data.reset_gpio,
GPIO_OUTDIR_FLG);
if (qca1530_data.reset_reg) {
qca1530_pwr_set_regulator(qca1530_data.reset_reg, 0);
qca1530_deinit_regulator(&qca1530_data.reset_reg);
}
}
/**
* qca1530_reset_init() - initialize SoC reset line resources
* @pdev: platform device data
*
* Function initializes reset line resources, including configuration of GPIO
* pin and power regulator.
*
* Return: 0 on success, error code otherwise
*/
static int qca1530_reset_init(struct platform_device *pdev)
{
int ret;
pr_debug("Initializing reset control");
ret = qca1530_init_gpio(pdev, &qca1530_data.reset_gpio,
QCA1530_OF_RESET_GPIO_NAME, GPIO_OUTDIR_FLG);
if (ret < 0) {
goto err_0;
}
ret = qca1530_pwr_init_regulator(
pdev, QCA1530_OF_RESET_REG_NAME, &qca1530_data.reset_reg);
if (ret)
goto err_0;
if (qca1530_data.reset_reg) {
ret = qca1530_pwr_set_regulator(qca1530_data.reset_reg, 1);
if (ret) {
pr_err("failed to turn on reset regulator");
goto err_0;
}
}
return 0;
err_0:
qca1530_reset_deinit(pdev);
return ret;
}
/**
* qca1530_xlna_set() - enables and disables xLNA
* @mode: 0 to disable, 1 to enable
*
* Function controls xLNA. It configures power regulator and GPIO pin to
* enable or disable the amplifier.
*
* Return: 0 on success, error code on error
*/
static int qca1530_xlna_set(int mode)
{
int ret = 0;
if (!qca1530_data.xlna_reg && qca1530_data.xlna_gpio < 0)
ret = -ENOSYS;
else if (mode) {
if (qca1530_data.xlna_reg) {
ret = qca1530_pwr_set_regulator(
qca1530_data.xlna_reg, mode);
}
if (!ret && qca1530_data.xlna_gpio >= 0)
gpio_set_value(qca1530_data.xlna_gpio, 1);
} else {
if (qca1530_data.xlna_gpio >= 0)
gpio_set_value(qca1530_data.xlna_gpio, 0);
if (qca1530_data.xlna_reg)
ret = qca1530_pwr_set_regulator(
qca1530_data.xlna_reg, mode);
}
return ret;
}
/**
* qca1530_read_u32_arr_property() - read u32 values property
* @pdev: platform device data
* @pname: property name
* @num_values: array size
* @values: array to put results to
*
* Function reads property with given name. Filles passed array of
* u32 values.
*
* Return: 0 on successful read, error code on error.
*/
static int qca1530_read_u32_arr_property(struct platform_device *pdev,
const char *pname, const int num_values, u32 *values)
{
int len = 0;
int ret = 0;
if (pdev->dev.of_node) {
const void *rp = of_get_property(pdev->dev.of_node,
pname, &len);
if (NULL != rp && len == sizeof(u32)*num_values)
ret = of_property_read_u32_array(pdev->dev.of_node,
pname, values, num_values);
else
ret = -ENODATA;
} else{
ret = -EINVAL;
}
if (ret)
pr_err("Error reading property %s, ret:%d", pname, ret);
return ret;
}
/**
* qca1530_xlna_deinit() - release all xLNA resources
* @pdev: platform device data
*
* Function switches off xLNA and releases all allocated resources.
*/
static void qca1530_xlna_deinit(struct platform_device *pdev)
{
qca1530_xlna_set(0);
qca1530_deinit_gpio(pdev, &qca1530_data.xlna_gpio,
GPIO_OPTIONAL_FLG | GPIO_OUTDIR_FLG);
qca1530_deinit_regulator(&qca1530_data.xlna_reg);
}
/**
* qca1530_xlna_init() - allocates xLNA resources
* @pdev: platform device data
*
* Function allocates xLNA resources according to DTS configuration.
* When configured, the following facilities are used:
* - power regulator (optionally)
* - GPIO pin (optionally)
*
* After initialization, xLNA is enabled.
*
* Return: 0 on successful initialization, error code on error.
*/
static int qca1530_xlna_init(struct platform_device *pdev)
{
int ret = 0;
u32 tmp[2];
pr_debug("Initializing xLNA control");
ret = qca1530_pwr_init_regulator(
pdev, QCA1530_OF_XLNA_REG_NAME, &qca1530_data.xlna_reg);
if (ret)
goto err_0;
if (qca1530_data.xlna_reg) {
ret = qca1530_read_u32_arr_property(pdev,
QCA1530_OF_XLNA_POWER_VOLTAGE, 2, tmp);
if (!ret) {
ret = regulator_set_voltage(qca1530_data.xlna_reg,
tmp[0], tmp[1]);
if (ret)
pr_warn("Failed to set voltage, ret=%d", ret);
else
pr_debug("Regulator %p voltage was set (%d)",
qca1530_data.xlna_reg, ret);
}
ret = qca1530_read_u32_arr_property(pdev,
QCA1530_OF_XLNA_POWER_CURRENT, 2, tmp);
if (!ret) {
ret = regulator_set_optimum_mode(qca1530_data.xlna_reg,
tmp[0]);
if (ret < 0)
pr_warn("Failed to set optimum mode, ret=%d",
ret);
else
pr_debug("Optimum mode for %p was set (%d)",
qca1530_data.xlna_reg, ret);
}
}
ret = qca1530_init_gpio(pdev, &qca1530_data.xlna_gpio,
QCA1530_OF_XLNA_GPIO_NAME, GPIO_OPTIONAL_FLG | GPIO_OUTDIR_FLG);
if (ret)
goto err_0;
if (qca1530_data.xlna_reg || qca1530_data.xlna_gpio >= 0) {
ret = qca1530_xlna_set(1);
if (ret) {
pr_err("Failed to enable xLNA, rc=%d", ret);
goto err_0;
}
pr_debug("Configured: reg=%p gpio=%d",
qca1530_data.xlna_reg,
qca1530_data.xlna_gpio);
} else {
pr_debug("xLNA control is not available");
}
return ret;
err_0:
qca1530_xlna_deinit(pdev);
return ret;
}
/**
* qca1530_set_chip_state() - performs driver initialization
* @pdev: platform device data
* @on: target state, 1 - on, 0 - off
*
* The driver probing includes initialization of the subsystems in the
* following order:
* - reset control
* - RTC and TXCO Clock control
* - xLNA control
* - SoC power control
*/
static int qca1530_set_chip_state(struct platform_device *pdev,
const int on_mask)
{
int ret;
int on_req;
ret = 0;
on_req = on_mask;
pr_debug("on_mask=%d", on_mask);
pr_debug("Switching on");
if (!(qca1530_data.chip_state & QCA1530_RESET_FLG) &&
(on_req & QCA1530_RESET_FLG)) {
ret = qca1530_reset_init(pdev);
if (ret < 0) {
pr_err("failed to init reset: %d", ret);
on_req = 0;
goto switching_off;
} else {
qca1530_data.chip_state |= QCA1530_RESET_FLG;
}
}
if (!(qca1530_data.chip_state & QCA1530_CLK_FLG) &&
(on_req & QCA1530_CLK_FLG)) {
ret = qca1530_clk_init(pdev);
if (ret) {
pr_err("failed to initialize clock: %d", ret);
on_req = 0;
goto switching_off;
} else {
qca1530_data.chip_state |= QCA1530_CLK_FLG;
}
}
if (!(qca1530_data.chip_state & QCA1530_XLNA_FLG) &&
(on_req & QCA1530_XLNA_FLG)) {
ret = qca1530_xlna_init(pdev);
if (ret) {
pr_err("failed to initialize xLNA: %d", ret);
on_req = 0;
goto switching_off;
} else {
qca1530_data.chip_state |= QCA1530_XLNA_FLG;
}
}
if (!(qca1530_data.chip_state & QCA1530_POWER_FLG) &&
(on_req & QCA1530_POWER_FLG)) {
ret = qca1530_pwr_init(pdev);
if (ret < 0) {
pr_err("failed to init power: %d", ret);
on_req = 0;
goto switching_off;
} else {
qca1530_data.chip_state |= QCA1530_POWER_FLG;
}
}
pr_debug("Chip on section over, qca1530_data.chip_state=%d",
qca1530_data.chip_state);
switching_off:
pr_debug("Switching off");
if ((qca1530_data.chip_state & QCA1530_POWER_FLG) &&
!(on_req & QCA1530_POWER_FLG)) {
qca1530_pwr_deinit(pdev);
qca1530_data.chip_state &= ~QCA1530_POWER_FLG;
}
if ((qca1530_data.chip_state & QCA1530_XLNA_FLG) &&
!(on_req & QCA1530_XLNA_FLG)) {
qca1530_xlna_deinit(pdev);
qca1530_data.chip_state &= ~QCA1530_XLNA_FLG;
}
if ((qca1530_data.chip_state & QCA1530_CLK_FLG) &&
!(on_req & QCA1530_CLK_FLG)) {
qca1530_clk_deinit(pdev);
qca1530_data.chip_state &= ~QCA1530_CLK_FLG;
}
if ((qca1530_data.chip_state & QCA1530_RESET_FLG) &&
!(on_req & QCA1530_RESET_FLG)) {
qca1530_reset_deinit(pdev);
qca1530_data.chip_state &= ~QCA1530_RESET_FLG;
}
pr_debug("Chip off section over, qca1530_data.chip_state=%d",
qca1530_data.chip_state);
return ret;
}
/**
* qca1530_attr_chip_state_show() - provides current chip state through sysfs
*/
static ssize_t qca1530_attr_chip_state_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
return snprintf(buf, 16, "%d\n", qca1530_data.chip_state);
}
/**
* qca1530_attr_chip_state_store() - sets new chip state
*/
static ssize_t qca1530_attr_chip_state_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
int retval;
int new_state;
sscanf(buf, "%d", &new_state);
pr_debug("new_state=%d", new_state);
if (count &&
((new_state <= QCA1530_ALL_FLG) && (new_state >= 0))) {
pr_debug("qca1530_data.chip_state=%d", qca1530_data.chip_state);
retval = qca1530_set_chip_state(qca1530_data.pdev, new_state);
pr_debug("qca1530_set_chip_state() returned %d", retval);
pr_debug("qca1530_data.chip_state=%d", qca1530_data.chip_state);
}
return count;
}
/**
* qca1530_attr_reset_show() - provides current reset state through sysfs
*/
static ssize_t qca1530_attr_reset_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
int v = -1;
if (qca1530_data.chip_state & QCA1530_RESET_FLG)
v = gpio_get_value(qca1530_data.reset_gpio);
return snprintf(buf, 16, "%d\n", v);
}
/**
* qca1530_attr_reset_store() - sets new reset state
*/
static ssize_t qca1530_attr_reset_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
int v;
if (sscanf(buf, "%d", &v) == 1
&& (qca1530_data.chip_state & QCA1530_RESET_FLG))
gpio_set_value(qca1530_data.reset_gpio, v ? 1 : 0);
return count;
}
/*
* qca1530_attributes - sysfs attribute definition array
*/
static struct kobj_attribute qca1530_attributes[] = {
__ATTR(chip_state,
S_IRUSR | S_IWUSR,
qca1530_attr_chip_state_show, qca1530_attr_chip_state_store),
__ATTR(reset,
S_IRUSR | S_IWUSR,
qca1530_attr_reset_show, qca1530_attr_reset_store),
};
/*
* qca1530_attrs - sysfs attributes for attribute group
*/
static struct attribute *qca1530_attrs[] = {
&qca1530_attributes[0].attr,
&qca1530_attributes[1].attr,
NULL,
};
/*
* qca1530_attr_group - driver sysfs attribute group
*/
static struct attribute_group qca1530_attr_group = {
.attrs = qca1530_attrs,
};
static struct kobject *qca1530_kobject;
/**
* qca1530_create_sysfs_node() - creates sysfs nodes for control
*
* Function exports two control nodes: for controlling chip control signals
* and for reset control.
*/
static int qca1530_create_sysfs_node(void)
{
int retval;
pr_debug("Creating sysfs node");
qca1530_kobject = kobject_create_and_add(SYSFS_NODE_NAME, kernel_kobj);
pr_debug("qca1530_kobject=%p", qca1530_kobject);
if (!qca1530_kobject)
return -ENOMEM;
retval = sysfs_create_group(qca1530_kobject, &qca1530_attr_group);
pr_debug("sysfs_create_group() returned %d", retval);
if (retval)
kobject_put(qca1530_kobject);
return retval;
}
/**
* qca1530_remove_sysfs_node() - removes sysfs nodes
*/
static void qca1530_remove_sysfs_node(void)
{
pr_debug("Removing sysfs node");
if (qca1530_kobject) {
sysfs_remove_group(qca1530_kobject, &qca1530_attr_group);
kobject_put(qca1530_kobject);
qca1530_kobject = NULL;
}
}
/**
* qca1530_probe() - performs driver initialization
* @pdev: platform device data
*
* Function tried to turn on all required signals.
* Refer to qca1530_set_chip_state() documentation for details.
*/
static int qca1530_probe(struct platform_device *pdev)
{
int retval;
pr_debug("Probing to install");
retval = qca1530_set_chip_state(pdev, QCA1530_ALL_FLG);
if (!retval) {
qca1530_data.pdev = pdev;
retval = qca1530_create_sysfs_node();
if (retval) {
qca1530_set_chip_state(pdev, 0);
qca1530_data.pdev = NULL;
pr_debug("qca1530_create_sysfs_node() returned %d",
retval);
}
} else
pr_debug("qca1530_set_chip_state() returned %d", retval);
return retval;
}
/**
* qca1530_remove() - releases all resources
* @pdev: platform device data
*
* Driver's removal stops subsystems in the following order:
* - Power control
* - xLNA control
* - RTC and TCXO clock control
* - Reset control
*/
static int qca1530_remove(struct platform_device *pdev)
{
int retval;
qca1530_remove_sysfs_node();
retval = qca1530_set_chip_state(pdev, 0);
qca1530_data.pdev = NULL;
return retval;
}
/*
* qca1530_of_match - DTS driver name
*/
static struct of_device_id qca1530_of_match[] = {
{.compatible = "qca,qca1530", },
{ },
};
/*
* qca1530_driver - driver registration data
*/
static struct platform_driver qca1530_driver = {
.probe = qca1530_probe,
.remove = qca1530_remove,
.driver = {
.name = "qca1530",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(qca1530_of_match),
},
};
/**
* qca1530_init() - registers the driver
*/
static int __init qca1530_init(void)
{
return platform_driver_register(&qca1530_driver);
}
/**
* qca1530_exit() - unregisters the driver
*/
static void __exit qca1530_exit(void)
{
platform_driver_unregister(&qca1530_driver);
}
module_init(qca1530_init);
module_exit(qca1530_exit);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("qca1530 SoC chip driver");
MODULE_ALIAS("qca1530");