sdk-hwV1.3/lichee/linux-4.9/drivers/input/misc/sunxi_pmc-pek.c

364 lines
9.7 KiB
C
Raw Normal View History

2024-05-07 10:09:20 +00:00
/*
* sunxi_pmc power button driver.
*
* Copyright (C) 2013 Carlo Caione <carlo@caione.org>
*
* This file is subject to the terms and conditions of the GNU General
* Public License. See the file "COPYING" in the main directory of this
* archive for more details.
*
* 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(x) KBUILD_MODNAME ": " x
#include <linux/errno.h>
#include <linux/irq.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mfd/sunxi_pmc.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/of.h>
#define IRQ_DBR_BIT BIT(0)
#define IRQ_DBF_BIT BIT(1)
struct pk_dts {
uint32_t pmu_powkey_off_time;
uint32_t pmu_powkey_off_func;
uint32_t pmu_powkey_off_en;
uint32_t pmu_powkey_off_delay_time;
uint32_t pmu_powkey_long_time;
uint32_t pmu_powkey_on_time;
uint32_t pmu_pwrok_time;
uint32_t pmu_pwrnoe_time;
uint32_t pmu_powkey_wakeup_rising;
uint32_t pmu_powkey_wakeup_falling;
};
struct sunxi_pmc_pek {
struct sunxi_pmc_dev *sunxi_pmc;
struct input_dev *input;
struct pk_dts pk_dts;
int irq_dbr;
int irq_dbf;
};
#define PMC_OF_PROP_READ(name, def_value) \
do { \
if (of_property_read_u32(node, #name, &pk_dts->name)) \
pk_dts->name = def_value; \
} while (0)
static int pmc_powerkey_dt_parse(struct device_node *node,
struct pk_dts *pk_dts)
{
if (!of_device_is_available(node)) {
pr_err("%s: failed\n", __func__);
return -1;
}
PMC_OF_PROP_READ(pmu_powkey_off_time, 6000);
PMC_OF_PROP_READ(pmu_powkey_off_func, 0);
PMC_OF_PROP_READ(pmu_powkey_off_en, 1);
PMC_OF_PROP_READ(pmu_powkey_off_delay_time, 0);
PMC_OF_PROP_READ(pmu_powkey_long_time, 1500);
PMC_OF_PROP_READ(pmu_powkey_on_time, 1000);
PMC_OF_PROP_READ(pmu_pwrok_time, 64);
PMC_OF_PROP_READ(pmu_pwrnoe_time, 2000);
pk_dts->pmu_powkey_wakeup_rising =
of_property_read_bool(node, "wakeup_rising");
pk_dts->pmu_powkey_wakeup_falling =
of_property_read_bool(node, "wakeup_falling");
return 0;
}
static irqreturn_t sunxi_pmc_pek_irq(int irq, void *pwr)
{
struct input_dev *idev = pwr;
struct sunxi_pmc_pek *sunxi_pmc_pek = input_get_drvdata(idev);
/*
* The power-button is connected to ground so a falling edge (dbf)
* means it is pressed.
*/
if (irq == sunxi_pmc_pek->irq_dbf)
input_report_key(idev, KEY_POWER, true);
else if (irq == sunxi_pmc_pek->irq_dbr)
input_report_key(idev, KEY_POWER, false);
input_sync(idev);
return IRQ_HANDLED;
}
static int pmc_config_set(struct sunxi_pmc_pek *sunxi_pmc_pek)
{
struct sunxi_pmc_dev *sunxi_pmc_dev = sunxi_pmc_pek->sunxi_pmc;
struct regmap *regmap = sunxi_pmc_dev->regmap;
struct pk_dts *pk_dts = &sunxi_pmc_pek->pk_dts;
unsigned int val;
regmap_read(regmap, PMC_DLY_CTRL_REG, &val);
val &= ~(0x7 << 5);
if (pk_dts->pmu_powkey_on_time <= 128) {
val |= (0x00 << 5);
} else if (pk_dts->pmu_powkey_on_time <= 256) {
val |= (0x01 << 5);
} else if (pk_dts->pmu_powkey_on_time <= 512) {
val |= (0x02 << 5);
} else if (pk_dts->pmu_powkey_on_time <= 1000) {
val |= (0x03 << 5);
} else {
val |= (0x04 << 5);
}
regmap_write(regmap, PMC_DLY_CTRL_REG, val);
/* pok long time set*/
if (pk_dts->pmu_powkey_long_time < 1000)
pk_dts->pmu_powkey_long_time = 1000;
if (pk_dts->pmu_powkey_long_time > 2500)
pk_dts->pmu_powkey_long_time = 2500;
regmap_read(regmap, PMC_DLY_CTRL_REG, &val);
val &= ~(0x3 << 10);
val |= (((pk_dts->pmu_powkey_long_time - 1000) / 500)
<< 10);
regmap_write(regmap, PMC_DLY_CTRL_REG, val);
/* pek offlevel poweroff en set*/
regmap_read(regmap, SW_CFG_REG, &val);
val &= ~((0x1 << 12) | (0xffff << 16));
val |= ((!!pk_dts->pmu_powkey_off_en) << 12);
regmap_write(regmap, SW_CFG_REG, val | 0x16AA << 16);
val &= ~(0xffff << 16);
val |= (0xAA16 << 16);
regmap_write(regmap, SW_CFG_REG, val);
/*Init offlevel restart or not */
regmap_read(regmap, SW_CFG_REG, &val);
val &= ~((0x3 << 1) | (0xffff << 16));
val |= ((!!pk_dts->pmu_powkey_off_func) << 1);/* restart or not restart */
regmap_write(regmap, SW_CFG_REG, val | (0x16AA) << 16);
val &= ~(0xffff << 16);
val |= (0xAA16 << 16);
regmap_write(regmap, SW_CFG_REG, val);
/* pek delay set */
regmap_read(regmap, RST_DLY_SEL_REG, &val);
val &= ~(0x7 << 4);
if (pk_dts->pmu_pwrok_time <= 4) {
val |= (0 << 4);
} else if (pk_dts->pmu_pwrok_time <= 8) {
val |= (1 << 4);
} else if (pk_dts->pmu_pwrok_time <= 16) {
val |= (2 << 4);
} else if (pk_dts->pmu_pwrok_time <= 32) {
val |= (3 << 4);
} else {
val |= (4 << 4);
}
regmap_write(regmap, RST_DLY_SEL_REG, val);
/* pek offlevel time set */
regmap_read(regmap, PMC_DLY_CTRL_REG, &val);
val &= ~(0x3 << 8);
if (pk_dts->pmu_powkey_off_time <= 6000) {
val |= (0 << 8);
} else if (pk_dts->pmu_powkey_off_time <= 8000) {
val |= (1 << 8);
} else {
val |= (2 << 8);
}
regmap_write(regmap, PMC_DLY_CTRL_REG, val);
return 0;
}
static void sunxi_pmc_dts_param_set(struct sunxi_pmc_pek *sunxi_pmc_pek)
{
struct sunxi_pmc_dev *sunxi_pmc_dev = sunxi_pmc_pek->sunxi_pmc;
if (!pmc_powerkey_dt_parse(sunxi_pmc_pek->input->dev.parent->of_node,
&sunxi_pmc_pek->pk_dts)) {
switch (sunxi_pmc_dev->variant) {
case PMC_V1_ID:
pmc_config_set(sunxi_pmc_pek);
break;
default:
pr_warn("Setting power key for unsupported PMC variant\n");
}
}
}
static int sunxi_pmc_pek_probe(struct platform_device *pdev)
{
struct sunxi_pmc_pek *sunxi_pmc_pek;
struct sunxi_pmc_dev *sunxi_pmc;
struct input_dev *idev;
int error;
sunxi_pmc_pek = devm_kzalloc(&pdev->dev, sizeof(struct sunxi_pmc_pek),
GFP_KERNEL);
if (!sunxi_pmc_pek)
return -ENOMEM;
sunxi_pmc_pek->sunxi_pmc = dev_get_drvdata(pdev->dev.parent);
sunxi_pmc = sunxi_pmc_pek->sunxi_pmc;
if (!sunxi_pmc->irq) {
pr_err("sunxi_pmc-pek can not register without irq\n");
return -EINVAL;
}
sunxi_pmc_pek->irq_dbr = platform_get_irq_byname(pdev, "PEK_DBR");
if (sunxi_pmc_pek->irq_dbr < 0) {
dev_err(&pdev->dev, "No IRQ for PEK_DBR, error=%d\n",
sunxi_pmc_pek->irq_dbr);
return sunxi_pmc_pek->irq_dbr;
}
sunxi_pmc_pek->irq_dbr = regmap_irq_get_virq(sunxi_pmc->regmap_irqc,
sunxi_pmc_pek->irq_dbr);
sunxi_pmc_pek->irq_dbf = platform_get_irq_byname(pdev, "PEK_DBF");
if (sunxi_pmc_pek->irq_dbf < 0) {
dev_err(&pdev->dev, "No IRQ for PEK_DBF, error=%d\n",
sunxi_pmc_pek->irq_dbf);
return sunxi_pmc_pek->irq_dbf;
}
sunxi_pmc_pek->irq_dbf = regmap_irq_get_virq(sunxi_pmc->regmap_irqc,
sunxi_pmc_pek->irq_dbf);
sunxi_pmc_pek->input = devm_input_allocate_device(&pdev->dev);
if (!sunxi_pmc_pek->input)
return -ENOMEM;
idev = sunxi_pmc_pek->input;
switch (sunxi_pmc->variant) {
case PMC_V1_ID:
idev->name = "pmc-v1-pek";
break;
default:
idev->name = "sunxi_pmc-pek";
break;
}
idev->phys = "m1kbd/input2";
idev->dev.parent = &pdev->dev;
input_set_capability(idev, EV_KEY, KEY_POWER);
set_bit(EV_REP, idev->evbit);
input_set_drvdata(idev, sunxi_pmc_pek);
sunxi_pmc_dts_param_set(sunxi_pmc_pek);
error = devm_request_any_context_irq(&pdev->dev, sunxi_pmc_pek->irq_dbr,
sunxi_pmc_pek_irq, 0,
"sunxi_pmc-pek-dbr", idev);
if (error < 0) {
dev_err(sunxi_pmc->dev, "Failed to request dbr IRQ#%d: %d\n",
sunxi_pmc_pek->irq_dbr, error);
return error;
}
error = devm_request_any_context_irq(&pdev->dev, sunxi_pmc_pek->irq_dbf,
sunxi_pmc_pek_irq, 0,
"sunxi_pmc-pek-dbf", idev);
if (error < 0) {
dev_err(sunxi_pmc->dev, "Failed to request dbf IRQ#%d: %d\n",
sunxi_pmc_pek->irq_dbf, error);
return error;
}
error = input_register_device(idev);
if (error) {
dev_err(sunxi_pmc->dev, "Can't register input device: %d\n",
error);
return error;
}
platform_set_drvdata(pdev, sunxi_pmc_pek);
return 0;
}
static int sunxi_pmc_pek_remove(struct platform_device *pdev)
{
struct sunxi_pmc_pek *sunxi_pmc_pek = platform_get_drvdata(pdev);
input_unregister_device(sunxi_pmc_pek->input);
return 0;
}
static int sunxi_pmc_powerkey_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct sunxi_pmc_pek *sunxi_pmc_pek = platform_get_drvdata(pdev);
if (!sunxi_pmc_pek->pk_dts.pmu_powkey_wakeup_rising) {
disable_irq(sunxi_pmc_pek->irq_dbr);
}
if (!sunxi_pmc_pek->pk_dts.pmu_powkey_wakeup_falling) {
disable_irq(sunxi_pmc_pek->irq_dbf);
}
return 0;
}
static int sunxi_pmc_powerkey_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct sunxi_pmc_pek *sunxi_pmc_pek = platform_get_drvdata(pdev);
if (!sunxi_pmc_pek->pk_dts.pmu_powkey_wakeup_rising) {
enable_irq(sunxi_pmc_pek->irq_dbr);
}
if (!sunxi_pmc_pek->pk_dts.pmu_powkey_wakeup_falling) {
enable_irq(sunxi_pmc_pek->irq_dbf);
}
return 0;
}
static const struct dev_pm_ops sunxi_pmc_powerkey_pm_ops = {
.suspend = sunxi_pmc_powerkey_suspend,
.resume = sunxi_pmc_powerkey_resume,
};
static struct of_device_id pmc_match_table[] = {
{ .compatible = "x-powers,pmc-pek" },
{ /* sentinel */ },
};
static struct platform_driver sunxi_pmc_pek_driver = {
.probe = sunxi_pmc_pek_probe,
.remove = sunxi_pmc_pek_remove,
.driver = {
.of_match_table = pmc_match_table,
.name = "pmc-pek",
.pm = &sunxi_pmc_powerkey_pm_ops,
},
};
module_platform_driver(sunxi_pmc_pek_driver);
MODULE_DESCRIPTION("sunxi_pmc Power Button");
MODULE_AUTHOR("Carlo Caione <carlo@caione.org>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:sunxi_pmc-pek");