mirror of
https://github.com/S3NEO/android_kernel_samsung_msm8226.git
synced 2024-11-07 03:47:13 +00:00
regulator: mem-acc-regulator: Add a driver to control the MEM ACC
The memory accelerator (MEM ACC) controls the delays applied during memory access. These delays vary based on the power-mode (corner) on which the memory is connected. This driver adds a regulator interface to configure the power-mode of the memory accelerator based on the APC (application processor) corner. CRs-fixed: 638439 Change-Id: I830ff000f8a16c4d3de5bdebf48d424354ddb086 Signed-off-by: Anirudh Ghayal <aghayal@codeaurora.org> Signed-off-by: Ke Liu <keliu@codeaurora.org>
This commit is contained in:
parent
8415fa61c2
commit
6ece10a174
4 changed files with 523 additions and 0 deletions
|
@ -0,0 +1,52 @@
|
|||
Qualcomm Technologies Memory Accelerator
|
||||
|
||||
Memory accelerator configures the power-mode (corner) for the
|
||||
accelerator.
|
||||
|
||||
Required properties:
|
||||
- compatible: Must be "qcom,mem-acc-regulator"
|
||||
- regulator-name: A string used to describe the regulator
|
||||
- regulator-min-microvolt: Minimum corner value as min constraint, which
|
||||
should be 1 for SVS corner
|
||||
- regulator-max-microvolt: Maximum corner value as max constraint, which
|
||||
should be 4 for SUPER_TURBO or 3 for TURBO
|
||||
- qcom,corner-acc-map Array which maps the APC (application processor)
|
||||
corner value to the accelerator corner.
|
||||
[0] maps APC SVS corner (1) to accelerator SVS corner
|
||||
[1] maps APC NOMINAL corner (2) to accelerator NOMINAL corner
|
||||
[2] maps APC TURBO corner (3) to accelerator TURBO corner
|
||||
|
||||
Optional properties:
|
||||
- reg: Register addresses for acc-en and acc-sel-l1 acc-sel-l2 control.
|
||||
- reg-names: Register names. Must be "acc-sel-l1", "acc-sel-l2", "acc-en".
|
||||
A given mem-acc-regulator driver must have "acc-sel-l1" or
|
||||
"acc-sel-l2" reg-names property and related register address
|
||||
property.
|
||||
- qcom,acc-en-bit-pos Array which specifies bit positions in the
|
||||
'acc-en' register. Setting these bits forces the
|
||||
the acclerator to use the corner value specified
|
||||
in the 'acc-sel-l1' and 'acc-sel-l2' register.
|
||||
- qcom,acc-sel-l1-bit-pos Array which specifies bit positions in the
|
||||
'acc-sel-l1' register. Each element in this array
|
||||
is the LSB of a 2-bit value. This 2-bit value
|
||||
specifies the corner value used by the
|
||||
accelerator for L1 cache.
|
||||
- qcom,acc-sel-l2-bit-pos Array which specifies bit positions in the
|
||||
'acc-sel-l2' register. Each element in this array
|
||||
is the LSB of a 2-bit value. This 2-bit value
|
||||
specifies the corner value used by the
|
||||
accelerator for L2 cache.
|
||||
|
||||
mem_acc_vreg_corner: regulator@fd4aa044 {
|
||||
compatible = "qcom,mem-acc-regulator";
|
||||
reg = <0xfd4aa048 0x1>, <0xfd4aa044 0x1>, <0xfd4af000 0x1>;
|
||||
reg-names = "acc-en", "acc-sel-l1" , "acc-sel-l2";
|
||||
regulator-name = "mem_acc_corner";
|
||||
regulator-min-microvolt = <1>;
|
||||
regulator-max-microvolt = <3>;
|
||||
|
||||
qcom,acc-en-bit-pos = <0>;
|
||||
qcom,acc-sel-l1-bit-pos = <0>;
|
||||
qcom,acc-sel-l2-bit-pos = <0>;
|
||||
qcom,corner-acc-map = <0 1 3>;
|
||||
};
|
|
@ -140,6 +140,15 @@ config REGULATOR_MC13892
|
|||
Say y here to support the regulators found on the Freescale MC13892
|
||||
PMIC.
|
||||
|
||||
config REGULATOR_MEM_ACC
|
||||
tristate "QTI Memory accelerator regulator driver"
|
||||
help
|
||||
Say y here to enable the memory accelerator driver for Qualcomm
|
||||
Technologies (QTI) chips. The accelerator controls delays applied
|
||||
for memory accesses.
|
||||
This driver configures the power-mode (corner) for the memory
|
||||
accelerator.
|
||||
|
||||
config REGULATOR_ISL6271A
|
||||
tristate "Intersil ISL6271A Power regulator"
|
||||
depends on I2C
|
||||
|
|
|
@ -34,6 +34,7 @@ obj-$(CONFIG_REGULATOR_MAX8998) += max8998.o
|
|||
obj-$(CONFIG_REGULATOR_MC13783) += mc13783-regulator.o
|
||||
obj-$(CONFIG_REGULATOR_MC13892) += mc13892-regulator.o
|
||||
obj-$(CONFIG_REGULATOR_MC13XXX_CORE) += mc13xxx-regulator-core.o
|
||||
obj-$(CONFIG_REGULATOR_MEM_ACC) += mem-acc-regulator.o
|
||||
obj-$(CONFIG_REGULATOR_ONSEMI_NCP6335D) += onsemi-ncp6335d.o
|
||||
obj-$(CONFIG_REGULATOR_PCAP) += pcap-regulator.o
|
||||
obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o
|
||||
|
|
461
drivers/regulator/mem-acc-regulator.c
Normal file
461
drivers/regulator/mem-acc-regulator.c
Normal file
|
@ -0,0 +1,461 @@
|
|||
/* Copyright (c) 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) "ACC: %s: " fmt, __func__
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/driver.h>
|
||||
#include <linux/regulator/machine.h>
|
||||
#include <linux/regulator/of_regulator.h>
|
||||
|
||||
#define MEM_ACC_SEL_MASK 0x3
|
||||
|
||||
enum {
|
||||
MEMORY_L1,
|
||||
MEMORY_L2,
|
||||
MEMORY_MAX,
|
||||
};
|
||||
|
||||
struct mem_acc_regulator {
|
||||
struct device *dev;
|
||||
struct regulator_desc rdesc;
|
||||
struct regulator_dev *rdev;
|
||||
|
||||
int corner;
|
||||
bool mem_acc_supported[MEMORY_MAX];
|
||||
|
||||
u32 acc_sel_reg[MEMORY_MAX];
|
||||
u32 *acc_sel_mask[MEMORY_MAX];
|
||||
u32 *acc_sel_bit_pos[MEMORY_MAX];
|
||||
u32 num_acc_sel[MEMORY_MAX];
|
||||
u32 *acc_en_bit_pos;
|
||||
u32 num_acc_en;
|
||||
u32 *corner_acc_map;
|
||||
u32 num_corners;
|
||||
|
||||
void __iomem *acc_sel_base[MEMORY_MAX];
|
||||
void __iomem *acc_en_base;
|
||||
phys_addr_t acc_sel_addr[MEMORY_MAX];
|
||||
phys_addr_t acc_en_addr;
|
||||
};
|
||||
|
||||
static inline u32 apc_to_acc_corner(struct mem_acc_regulator *mem_acc_vreg,
|
||||
int corner)
|
||||
{
|
||||
/*
|
||||
* corner_acc_map maps the corner from index 0 and APC corner value
|
||||
* starts from the value 1
|
||||
*/
|
||||
return mem_acc_vreg->corner_acc_map[corner - 1];
|
||||
}
|
||||
|
||||
static void __update_acc_sel(struct mem_acc_regulator *mem_acc_vreg,
|
||||
int corner, int mem_type)
|
||||
{
|
||||
u32 acc_data, i, bit, acc_corner;
|
||||
|
||||
acc_data = mem_acc_vreg->acc_sel_reg[mem_type];
|
||||
for (i = 0; i < mem_acc_vreg->num_acc_sel[mem_type]; i++) {
|
||||
bit = mem_acc_vreg->acc_sel_bit_pos[mem_type][i];
|
||||
acc_data &= ~mem_acc_vreg->acc_sel_mask[mem_type][i];
|
||||
acc_corner = apc_to_acc_corner(mem_acc_vreg, corner);
|
||||
acc_data |= (acc_corner << bit) &
|
||||
mem_acc_vreg->acc_sel_mask[mem_type][i];
|
||||
}
|
||||
pr_debug("corner=%d old_acc_sel=0x%02x new_acc_sel=0x%02x mem_type=%d\n",
|
||||
corner, mem_acc_vreg->acc_sel_reg[mem_type],
|
||||
acc_data, mem_type);
|
||||
writel_relaxed(acc_data, mem_acc_vreg->acc_sel_base[mem_type]);
|
||||
mem_acc_vreg->acc_sel_reg[mem_type] = acc_data;
|
||||
}
|
||||
|
||||
static void update_acc_sel(struct mem_acc_regulator *mem_acc_vreg, int corner)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MEMORY_MAX; i++) {
|
||||
if (mem_acc_vreg->mem_acc_supported[i])
|
||||
__update_acc_sel(mem_acc_vreg, corner, i);
|
||||
}
|
||||
}
|
||||
|
||||
static int mem_acc_regulator_set_voltage(struct regulator_dev *rdev,
|
||||
int corner, int corner_max, unsigned *selector)
|
||||
{
|
||||
struct mem_acc_regulator *mem_acc_vreg = rdev_get_drvdata(rdev);
|
||||
int i;
|
||||
|
||||
if (corner > mem_acc_vreg->num_corners) {
|
||||
pr_err("Invalid corner=%d requested\n", corner);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pr_debug("old corner=%d, new corner=%d\n",
|
||||
mem_acc_vreg->corner, corner);
|
||||
|
||||
if (corner == mem_acc_vreg->corner)
|
||||
return 0;
|
||||
|
||||
/* go up or down one level at a time */
|
||||
if (corner > mem_acc_vreg->corner) {
|
||||
for (i = mem_acc_vreg->corner + 1; i <= corner; i++) {
|
||||
pr_debug("UP: to corner %d\n", i);
|
||||
update_acc_sel(mem_acc_vreg, i);
|
||||
}
|
||||
} else {
|
||||
for (i = mem_acc_vreg->corner - 1; i >= corner; i--) {
|
||||
pr_debug("DOWN: to corner %d\n", i);
|
||||
update_acc_sel(mem_acc_vreg, i);
|
||||
}
|
||||
}
|
||||
|
||||
pr_debug("new voltage corner set %d\n", corner);
|
||||
|
||||
mem_acc_vreg->corner = corner;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mem_acc_regulator_get_voltage(struct regulator_dev *rdev)
|
||||
{
|
||||
struct mem_acc_regulator *mem_acc_vreg = rdev_get_drvdata(rdev);
|
||||
|
||||
return mem_acc_vreg->corner;
|
||||
}
|
||||
|
||||
static struct regulator_ops mem_acc_corner_ops = {
|
||||
.set_voltage = mem_acc_regulator_set_voltage,
|
||||
.get_voltage = mem_acc_regulator_get_voltage,
|
||||
};
|
||||
|
||||
static int __mem_acc_sel_init(struct mem_acc_regulator *mem_acc_vreg,
|
||||
int mem_type)
|
||||
{
|
||||
int i;
|
||||
u32 bit;
|
||||
|
||||
mem_acc_vreg->acc_sel_mask[mem_type] = devm_kzalloc(mem_acc_vreg->dev,
|
||||
mem_acc_vreg->num_acc_sel[mem_type] * sizeof(u32), GFP_KERNEL);
|
||||
if (!mem_acc_vreg->acc_sel_mask[mem_type]) {
|
||||
pr_err("Unable to allocate memory for mem_type=%d\n", mem_type);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
for (i = 0; i < mem_acc_vreg->num_acc_sel[mem_type]; i++) {
|
||||
bit = mem_acc_vreg->acc_sel_bit_pos[mem_type][i];
|
||||
mem_acc_vreg->acc_sel_mask[mem_type][i] =
|
||||
MEM_ACC_SEL_MASK << bit;
|
||||
}
|
||||
|
||||
mem_acc_vreg->acc_sel_reg[mem_type] =
|
||||
readl_relaxed(mem_acc_vreg->acc_sel_base[mem_type]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mem_acc_sel_init(struct mem_acc_regulator *mem_acc_vreg)
|
||||
{
|
||||
int i, rc;
|
||||
|
||||
for (i = 0; i < MEMORY_MAX; i++) {
|
||||
if (mem_acc_vreg->mem_acc_supported[i]) {
|
||||
rc = __mem_acc_sel_init(mem_acc_vreg, i);
|
||||
if (rc) {
|
||||
pr_err("Unable to intialize mem_type=%d rc=%d\n",
|
||||
i, rc);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mem_acc_en_init(struct mem_acc_regulator *mem_acc_vreg)
|
||||
{
|
||||
int i, bit;
|
||||
u32 acc_data;
|
||||
|
||||
acc_data = readl_relaxed(mem_acc_vreg->acc_en_base);
|
||||
pr_debug("init: acc_en_register=%x\n", acc_data);
|
||||
for (i = 0; i < mem_acc_vreg->num_acc_en; i++) {
|
||||
bit = mem_acc_vreg->acc_en_bit_pos[i];
|
||||
acc_data |= BIT(bit);
|
||||
}
|
||||
pr_debug("final: acc_en_register=%x\n", acc_data);
|
||||
writel_relaxed(acc_data, mem_acc_vreg->acc_en_base);
|
||||
}
|
||||
|
||||
static int populate_acc_data(struct mem_acc_regulator *mem_acc_vreg,
|
||||
const char *prop_name, u32 **value, u32 *len)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (!of_get_property(mem_acc_vreg->dev->of_node, prop_name, len)) {
|
||||
pr_err("Unable to find %s property\n", prop_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
*len /= sizeof(u32);
|
||||
if (!(*len)) {
|
||||
pr_err("Incorrect entries in %s\n", prop_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*value = devm_kzalloc(mem_acc_vreg->dev, (*len) * sizeof(u32),
|
||||
GFP_KERNEL);
|
||||
if (!(*value)) {
|
||||
pr_err("Unable to allocate memory for %s\n", prop_name);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pr_debug("Found %s, data-length = %d\n", prop_name, *len);
|
||||
|
||||
rc = of_property_read_u32_array(mem_acc_vreg->dev->of_node,
|
||||
prop_name, *value, *len);
|
||||
if (rc) {
|
||||
pr_err("Unable to populate %s rc=%d\n", prop_name, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mem_acc_sel_setup(struct mem_acc_regulator *mem_acc_vreg,
|
||||
struct resource *res, int mem_type)
|
||||
{
|
||||
int len, rc;
|
||||
char *mem_select_str;
|
||||
|
||||
mem_acc_vreg->acc_sel_addr[mem_type] = res->start;
|
||||
len = res->end - res->start + 1;
|
||||
pr_debug("'acc_sel_addr' = %pa mem_type=%d (len=%d)\n",
|
||||
&res->start, mem_type, len);
|
||||
|
||||
mem_acc_vreg->acc_sel_base[mem_type] = devm_ioremap(mem_acc_vreg->dev,
|
||||
mem_acc_vreg->acc_sel_addr[mem_type], len);
|
||||
if (!mem_acc_vreg->acc_sel_base[mem_type]) {
|
||||
pr_err("Unable to map 'acc_sel_addr' %pa for mem_type=%d\n",
|
||||
&mem_acc_vreg->acc_sel_addr[mem_type], mem_type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (mem_type) {
|
||||
case MEMORY_L1:
|
||||
mem_select_str = "qcom,acc-sel-l1-bit-pos";
|
||||
break;
|
||||
case MEMORY_L2:
|
||||
mem_select_str = "qcom,acc-sel-l2-bit-pos";
|
||||
break;
|
||||
}
|
||||
|
||||
rc = populate_acc_data(mem_acc_vreg, mem_select_str,
|
||||
&mem_acc_vreg->acc_sel_bit_pos[mem_type],
|
||||
&mem_acc_vreg->num_acc_sel[mem_type]);
|
||||
if (rc)
|
||||
pr_err("Unable to populate '%s' rc=%d\n", mem_select_str, rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int mem_acc_init(struct platform_device *pdev,
|
||||
struct mem_acc_regulator *mem_acc_vreg)
|
||||
{
|
||||
struct resource *res;
|
||||
int len, rc, i;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "acc-en");
|
||||
if (!res || !res->start) {
|
||||
pr_debug("'acc-en' resource missing or not used.\n");
|
||||
} else {
|
||||
mem_acc_vreg->acc_en_addr = res->start;
|
||||
len = res->end - res->start + 1;
|
||||
pr_debug("'acc_en_addr' = %pa (len=0x%x)\n", &res->start, len);
|
||||
|
||||
mem_acc_vreg->acc_en_base = devm_ioremap(mem_acc_vreg->dev,
|
||||
mem_acc_vreg->acc_en_addr, len);
|
||||
if (!mem_acc_vreg->acc_en_base) {
|
||||
pr_err("Unable to map 'acc_en_addr' %pa\n",
|
||||
&mem_acc_vreg->acc_en_addr);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = populate_acc_data(mem_acc_vreg, "qcom,acc-en-bit-pos",
|
||||
&mem_acc_vreg->acc_en_bit_pos,
|
||||
&mem_acc_vreg->num_acc_en);
|
||||
if (rc) {
|
||||
pr_err("Unable to populate 'qcom,acc-en-bit-pos' rc=%d\n",
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "acc-sel-l1");
|
||||
if (!res || !res->start) {
|
||||
pr_debug("'acc-sel-l1' resource missing or not used.\n");
|
||||
} else {
|
||||
rc = mem_acc_sel_setup(mem_acc_vreg, res, MEMORY_L1);
|
||||
if (rc) {
|
||||
pr_err("Unable to setup mem-acc for mem_type=%d rc=%d\n",
|
||||
MEMORY_L1, rc);
|
||||
return rc;
|
||||
}
|
||||
mem_acc_vreg->mem_acc_supported[MEMORY_L1] = true;
|
||||
}
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "acc-sel-l2");
|
||||
if (!res || !res->start) {
|
||||
pr_debug("'acc-sel-l2' resource missing or not used.\n");
|
||||
} else {
|
||||
rc = mem_acc_sel_setup(mem_acc_vreg, res, MEMORY_L2);
|
||||
if (rc) {
|
||||
pr_err("Unable to setup mem-acc for mem_type=%d rc=%d\n",
|
||||
MEMORY_L2, rc);
|
||||
return rc;
|
||||
}
|
||||
mem_acc_vreg->mem_acc_supported[MEMORY_L2] = true;
|
||||
}
|
||||
|
||||
rc = populate_acc_data(mem_acc_vreg, "qcom,corner-acc-map",
|
||||
&mem_acc_vreg->corner_acc_map,
|
||||
&mem_acc_vreg->num_corners);
|
||||
if (rc) {
|
||||
pr_err("Unable to find 'qcom,corner-acc-map' rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
pr_debug("num_corners = %d\n", mem_acc_vreg->num_corners);
|
||||
|
||||
/* Check if at least one valid mem-acc config. is specified */
|
||||
for (i = 0; i < MEMORY_MAX; i++) {
|
||||
if (mem_acc_vreg->mem_acc_supported[i])
|
||||
break;
|
||||
}
|
||||
if (i == MEMORY_MAX) {
|
||||
pr_err("No mem-acc configuration specified\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (mem_acc_vreg->num_acc_en)
|
||||
mem_acc_en_init(mem_acc_vreg);
|
||||
|
||||
rc = mem_acc_sel_init(mem_acc_vreg);
|
||||
if (rc) {
|
||||
pr_err("Unable to intialize mem_acc_sel reg rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mem_acc_regulator_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct mem_acc_regulator *mem_acc_vreg;
|
||||
struct regulator_desc *rdesc;
|
||||
struct regulator_init_data *init_data;
|
||||
int rc;
|
||||
|
||||
if (!pdev->dev.of_node) {
|
||||
pr_err("Device tree node is missing\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
init_data = of_get_regulator_init_data(&pdev->dev, pdev->dev.of_node);
|
||||
if (!init_data) {
|
||||
pr_err("regulator init data is missing\n");
|
||||
return -EINVAL;
|
||||
} else {
|
||||
init_data->constraints.input_uV
|
||||
= init_data->constraints.max_uV;
|
||||
init_data->constraints.valid_ops_mask
|
||||
|= REGULATOR_CHANGE_VOLTAGE;
|
||||
}
|
||||
|
||||
mem_acc_vreg = devm_kzalloc(&pdev->dev, sizeof(*mem_acc_vreg),
|
||||
GFP_KERNEL);
|
||||
if (!mem_acc_vreg) {
|
||||
pr_err("Can't allocate mem_acc_vreg memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
mem_acc_vreg->dev = &pdev->dev;
|
||||
|
||||
rc = mem_acc_init(pdev, mem_acc_vreg);
|
||||
if (rc) {
|
||||
pr_err("Unable to initialize mem_acc configuration rc=%d\n",
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rdesc = &mem_acc_vreg->rdesc;
|
||||
rdesc->owner = THIS_MODULE;
|
||||
rdesc->type = REGULATOR_VOLTAGE;
|
||||
rdesc->ops = &mem_acc_corner_ops;
|
||||
rdesc->name = init_data->constraints.name;
|
||||
|
||||
mem_acc_vreg->rdev = regulator_register(rdesc, &pdev->dev,
|
||||
init_data, mem_acc_vreg, pdev->dev.of_node);
|
||||
if (IS_ERR(mem_acc_vreg->rdev)) {
|
||||
rc = PTR_ERR(mem_acc_vreg->rdev);
|
||||
if (rc != -EPROBE_DEFER)
|
||||
pr_err("regulator_register failed: rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, mem_acc_vreg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mem_acc_regulator_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mem_acc_regulator *mem_acc_vreg = platform_get_drvdata(pdev);
|
||||
|
||||
regulator_unregister(mem_acc_vreg->rdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct of_device_id mem_acc_regulator_match_table[] = {
|
||||
{ .compatible = "qcom,mem-acc-regulator", },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver mem_acc_regulator_driver = {
|
||||
.probe = mem_acc_regulator_probe,
|
||||
.remove = mem_acc_regulator_remove,
|
||||
.driver = {
|
||||
.name = "qcom,mem-acc-regulator",
|
||||
.of_match_table = mem_acc_regulator_match_table,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
int __init mem_acc_regulator_init(void)
|
||||
{
|
||||
return platform_driver_register(&mem_acc_regulator_driver);
|
||||
}
|
||||
postcore_initcall(mem_acc_regulator_init);
|
||||
|
||||
static void __exit mem_acc_regulator_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&mem_acc_regulator_driver);
|
||||
}
|
||||
module_exit(mem_acc_regulator_exit);
|
||||
|
||||
MODULE_DESCRIPTION("MEM-ACC-SEL regulator driver");
|
||||
MODULE_LICENSE("GPL v2");
|
Loading…
Reference in a new issue