/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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");