sdk-hwV1.3/lichee/linux-4.9/drivers/mfd/sunxi_pmc.c

376 lines
9.1 KiB
C
Raw Normal View History

2024-05-07 10:09:20 +00:00
/*
* MFD core driver for the X-Powers' Power Management ICs
*
* sunxi_pmc typically comprises an adaptive USB-Compatible PWM charger, BUCK DC-DC
* converters, LDOs, multiple 12-bit ADCs of voltage, current and temperature
* as well as configurable GPIOs.
*
* This file contains the interface independent core functions.
*
* Copyright (C) 2014 Carlo Caione
*
* Author: Carlo Caione <carlo@caione.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/mfd/sunxi_pmc.h>
#include <linux/mfd/core.h>
#include <linux/of_device.h>
#include <linux/acpi.h>
static const char *const pmc_model_names[] = {
"PMC",
};
static const struct regmap_config pmc_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = PMC_ADDR_EXT,
.use_single_rw = true,
.cache_type = REGCACHE_NONE,
};
/*------------------*/
#define INIT_REGMAP_IRQ(_variant, _irq, _off, _mask) \
[_variant##_IRQ_##_irq] = { .reg_offset = (_off), .mask = BIT(_mask) }
static const struct regmap_irq pmc_regmap_irqs[] = {
INIT_REGMAP_IRQ(PMC, PWRON_INT_NEG, 0, PMC_IRQ_PWRON_INT_NEG),
INIT_REGMAP_IRQ(PMC, PWRON_INT_POS, 0, PMC_IRQ_PWRON_INT_POS),
INIT_REGMAP_IRQ(PMC, PWRON_INT_SLVL, 0, PMC_IRQ_PWRON_INT_SLVL),
INIT_REGMAP_IRQ(PMC, PWRON_INT_LLVL, 0, PMC_IRQ_PWRON_INT_LLVL),
INIT_REGMAP_IRQ(PMC, PWRON_INT_SUPER_KEY, 0, PMC_IRQ_PWRON_INT_SUPER_KEY),
INIT_REGMAP_IRQ(PMC, IRQ_INT_POS, 0, PMC_IRQ_IRQ_INT_POS),
INIT_REGMAP_IRQ(PMC, IRQ_INT_NEG, 0, PMC_IRQ_IRQ_INT_NEG),
INIT_REGMAP_IRQ(PMC, PWR_VBUS_IN_INT, 0, PMC_IRQ_PWR_VBUS_IN_INT),
INIT_REGMAP_IRQ(PMC, PWR_VBUS_OUT_INT, 0, PMC_IRQ_PWR_VBUS_OUT_INT),
};
static const struct regmap_irq_chip pmc_regmap_irq_chip = {
.name = "pmc_irq_chip",
.status_base = PWRON_INT_EN_REG,
.ack_base = PWRON_INT_EN_REG,
.mask_base = PWRON_INT_EN_REG,
.mask_invert = true,
.ack_invert = true,
.init_ack_masked = true,
.irqs = pmc_regmap_irqs,
.num_irqs = ARRAY_SIZE(pmc_regmap_irqs),
.num_regs = 1,
};
/*--------------------*/
static struct resource pmc_pek_resources[] = {
DEFINE_RES_IRQ_NAMED(PMC_IRQ_PWRON_INT_NEG, "PEK_DBF"),
DEFINE_RES_IRQ_NAMED(PMC_IRQ_PWRON_INT_POS, "PEK_DBR"),
};
static struct resource pmc_power_supply_resources[] = {
DEFINE_RES_IRQ_NAMED(PMC_IRQ_PWR_VBUS_IN_INT, "usb in"),
DEFINE_RES_IRQ_NAMED(PMC_IRQ_PWR_VBUS_OUT_INT, "usb out"),
};
#define PMC_DCDC1 "dcdc1"
#define PMC_DCDC2 "dcdc2"
#define PMC_DCDC3 "dcdc3"
#define PMC_LDOA "ldoa"
#define PMC_ALDO "aldo"
#define PMC_RTCLDO "rtcldo"
#define PMC_LDO33 "ldo33"
#define PMC_LDO09 "ldo09"
static struct mfd_cell pmc_cells[] = {
{
/* match drivers/regulator/sunxi_pmc-regulator.c */
.name = "pmc-regulator",
},
{
.name = "pmc-pek",
.of_compatible = "x-powers,pmc-pek",
.resources = pmc_pek_resources,
.num_resources = ARRAY_SIZE(pmc_pek_resources),
},
{
.name = "pmc-usb-power-supply",
.of_compatible = "x-powers,pmc-usb-power-supply",
.resources = pmc_power_supply_resources,
.num_resources = ARRAY_SIZE(pmc_power_supply_resources),
},
{
.of_compatible = "xpower-vregulator,dcdc1",
.name = "reg-virt-consumer",
.id = 1,
.platform_data = PMC_DCDC1,
.pdata_size = sizeof(PMC_DCDC1),
},
{
.of_compatible = "xpower-vregulator,dcdc2",
.name = "reg-virt-consumer",
.id = 2,
.platform_data = PMC_DCDC2,
.pdata_size = sizeof(PMC_DCDC2),
},
{
.of_compatible = "xpower-vregulator,dcdc3",
.name = "reg-virt-consumer",
.id = 3,
.platform_data = PMC_DCDC3,
.pdata_size = sizeof(PMC_DCDC3),
},
{
.of_compatible = "xpower-vregulator,ldoa",
.name = "reg-virt-consumer",
.id = 4,
.platform_data = PMC_LDOA,
.pdata_size = sizeof(PMC_LDOA),
},
{
.of_compatible = "xpower-vregulator,aldo",
.name = "reg-virt-consumer",
.id = 5,
.platform_data = PMC_ALDO,
.pdata_size = sizeof(PMC_ALDO),
},
{
.of_compatible = "xpower-vregulator,rtcldo",
.name = "reg-virt-consumer",
.id = 6,
.platform_data = PMC_RTCLDO,
.pdata_size = sizeof(PMC_RTCLDO),
},
{
.of_compatible = "xpower-vregulator,ldo33",
.name = "reg-virt-consumer",
.id = 7,
.platform_data = PMC_LDO33,
.pdata_size = sizeof(PMC_LDO33),
},
{
.of_compatible = "xpower-vregulator,ldo09",
.name = "reg-virt-consumer",
.id = 8,
.platform_data = PMC_LDO09,
.pdata_size = sizeof(PMC_LDO09),
},
};
/*----------------------*/
static struct sunxi_pmc_dev *sunxi_pmc_pm_power_off;
static void sunxi_pmc_power_off(void)
{
/* Give capacitors etc. time to drain to avoid kernel panic msg. */
msleep(500);
}
int sunxi_pmc_match_device(struct sunxi_pmc_dev *sunxi_pmc)
{
struct device *dev = sunxi_pmc->dev;
const struct acpi_device_id *acpi_id;
const struct of_device_id *of_id;
if (dev->of_node) {
of_id = of_match_device(dev->driver->of_match_table, dev);
if (!of_id) {
dev_err(dev, "Unable to match OF ID\n");
return -ENODEV;
}
sunxi_pmc->variant = (long)of_id->data;
} else {
acpi_id = acpi_match_device(dev->driver->acpi_match_table, dev);
if (!acpi_id || !acpi_id->driver_data) {
dev_err(dev, "Unable to match ACPI ID and data\n");
return -ENODEV;
}
sunxi_pmc->variant = (long)acpi_id->driver_data;
}
switch (sunxi_pmc->variant) {
case PMC_V1_ID:
sunxi_pmc->nr_cells = ARRAY_SIZE(pmc_cells);
sunxi_pmc->cells = pmc_cells;
sunxi_pmc->regmap_cfg = &pmc_regmap_config;
sunxi_pmc->regmap_irq_chip = &pmc_regmap_irq_chip;
break;
/*-------------------*/
default:
dev_err(dev, "unsupported sunxi_pmc ID %lu\n", sunxi_pmc->variant);
return -EINVAL;
}
dev_info(dev, "sunxi_pmc variant %s found\n",
pmc_model_names[sunxi_pmc->variant]);
return 0;
}
EXPORT_SYMBOL(sunxi_pmc_match_device);
int pmc_debug_mask;
static ssize_t debugmask_store(struct class *class,
struct class_attribute *attr,
const char *buf, size_t count)
{
int val, err;
err = kstrtoint(buf, 16, &val);
if (err)
return err;
pmc_debug_mask = val;
return count;
}
static ssize_t debugmask_show(struct class *class,
struct class_attribute *attr, char *buf)
{
char *s = buf;
char *end = (char *)((ptrdiff_t)buf + (ptrdiff_t)PAGE_SIZE);
s += scnprintf(s, end - s, "%s\n", "1: SPLY 2: REGU 4: INT 8: CHG");
s += scnprintf(s, end - s, "debug_mask=%d\n", pmc_debug_mask);
return s - buf;
}
static u32 pmc_reg_addr;
static ssize_t pmc_reg_show(struct class *class,
struct class_attribute *attr, char *buf)
{
u32 val;
regmap_read(sunxi_pmc_pm_power_off->regmap, pmc_reg_addr, &val);
return sprintf(buf, "REG[0x%x]=0x%x\n",
pmc_reg_addr, val);
}
static ssize_t pmc_reg_store(struct class *class,
struct class_attribute *attr,
const char *buf, size_t count)
{
s32 tmp;
u32 val;
int err;
err = kstrtoint(buf, 16, &tmp);
if (err)
return err;
if (tmp < 256) {
pmc_reg_addr = tmp;
} else {
val = tmp & 0x00FF;
pmc_reg_addr = (tmp >> 8) & 0x00FF;
regmap_write(sunxi_pmc_pm_power_off->regmap, pmc_reg_addr, val);
}
return count;
}
static struct class_attribute pmc_class_attrs[] = {
__ATTR(pmc_reg, S_IRUGO|S_IWUSR, pmc_reg_show, pmc_reg_store),
__ATTR(debug_mask, S_IRUGO|S_IWUSR, debugmask_show, debugmask_store),
__ATTR_NULL
};
struct class pmc_class = {
.name = "pmc",
.class_attrs = pmc_class_attrs,
};
static int pmc_sysfs_init(void)
{
int status;
status = class_register(&pmc_class);
if (status < 0)
pr_err("%s,%d err, status:%d\n", __func__, __LINE__, status);
return status;
}
int sunxi_pmc_device_probe(struct sunxi_pmc_dev *sunxi_pmc)
{
int ret;
/*
* on some board ex. qaqc test board, there's no interrupt for sunxi_pmc
*/
if (sunxi_pmc->irq) {
ret = regmap_add_irq_chip(sunxi_pmc->regmap, sunxi_pmc->irq,
IRQF_ONESHOT | IRQF_SHARED, -1,
sunxi_pmc->regmap_irq_chip,
&sunxi_pmc->regmap_irqc);
if (ret) {
dev_err(sunxi_pmc->dev, "failed to add irq chip: %d\n", ret);
return ret;
}
}
if (sunxi_pmc->dts_parse)
sunxi_pmc->dts_parse(sunxi_pmc);
ret = mfd_add_devices(sunxi_pmc->dev, 0, sunxi_pmc->cells,
sunxi_pmc->nr_cells, NULL, 0, NULL);
if (ret) {
dev_err(sunxi_pmc->dev, "failed to add MFD devices: %d\n", ret);
if (sunxi_pmc->irq)
regmap_del_irq_chip(sunxi_pmc->irq, sunxi_pmc->regmap_irqc);
return ret;
}
sunxi_pmc_pm_power_off = sunxi_pmc;
pmc_sysfs_init();
if (!pm_power_off) {
pm_power_off = sunxi_pmc_power_off;
}
dev_info(sunxi_pmc->dev, "sunxi_pmc driver loaded\n");
return 0;
}
EXPORT_SYMBOL(sunxi_pmc_device_probe);
int sunxi_pmc_device_remove(struct sunxi_pmc_dev *sunxi_pmc)
{
if (sunxi_pmc == sunxi_pmc_pm_power_off) {
sunxi_pmc_pm_power_off = NULL;
pm_power_off = NULL;
}
mfd_remove_devices(sunxi_pmc->dev);
if (sunxi_pmc->irq)
regmap_del_irq_chip(sunxi_pmc->irq, sunxi_pmc->regmap_irqc);
return 0;
}
EXPORT_SYMBOL(sunxi_pmc_device_remove);
MODULE_DESCRIPTION("PMIC MFD core driver for sunxi_pmc");
MODULE_AUTHOR("Carlo Caione <carlo@caione.org>");
MODULE_LICENSE("GPL");