Drivers/PMIC: support pmic regulator function for hikey970.

Add PMIC code in kernel to support pmic regulator for hikey970.

Signed-off-by: mwx533604 <mwx533604@notesmail.huawei.com>
This commit is contained in:
mwx533604
2018-02-05 22:34:45 +08:00
committed by Guodong Xu
parent 72395c6263
commit 08464419fb
13 changed files with 2556 additions and 2 deletions
+210
View File
@@ -0,0 +1,210 @@
/*
* Copyright (C) 2012-2013 Linaro Ltd.
*
* 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
* publishhed by the Free Software Foundation.
*/
/*hisilicon,valid-modes-mask:
*#define REGULATOR_MODE_FAST 0x1
*#define REGULATOR_MODE_NORMAL 0x2
*#define REGULATOR_MODE_IDLE 0x4
*#define REGULATOR_MODE_STANDBY 0x8
*/
/*hisilicon,valid-idle-mask:
*#define REGULATOR_CHANGE_VOLTAGE 0x1
*#define REGULATOR_CHANGE_CURRENT 0x2
*#define REGULATOR_CHANGE_MODE 0x4
*#define REGULATOR_CHANGE_STATUS 0x8
*#define REGULATOR_CHANGE_DRMS 0x10
*#define REGULATOR_CHANGE_BYPASS 0x20
*/
/ {
spmi: spmi@FFF24000 {
pmic: pmic@0 {
compatible = "hisilicon-hisi-pmic-spmi";
slave_id = <0>;
reg = <0 0>;
#interrupt-cells = <2>;
interrupt-controller;
gpios = <&gpio28 0 0>;
pinctrl-names = "default";
pinctrl-0 = <>;
hisilicon,hisi-pmic-irq-num = <16>;/*hisi irq number*/
hisilicon,hisi-pmic-irq-array = <2>;/*hisi irq array*/
hisilicon,hisi-pmic-irq-mask-addr = <0x202 2>;/*hisi irq mask addr*/
hisilicon,hisi-pmic-irq-addr = <0x212 2>;/*hisi irq addr*/
hisilicon,pmic_fpga_flag=<0>;
hisilicon,hisi-pmic-vbus = <0x6 1>;/*vbus status addr and bit*/
hisilicon,hisi-pmic-lock = <0x125 0x7D>; /*lock: <addr value>*/
hisilicon,hisi-pmic-debug-lock = <0x244 0x6C>;/*debug-lock: <addr value>*/
spmi-slave-container;/*spmi-dev-container;*/
};
/* need to modify according to the design */
ldo3: ldo3@16 {
compatible = "hisilicon-hisi-ldo";
reg = <9 0>;
regulator-name = "ldo3";
regulator-min-microvolt = <1500000>;
regulator-max-microvolt = <2000000>;
hisilicon,hisi-ctrl = <0x16 0x01 0x00>;/*ctrl_reg、enable_mask、eco_mode_mask*/
hisilicon,hisi-vset = <0x51 0x0F>;
hisilicon,hisi-n-voltages = <16>;
hisilicon,hisi-vset-table = <1500000>, <1550000>,
<1600000>, <1650000>,
<1700000>, <1725000>,
<1750000>, <1775000>,
<1800000>, <1825000>,
<1850000>, <1875000>,
<1900000>, <1925000>,
<1950000>, <2000000>;
hisilicon,hisi-off-on-delay-us = <20000>;
hisilicon,hisi-enable-time-us = <120>;
hisilicon,hisi-eco-microamp = <10000>;
hisilicon,valid-modes-mask = <0x02>;
hisilicon,valid-idle-mask = <0x09>;
};
ldo4: ldo4@17 { /* 40 PIN */
compatible = "hisilicon-hisi-ldo";
reg = <1 0>;
regulator-name = "ldo4";
regulator-min-microvolt = <1725000>;
regulator-max-microvolt = <1900000>;
hisilicon,hisi-ctrl = <0x17 0x01 0x10>;/*ctrl_reg、enable_mask、eco_mode_mask*/
hisilicon,hisi-vset = <0x52 0x07>;
hisilicon,hisi-n-voltages = <8>;
hisilicon,hisi-vset-table = <1725000>, <1750000>,
<1775000>, <1800000>,
<1825000>, <1850000>,
<1875000>, <1900000>;
hisilicon,hisi-off-on-delay-us = <20000>;
hisilicon,hisi-enable-time-us = <120>;
hisilicon,hisi-eco-microamp = <10000>;
hisilicon,valid-modes-mask = <0x06>;
hisilicon,valid-idle-mask = <0x0D>;
};
ldo9: ldo9@1C { /* SDCARD I/O */
compatible = "hisilicon-hisi-ldo";
reg = <2 0>;
regulator-name = "ldo9";
regulator-min-microvolt = <1750000>;
regulator-max-microvolt = <3300000>;
hisilicon,hisi-ctrl = <0x1C 0x01 0x10>;
hisilicon,hisi-vset = <0x57 0x07>;
hisilicon,hisi-n-voltages = <8>;
hisilicon,hisi-vset-table = <1750000>, <1800000>,
<1825000>, <2800000>,
<2850000>, <2950000>,
<3000000>, <3300000>;
hisilicon,hisi-off-on-delay-us = <20000>;
hisilicon,hisi-enable-time-us = <360>;
hisilicon,hisi-eco-microamp = <10000>;
hisilicon,valid-modes-mask = <0x06>;
hisilicon,valid-idle-mask = <0x0D>;
};
ldo15: ldo15@21 { /* UFS */
compatible = "hisilicon-hisi-ldo";
reg = <3 0>;
regulator-name = "ldo15";
regulator-always-on;
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <3000000>;
hisilicon,hisi-ctrl = <0x21 0x01 0x10>;
hisilicon,hisi-vset = <0x5C 0x07>;
hisilicon,hisi-n-voltages = <8>;
hisilicon,hisi-vset-table = <1800000>, <1850000>,
<2400000>, <2600000>,
<2700000>, <2850000>,
<2950000>, <3000000>;
hisilicon,hisi-off-on-delay-us = <20000>;
hisilicon,hisi-enable-time-us = <120>;
hisilicon,hisi-eco-microamp = <10000>;
hisilicon,valid-modes-mask = <0x06>;
hisilicon,valid-idle-mask = <0x0D>;
};
ldo16: ldo16@22 { /* SD */
compatible = "hisilicon-hisi-ldo";
reg = <4 0>;
regulator-name = "ldo16";
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <3000000>;
hisilicon,hisi-ctrl = <0x22 0x01 0x10>;
hisilicon,hisi-vset = <0x5D 0x07>;
hisilicon,hisi-n-voltages = <8>;
hisilicon,hisi-vset-table = <1800000>, <1850000>,
<2400000>, <2600000>,
<2700000>, <2850000>,
<2950000>, <3000000>;
hisilicon,hisi-off-on-delay-us = <20000>;
hisilicon,hisi-enable-time-us = <360>;
hisilicon,hisi-eco-microamp = <10000>;
hisilicon,valid-modes-mask = <0x06>;
hisilicon,valid-idle-mask = <0x0D>;
};
ldo17: ldo17@23 {
compatible = "hisilicon-hisi-ldo";
reg = <7 0>;
regulator-name = "ldo17";
regulator-min-microvolt = <2500000>;
regulator-max-microvolt = <3300000>;
hisilicon,hisi-ctrl = <0x23 0x01 0x10>;
hisilicon,hisi-vset = <0x5E 0x07>;
hisilicon,hisi-n-voltages = <8>;
hisilicon,hisi-vset-table = <2500000>, <2600000>,
<2700000>, <2800000>,
<3000000>, <3100000>,
<3200000>, <3300000>;
hisilicon,hisi-off-on-delay-us = <20000>;
hisilicon,hisi-enable-time-us = <120>;
hisilicon,hisi-eco-microamp = <10000>;
hisilicon,valid-modes-mask = <0x06>;
hisilicon,valid-idle-mask = <0x0D>;
};
ldo33: ldo33@32 { /* PEX8606 */
compatible = "hisilicon-hisi-ldo";
reg = <5 0>;
regulator-name = "ldo33";
regulator-min-microvolt = <2500000>;
regulator-max-microvolt = <3300000>;
hisilicon,hisi-ctrl = <0x32 0x01 0x00>;
hisilicon,hisi-vset = <0x6D 0x07>;
hisilicon,hisi-n-voltages = <8>;
hisilicon,hisi-vset-table = <2500000>, <2600000>,
<2700000>, <2800000>,
<3000000>, <3100000>,
<3200000>, <3300000>;
hisilicon,hisi-off-on-delay-us = <20000>;
hisilicon,hisi-enable-time-us = <120>;
hisilicon,hisi-eco-microamp = <10000>;
hisilicon,valid-modes-mask = <0x02>;
hisilicon,valid-idle-mask = <0x09>;
};
ldo34: ldo34@33 { /* GPS AUX IN VDD*/
compatible = "hisilicon-hisi-ldo";
reg = <6 0>;
regulator-name = "ldo34";
regulator-min-microvolt = <2600000>;
regulator-max-microvolt = <3300000>;
hisilicon,hisi-ctrl = <0x33 0x01 0x00>;
hisilicon,hisi-vset = <0x6E 0x07>;
hisilicon,hisi-n-voltages = <8>;
hisilicon,hisi-vset-table = <2600000>, <2700000>,
<2800000>, <2900000>,
<3000000>, <3100000>,
<3200000>, <3300000>;
hisilicon,hisi-off-on-delay-us = <20000>;
hisilicon,hisi-enable-time-us = <120>;
hisilicon,hisi-eco-microamp = <10000>;
hisilicon,valid-modes-mask = <0x02>;
hisilicon,valid-idle-mask = <0x09>;
};
};/*end of spmi*/
};
+238
View File
@@ -0,0 +1,238 @@
// SPDX-License-Identifier: GPL-2.0
/*
* dts file for Hisilicon HiKey970 Development Board
*
* Copyright (C) 2016, Hisilicon Ltd.
*
*/
/dts-v1/;
#include "kirin970.dtsi"
#include "kirin970-gpu.dtsi"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
#include "kirin970_spmi.dtsi"
#include "hisi_6421v600_pmic_spmi.dtsi"
#include "kirin970-drm.dtsi"
#include "kirin970-ion.dtsi"
#include "kirin970_ipc.dtsi"
/ {
model = "HiKey970";
compatible = "Hisilicon,kirin970-hikey970", "Hisilicon,kirin970";
aliases {
mshc1 = &dwmmc1;
mshc2 = &dwmmc2;
serial0 = &uart0; /* UDP console UART */
serial1 = &uart1;
serial2 = &uart2;
serial3 = &uart3;
serial4 = &uart4;
serial5 = &uart5;
serial6 = &uart6;
};
chosen {
stdout-path = "serial6:115200n8";
};
memory@0 {
device_type = "memory";
/* rewrite this at bootloader */
reg = <0x0 0x0 0x0 0x0>;
};
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
bl31 {
reg = <0x0 0x1FE00000 0x0 0x400000>;
no-map;
};
ramoops_ram:pstore-mem{
reg = <0x0 0x20A00000 0x0 0x100000>;
no-map;
};
uefi-reboot-mode {
reg = <0x0 0x32100000 0x0 0x00001000>;
no-map;
};
hifi-base {
reg = <0x0 0x8D500000 0x0 0xC00000>;
no-map;
};
lpmx-core {
reg = <0x0 0x8E100000 0x0 0x100000>;
no-map;
};
lpmcu {
reg = <0x0 0x8E200000 0x0 0x40000>;
no-map;
};
hifi-data {
reg = <0x0 0x8E300000 0x0 0x500000>;
no-map;
};
};
reboot-mode-syscon@32100000 {
compatible = "syscon", "simple-mfd";
reg = <0x0 0x32100000 0x0 0x00001000>;
reboot-mode {
compatible = "syscon-reboot-mode";
offset = <0x0>;
mode-normal = <0x77665501>;
mode-bootloader = <0x77665500>;
mode-recovery = <0x77665502>;
};
};
pclk: apb_pclk {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <20000000>;
clock-output-names = "apb_pclk";
};
keys: gpio-keys {
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&pwr_key_pmx_func &pwr_key_cfg_func>;
power {
wakeup-source;
gpios = <&gpio25 3 GPIO_ACTIVE_LOW>;
label = "GPIO Power";
linux,code = <KEY_POWER>;
};
};
leds {
compatible = "gpio-leds";
can_active_led {
label = "can_active";
/* GPIO_012_USER_LED5*/
gpios = <&gpio1 4 0>;
linux,default-trigger = "can0-rxtx";
default-state = "off";
};
};
soc {
uart6: serial@fff32000 {
status = "okay";
};
};
hifidsp {
compatible = "hisilicon,k3hifidsp";
};
leds {
compatible = "gpio-leds";
user_led2 {
label = "user_led2";
/* gpio_151_user_led2 */
gpios = <&gpio20 7 0>;
linux,default-trigger = "mmc0";
};
wlan_active_led {
label = "wifi_active";
/* gpio_024_wifi_active */
gpios = <&gpio3 0 0>;
linux,default-trigger = "phy0tx";
default-state = "off";
};
};
vdec {
compatible = "hisi,kirin970-vdec";
reg = <0x0 0xe8800000 0x0 0x40000>;
interrupts = <0 290 4>, <0 291 4>, <0 292 4>;
clocks = <&media2_crg KIRIN970_CLK_GATE_VDECFREQ>;
clock-names = "clk_gate_vdecfreq";
/*clocks = <&clk_gate_vdec>;*/
/*clock-names = "clk_vdec";*/
dec_clk_rate = <450000000>, <300000000>, <185000000>;
vdec_fpga = <0x1>;
status = "ok"
iommu_info {
start-addr = <0x40000>;
size = <0xE0000000>;
};
};
venc {
compatible = "hisi,kirin970-venc";
reg = <0x0 0xe8880000 0x0 0x40000>;
interrupts = <0 296 4>, <0 297 4>, <0 298 4>;
clocks = <&media2_crg KIRIN970_CLK_GATE_VENCFREQ>;
clock-names = "clk_gate_vencfreq";
/*clocks = <&clk_gate_venc &venc_volt_hold>;*/
/*clock-names = "clk_venc", "venc_volt_hold";*/
enc_clk_rate = <554000000>, <450000000>, <238000000>;
venc_fpga = <0x1>;
status = "ok"
iommu_info {
start-addr = <0x40000>;
size = <0xE0000000>;
};
};
smmu_lpae{
compatible = "hisi,hisi-smmu-lpae";
status = "ok"
};
wlan_en: wlan-en-1-8v {
compatible = "regulator-fixed";
regulator-name = "wlan-en-regulator";
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <1800000>;
/* GPIO_051_WIFI_EN */
gpio = <&gpio6 3 0>;
regulator-always-on;
/* WLAN card specific delay */
startup-delay-us = <70000>;
enable-active-high;
};
};
&dwmmc1 {
vmmc-supply = <&ldo16>;
vqmmc-supply = <&ldo9>;
status = "okay";
};
&dwmmc2 { /* WIFI */
broken-cd;
/* WL_EN */
vmmc-supply = <&wlan_en>;
ti,non-removable;
non-removable;
#address-cells = <0x1>;
#size-cells = <0x0>;
status = "ok"
wlcore: wlcore@2 {
compatible = "ti,wl1837";
reg = <2>; /* sdio func num */
/* WL_IRQ, GPIO_177_WL_WAKEUP_AP */
interrupt-parent = <&gpio22>;
interrupts = <1 IRQ_TYPE_EDGE_RISING>;
};
};
+21
View File
@@ -0,0 +1,21 @@
/*
* Hisilicon Ltd. kirin970 SoC
*
* Copyright (C) 2014-2016 Hisilicon Ltd.
*
* 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
* publishhed by the Free Software Foundation.
*/
/{
spmi: spmi@FFF24000 {
compatible = "hisilicon,spmi-controller";
#address-cells = <2>;
#size-cells = <0>;
status = "ok"
reg = <0x0 0xfff24000 0x0 0x1000>;
spmi-channel = <2>;
};
};
Regular → Executable
+1 -1
View File
@@ -448,4 +448,4 @@ static int __init pl061_gpio_init(void)
{
return amba_driver_register(&pl061_gpio_driver);
}
device_initcall(pl061_gpio_init);
subsys_initcall(pl061_gpio_init);
Regular → Executable
+10
View File
@@ -358,6 +358,16 @@ config MFD_HI655X_PMIC
help
Select this option to enable Hisilicon hi655x series pmic driver.
config HISI_PMIC_PMU_SPMI
tristate "HiSilicon PMU/Codec IC and use spmi passageway"
depends on OF
depends on HISI_SPMI
help
This driver supports HiSilicon Hisi power management and codec IC,
including regulators, codec, ADCs, Coulomb counter, etc. Memory
mapped I/O ports are the way of communication with it. and use spmi
passageway read pmu address.
config HTC_PASIC3
tristate "HTC PASIC3 LED/DS1WM chip support"
select MFD_CORE
Regular → Executable
+1
View File
@@ -201,6 +201,7 @@ obj-$(CONFIG_MFD_IPAQ_MICRO) += ipaq-micro.o
obj-$(CONFIG_MFD_MENF21BMC) += menf21bmc.o
obj-$(CONFIG_MFD_HI6421_PMIC) += hi6421-pmic-core.o
obj-$(CONFIG_MFD_HI655X_PMIC) += hi655x-pmic.o
obj-$(CONFIG_HISI_PMIC_PMU_SPMI) += hisi_pmic_spmi.o
obj-$(CONFIG_MFD_DLN2) += dln2.o
obj-$(CONFIG_MFD_RT5033) += rt5033.o
obj-$(CONFIG_MFD_SKY81452) += sky81452.o
+759
View File
@@ -0,0 +1,759 @@
/*
* Device driver for regulators in HISI PMIC IC
*
* Copyright (c) 2013 Linaro Ltd.
* Copyright (c) 2011 Hisilicon.
*
*
* 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.
*
* 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.
*
*/
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/mfd/hisi_pmic.h>
#include <linux/irq.h>
#include <linux/spmi.h>
#ifndef NO_IRQ
#define NO_IRQ 0
#endif
/* 8-bit register offset in PMIC */
#define HISI_MASK_STATE 0xff
#define HISI_IRQ_KEY_NUM 0
#define HISI_IRQ_KEY_VALUE 0xc0
#define HISI_IRQ_KEY_DOWN 7
#define HISI_IRQ_KEY_UP 6
/*#define HISI_NR_IRQ 25*/
#define HISI_MASK_FIELD 0xFF
#define HISI_BITS 8
#define PMIC_FPGA_FLAG 1
/*define the first group interrupt register number*/
#define HISI_PMIC_FIRST_GROUP_INT_NUM 2
static struct bit_info g_pmic_vbus = {0};
#ifndef BIT
#define BIT(x) (0x1U << (x))
#endif
static struct hisi_pmic *g_pmic;
static unsigned int g_extinterrupt_flag = 0;
static struct of_device_id of_hisi_pmic_match_tbl[] = {
{
.compatible = "hisilicon-hisi-pmic-spmi",
},
{ /* end */ }
};
/*
* The PMIC register is only 8-bit.
* Hisilicon SoC use hardware to map PMIC register into SoC mapping.
* At here, we are accessing SoC register with 32-bit.
*/
u32 hisi_pmic_read(struct hisi_pmic *pmic, int reg)
{
u32 ret;
u8 read_value = 0;
struct spmi_device *pdev;
if (NULL == g_pmic) {
pr_err(" g_pmic is NULL\n");
return 0;
}
pdev = to_spmi_device(g_pmic->dev);
if (NULL == pdev) {
pr_err("%s:pdev get failed!\n", __func__);
return 0;
}
ret = spmi_ext_register_readl(pdev, reg, (unsigned char*)&read_value, 1);/*lint !e734 !e732 */
if (ret) {
pr_err("%s:spmi_ext_register_readl failed!\n", __func__);
return ret;
}
return (u32)read_value;
}
EXPORT_SYMBOL(hisi_pmic_read);
void hisi_pmic_write(struct hisi_pmic *pmic, int reg, u32 val)
{
u32 ret;
struct spmi_device *pdev;
if (NULL == g_pmic) {
pr_err(" g_pmic is NULL\n");
return;
}
pdev = to_spmi_device(g_pmic->dev);
if (NULL == pdev) {
pr_err("%s:pdev get failed!\n", __func__);
return;
}
ret = spmi_ext_register_writel(pdev, reg, (unsigned char*)&val, 1);/*lint !e734 !e732 */
if (ret) {
pr_err("%s:spmi_ext_register_writel failed!\n", __func__);
return ;
}
}
EXPORT_SYMBOL(hisi_pmic_write);
#ifdef CONFIG_HISI_DIEID
u32 hisi_pmic_read_sub_pmu(u8 sid, int reg)
{
u32 ret;
u8 read_value = 0;
struct spmi_device *pdev;
if(strstr(saved_command_line, "androidboot.swtype=factory"))
{
if (NULL == g_pmic) {
pr_err(" g_pmic is NULL\n");
return -1;/*lint !e570 */
}
pdev = to_spmi_device(g_pmic->dev);
if (NULL == pdev) {
pr_err("%s:pdev get failed!\n", __func__);
return -1;/*lint !e570 */
}
ret = spmi_ext_register_readl(pdev->ctrl, sid, reg, (unsigned char*)&read_value, 1);/*lint !e734 !e732 */
if (ret) {
pr_err("%s:spmi_ext_register_readl failed!\n", __func__);
return ret;
}
return (u32)read_value;
}
return 0;
}
EXPORT_SYMBOL(hisi_pmic_read_sub_pmu);
void hisi_pmic_write_sub_pmu(u8 sid, int reg, u32 val)
{
u32 ret;
struct spmi_device *pdev;
if(strstr(saved_command_line, "androidboot.swtype=factory"))
{
if (NULL == g_pmic) {
pr_err(" g_pmic is NULL\n");
return;
}
pdev = to_spmi_device(g_pmic->dev);
if (NULL == pdev) {
pr_err("%s:pdev get failed!\n", __func__);
return;
}
ret = spmi_ext_register_writel(pdev->ctrl, sid, reg, (unsigned char*)&val, 1);/*lint !e734 !e732 */
if (ret) {
pr_err("%s:spmi_ext_register_writel failed!\n", __func__);
return ;
}
}
return ;
}
EXPORT_SYMBOL(hisi_pmic_write_sub_pmu);
#endif
void hisi_pmic_rmw(struct hisi_pmic *pmic, int reg,
u32 mask, u32 bits)
{
u32 data;
unsigned long flags;
if (NULL == g_pmic) {
pr_err(" g_pmic is NULL\n");
return;
}
spin_lock_irqsave(&g_pmic->lock, flags);
data = hisi_pmic_read(pmic, reg) & ~mask;
data |= mask & bits;
hisi_pmic_write(pmic, reg, data);
spin_unlock_irqrestore(&g_pmic->lock, flags);
}
EXPORT_SYMBOL(hisi_pmic_rmw);
unsigned int hisi_pmic_reg_read(int addr)
{
return (unsigned int)hisi_pmic_read(g_pmic, addr);
}
EXPORT_SYMBOL(hisi_pmic_reg_read);
void hisi_pmic_reg_write(int addr, int val)
{
hisi_pmic_write(g_pmic, addr, val);
}
EXPORT_SYMBOL(hisi_pmic_reg_write);
void hisi_pmic_reg_write_lock(int addr, int val)
{
unsigned long flags;
if (NULL == g_pmic) {
pr_err(" g_pmic is NULL\n");
return;
}
spin_lock_irqsave(&g_pmic->lock, flags);
hisi_pmic_write(g_pmic, g_pmic->normal_lock.addr, g_pmic->normal_lock.val);
hisi_pmic_write(g_pmic, g_pmic->debug_lock.addr, g_pmic->debug_lock.val);
hisi_pmic_write(g_pmic, addr, val);
hisi_pmic_write(g_pmic, g_pmic->normal_lock.addr, 0);
hisi_pmic_write(g_pmic, g_pmic->debug_lock.addr, 0);
spin_unlock_irqrestore(&g_pmic->lock, flags);
}
int hisi_pmic_array_read(int addr, char *buff, unsigned int len)
{
unsigned int i;
if ((len > 32) || (NULL == buff)) {
return -EINVAL;
}
/*
* Here is a bug in the pmu die.
* the coul driver will read 4 bytes,
* but the ssi bus only read 1 byte, and the pmu die
* will make sampling 1/10669us about vol cur,so the driver
* read the data is not the same sampling
*/
for (i = 0; i < len; i++)
{
*(buff + i) = hisi_pmic_reg_read(addr+i);
}
return 0;
}
int hisi_pmic_array_write(int addr, char *buff, unsigned int len)
{
unsigned int i;
if ((len > 32) || (NULL == buff)) {
return -EINVAL;
}
for (i = 0; i < len; i++)
{
hisi_pmic_reg_write(addr+i, *(buff + i));
}
return 0;
}
static irqreturn_t hisi_irq_handler(int irq, void *data)
{
struct hisi_pmic *pmic = (struct hisi_pmic *)data;
unsigned long pending;
int i, offset;
for (i = 0; i < pmic->irqarray; i++) {
pending = hisi_pmic_reg_read((i + pmic->irq_addr.start_addr));
pending &= HISI_MASK_FIELD;
if (pending != 0) {
pr_info("pending[%d]=0x%lx\n\r", i, pending);
}
hisi_pmic_reg_write((i + pmic->irq_addr.start_addr), pending);
/*solve powerkey order*/
if ((HISI_IRQ_KEY_NUM == i) && ((pending & HISI_IRQ_KEY_VALUE) == HISI_IRQ_KEY_VALUE)) {
generic_handle_irq(pmic->irqs[HISI_IRQ_KEY_DOWN]);
generic_handle_irq(pmic->irqs[HISI_IRQ_KEY_UP]);
pending &= (~HISI_IRQ_KEY_VALUE);
}
if (pending) {
for_each_set_bit(offset, &pending, HISI_BITS)
generic_handle_irq(pmic->irqs[offset + i * HISI_BITS]);/*lint !e679 */
}
}
/*Handle the second group irq if analysis the second group irq from dtsi*/
if (1 == g_extinterrupt_flag){
for (i = 0; i < pmic->irqarray1; i++) {
pending = hisi_pmic_reg_read((i + pmic->irq_addr1.start_addr));
pending &= HISI_MASK_FIELD;
if (pending != 0) {
pr_info("pending[%d]=0x%lx\n\r", i, pending);
}
hisi_pmic_reg_write((i + pmic->irq_addr1.start_addr), pending);
if (pending) {
for_each_set_bit(offset, &pending, HISI_BITS)
generic_handle_irq(pmic->irqs[offset + (i+HISI_PMIC_FIRST_GROUP_INT_NUM) * HISI_BITS]);/*lint !e679 */
}
}
}
return IRQ_HANDLED;
}
static void hisi_irq_mask(struct irq_data *d)
{
struct hisi_pmic *pmic = irq_data_get_irq_chip_data(d);
u32 data, offset;
unsigned long flags;
if (NULL == g_pmic) {
pr_err(" g_pmic is NULL\n");
return;
}
offset = (irqd_to_hwirq(d) >> 3);
if (1==g_extinterrupt_flag){
if ( offset < HISI_PMIC_FIRST_GROUP_INT_NUM)
offset += pmic->irq_mask_addr.start_addr;
else/*Change addr when irq num larger than 16 because interrupt addr is nonsequence*/
offset = offset+(pmic->irq_mask_addr1.start_addr)-HISI_PMIC_FIRST_GROUP_INT_NUM;
}else{
offset += pmic->irq_mask_addr.start_addr;
}
spin_lock_irqsave(&g_pmic->lock, flags);
data = hisi_pmic_reg_read(offset);
data |= (1 << (irqd_to_hwirq(d) & 0x07));
hisi_pmic_reg_write(offset, data);
spin_unlock_irqrestore(&g_pmic->lock, flags);
}
static void hisi_irq_unmask(struct irq_data *d)
{
struct hisi_pmic *pmic = irq_data_get_irq_chip_data(d);
u32 data, offset;
unsigned long flags;
if (NULL == g_pmic) {
pr_err(" g_pmic is NULL\n");
return;
}
offset = (irqd_to_hwirq(d) >> 3);
if (1==g_extinterrupt_flag){
if ( offset < HISI_PMIC_FIRST_GROUP_INT_NUM)
offset += pmic->irq_mask_addr.start_addr;
else
offset = offset+(pmic->irq_mask_addr1.start_addr)-HISI_PMIC_FIRST_GROUP_INT_NUM;
}else{
offset += pmic->irq_mask_addr.start_addr;
}
spin_lock_irqsave(&g_pmic->lock, flags);
data = hisi_pmic_reg_read(offset);
data &= ~(1 << (irqd_to_hwirq(d) & 0x07)); /*lint !e502 */
hisi_pmic_reg_write(offset, data);
spin_unlock_irqrestore(&g_pmic->lock, flags);
}
static struct irq_chip hisi_pmu_irqchip = {
.name = "hisi-irq",
.irq_mask = hisi_irq_mask,
.irq_unmask = hisi_irq_unmask,
.irq_disable = hisi_irq_mask,
.irq_enable = hisi_irq_unmask,
};
static int hisi_irq_map(struct irq_domain *d, unsigned int virq,
irq_hw_number_t hw)
{
struct hisi_pmic *pmic = d->host_data;
irq_set_chip_and_handler_name(virq, &hisi_pmu_irqchip,
handle_simple_irq, "hisi");
irq_set_chip_data(virq, pmic);
irq_set_irq_type(virq, IRQ_TYPE_NONE);
return 0;
}
static struct irq_domain_ops hisi_domain_ops = {
.map = hisi_irq_map,
.xlate = irq_domain_xlate_twocell,
};
/*lint -e570 -e64*/
static int get_pmic_device_tree_data(struct device_node *np, struct hisi_pmic *pmic)
{
int ret = 0;
/*get pmic irq num*/
ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-irq-num",
&(pmic->irqnum), 1);
if (ret) {
pr_err("no hisilicon,hisi-pmic-irq-num property set\n");
ret = -ENODEV;
return ret;
}
/*get pmic irq array number*/
ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-irq-array",
&(pmic->irqarray), 1);
if (ret) {
pr_err("no hisilicon,hisi-pmic-irq-array property set\n");
ret = -ENODEV;
return ret;
}
/*SOC_PMIC_IRQ_MASK_0_ADDR*/
ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-irq-mask-addr",
(int *)&pmic->irq_mask_addr, 2);
if (ret) {
pr_err("no hisilicon,hisi-pmic-irq-mask-addr property set\n");
ret = -ENODEV;
return ret;
}
/*SOC_PMIC_IRQ0_ADDR*/
ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-irq-addr",
(int *)&pmic->irq_addr, 2);
if (ret) {
pr_err("no hisilicon,hisi-pmic-irq-addr property set\n");
ret = -ENODEV;
return ret;
}
ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-vbus",
(u32 *)&g_pmic_vbus, 2);
if (ret) {
pr_err("no hisilicon,hisi-pmic-vbus property\n");
ret = -ENODEV;
return ret;
}
/*pmic lock*/
ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-lock",
(int *)&pmic->normal_lock, 2);
if (ret) {
pr_err("no hisilicon,hisi-pmic-lock property set\n");
ret = -ENODEV;
return ret;
}
/*pmic debug lock*/
ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-debug-lock",
(int *)&pmic->debug_lock, 2);
if (ret) {
pr_err("no hisilicon,hisi-pmic-debug-lock property set\n");
ret = -ENODEV;
return ret;
}
return ret;
}/*lint -restore*/
/*lint -e570 -e64*/
static int get_pmic_device_tree_data1(struct device_node *np, struct hisi_pmic *pmic)
{
int ret = 0;
/*get pmic irq num*/
ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-irq-num1",
&(pmic->irqnum1), 1);
if (ret) {
pr_err("no hisilicon,hisi-pmic-irq-num1 property set\n");
ret = -ENODEV;
pmic->irqnum1 = 0;
return ret;
}
/*get pmic irq array number*/
ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-irq-array1",
&(pmic->irqarray1), 1);
if (ret) {
pr_err("no hisilicon,hisi-pmic-irq-array1 property set\n");
ret = -ENODEV;
return ret;
}
/*SOC_PMIC_IRQ_MASK_0_ADDR*/
ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-irq-mask-addr1",
(int *)&pmic->irq_mask_addr1, 2);
if (ret) {
pr_err("no hisilicon,hisi-pmic-irq-mask-addr1 property set\n");
ret = -ENODEV;
return ret;
}
/*SOC_PMIC_IRQ0_ADDR*/
ret = of_property_read_u32_array(np, "hisilicon,hisi-pmic-irq-addr1",
(int *)&pmic->irq_addr1, 2);
if (ret) {
pr_err("no hisilicon,hisi-pmic-irq-addr1 property set\n");
ret = -ENODEV;
return ret;
}
g_extinterrupt_flag = 1;
return ret;
}/*lint -restore*/
int hisi_get_pmic_irq_byname(unsigned int pmic_irq_list)
{
if ( NULL == g_pmic ) {
pr_err("[%s]g_pmic is NULL\n", __func__);
return -1;
}
if (pmic_irq_list > (unsigned int)g_pmic->irqnum) {
pr_err("[%s]input pmic irq number is error.\n", __func__);
return -1;
}
pr_info("%s:g_pmic->irqs[%d]=%d\n", __func__, pmic_irq_list, g_pmic->irqs[pmic_irq_list]);
return (int)g_pmic->irqs[pmic_irq_list];
}
EXPORT_SYMBOL(hisi_get_pmic_irq_byname);
int hisi_pmic_get_vbus_status(void)
{
if (0 == g_pmic_vbus.addr)
return -1;
if (hisi_pmic_reg_read(g_pmic_vbus.addr) & BIT(g_pmic_vbus.bit))
return 1;
return 0;
}
EXPORT_SYMBOL(hisi_pmic_get_vbus_status);
static void hisi_pmic_irq_prc(struct hisi_pmic *pmic)
{
int i;
for (i = 0 ; i < pmic->irq_mask_addr.array; i++) {
hisi_pmic_write(pmic, pmic->irq_mask_addr.start_addr + i, HISI_MASK_STATE);
}
for (i = 0 ; i < pmic->irq_addr.array; i++) {
unsigned int pending = hisi_pmic_read(pmic, pmic->irq_addr.start_addr + i);
pr_debug("PMU IRQ address value:irq[0x%x] = 0x%x\n", pmic->irq_addr.start_addr + i, pending);
hisi_pmic_write(pmic, pmic->irq_addr.start_addr + i, HISI_MASK_STATE);
}
}
static void hisi_pmic_irq1_prc(struct hisi_pmic *pmic)
{
int i;
if(1 == g_extinterrupt_flag){
for (i = 0 ; i < pmic->irq_mask_addr1.array; i++) {
hisi_pmic_write(pmic, pmic->irq_mask_addr1.start_addr + i, HISI_MASK_STATE);
}
for (i = 0 ; i < pmic->irq_addr1.array; i++) {
unsigned int pending1 = hisi_pmic_read(pmic, pmic->irq_addr1.start_addr + i);
pr_debug("PMU IRQ address1 value:irq[0x%x] = 0x%x\n", pmic->irq_addr1.start_addr + i, pending1);
hisi_pmic_write(pmic, pmic->irq_addr1.start_addr + i, HISI_MASK_STATE);
}
}
}
static int hisi_pmic_probe(struct spmi_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct hisi_pmic *pmic = NULL;
enum of_gpio_flags flags;
int ret = 0;
int i;
unsigned int fpga_flag = 0;
unsigned int virq;
pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL);
if (!pmic) {
dev_err(dev, "cannot allocate hisi_pmic device info\n");
return -ENOMEM;
}
/*TODO: get pmic dts info*/
ret = get_pmic_device_tree_data(np, pmic);
if (ret) {
dev_err(&pdev->dev, "Error reading hisi pmic dts \n");
return ret;
}
/*get pmic dts the second group irq*/
ret = get_pmic_device_tree_data1(np, pmic);
if (ret) {
dev_err(&pdev->dev, "the platform don't support ext-interrupt.\n");
}
/* TODO: get and enable clk request */
spin_lock_init(&pmic->lock);
pmic->dev = dev;
g_pmic = pmic;
ret = of_property_read_u32_array(np, "hisilicon,pmic_fpga_flag", &fpga_flag, 1);
if (ret) {
pr_err("no hisilicon,pmic_fpga_flag property set\n");
}
if (PMIC_FPGA_FLAG == fpga_flag) {
goto after_irq_register;
}
pmic->gpio = of_get_gpio_flags(np, 0, &flags);
if (pmic->gpio < 0)
return pmic->gpio;
if (!gpio_is_valid(pmic->gpio))
return -EINVAL;
ret = gpio_request_one(pmic->gpio, GPIOF_IN, "pmic");
if (ret < 0) {
dev_err(dev, "failed to request gpio%d\n", pmic->gpio);
return ret;
}
pmic->irq = gpio_to_irq(pmic->gpio);
/* mask && clear IRQ status */
hisi_pmic_irq_prc(pmic);
/*clear && mask the new adding irq*/
hisi_pmic_irq1_prc(pmic);
pmic->irqnum += pmic->irqnum1;
pmic->irqs = (unsigned int *)devm_kmalloc(dev, pmic->irqnum * sizeof(int), GFP_KERNEL);
if (!pmic->irqs) {
pr_err("%s:Failed to alloc memory for pmic irq number!\n", __func__);
goto irq_malloc;
}
memset(pmic->irqs, 0, pmic->irqnum);
pmic->domain = irq_domain_add_simple(np, pmic->irqnum, 0,
&hisi_domain_ops, pmic);
if (!pmic->domain) {
dev_err(dev, "failed irq domain add simple!\n");
ret = -ENODEV;
goto irq_domain;
}
for (i = 0; i < pmic->irqnum; i++) {
virq = irq_create_mapping(pmic->domain, i);
if (virq == NO_IRQ) {
pr_debug("Failed mapping hwirq\n");
ret = -ENOSPC;
goto irq_create_mapping;
}
pmic->irqs[i] = virq;
pr_info("[%s]. pmic->irqs[%d] = %d\n", __func__, i, pmic->irqs[i]);
}
ret = request_threaded_irq(pmic->irq, hisi_irq_handler, NULL,
IRQF_TRIGGER_LOW | IRQF_SHARED | IRQF_NO_SUSPEND,
"pmic", pmic);
if (ret < 0) {
dev_err(dev, "could not claim pmic %d\n", ret);
ret = -ENODEV;
goto request_theaded_irq;
}
after_irq_register:
return 0;
request_theaded_irq:
irq_create_mapping:
irq_domain:
irq_malloc:
gpio_free(pmic->gpio);
g_pmic = NULL;
return ret;
}
static void hisi_pmic_remove(struct spmi_device *pdev)
{
struct hisi_pmic *pmic = dev_get_drvdata(&pdev->dev);
free_irq(pmic->irq, pmic);
gpio_free(pmic->gpio);
devm_kfree(&pdev->dev, pmic);
}
static int hisi_pmic_suspend(struct device *dev, pm_message_t state)
{
struct hisi_pmic *pmic = dev_get_drvdata(dev);
if (NULL == pmic) {
pr_err("%s:pmic is NULL\n", __func__);
return -ENOMEM;
}
pr_info("%s:+\n", __func__);
pr_info("%s:-\n", __func__);
return 0;
}/*lint !e715 */
static int hisi_pmic_resume(struct device *dev)
{
struct hisi_pmic *pmic = dev_get_drvdata(dev);
if (NULL == pmic) {
pr_err("%s:pmic is NULL\n", __func__);
return -ENOMEM;
}
pr_info("%s:+\n", __func__);
pr_info("%s:-\n", __func__);
return 0;
}
MODULE_DEVICE_TABLE(spmi, pmic_spmi_id);
static struct spmi_driver hisi_pmic_driver = {
.driver = {
.name = "hisi_pmic",
.owner = THIS_MODULE,
.of_match_table = of_hisi_pmic_match_tbl,
.suspend = hisi_pmic_suspend,
.resume = hisi_pmic_resume,
},
.probe = hisi_pmic_probe,
.remove = hisi_pmic_remove,
};
static int __init hisi_pmic_init(void)
{
return spmi_driver_register(&hisi_pmic_driver);
}
static void __exit hisi_pmic_exit(void)
{
spmi_driver_unregister(&hisi_pmic_driver);
}
subsys_initcall_sync(hisi_pmic_init);
module_exit(hisi_pmic_exit);
MODULE_DESCRIPTION("PMIC driver");
MODULE_LICENSE("GPL v2");
Regular → Executable
+12 -1
View File
@@ -2028,7 +2028,18 @@ static struct platform_driver pcs_driver = {
#endif
};
module_platform_driver(pcs_driver);
static int __init pinctrl_single_init(void)
{
return platform_driver_register(&pcs_driver);
}
static void __exit pinctrl_single_exit(void)
{
platform_driver_unregister(&pcs_driver);
}
arch_initcall(pinctrl_single_init);
module_exit(pinctrl_single_exit);
MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
MODULE_DESCRIPTION("One-register-per-pin type device tree based pinctrl driver");
Regular → Executable
+7
View File
@@ -288,6 +288,13 @@ config REGULATOR_HI655X
This driver provides support for the voltage regulators of the
Hisilicon Hi655x PMIC device.
config HISI_REGULATOR_SPMI
tristate "HiSilicon PMIC regulators from spmi device"
depends on HISI_PMIC_PMU_SPMI
help
This driver provides support for the voltage regulators on the
HiSilicon Hisi PMU / Codec IC.
config REGULATOR_ISL9305
tristate "Intersil ISL9305 regulator"
depends on I2C
Regular → Executable
+1
View File
@@ -37,6 +37,7 @@ obj-$(CONFIG_REGULATOR_GPIO) += gpio-regulator.o
obj-$(CONFIG_REGULATOR_HI6421) += hi6421-regulator.o
obj-$(CONFIG_REGULATOR_HI6421V530) += hi6421v530-regulator.o
obj-$(CONFIG_REGULATOR_HI655X) += hi655x-regulator.o
obj-$(CONFIG_HISI_REGULATOR_SPMI) += hisi_regulator_spmi.o
obj-$(CONFIG_REGULATOR_ISL6271A) += isl6271a-regulator.o
obj-$(CONFIG_REGULATOR_ISL9305) += isl9305.o
obj-$(CONFIG_REGULATOR_LM363X) += lm363x-regulator.o
+741
View File
@@ -0,0 +1,741 @@
/*
* Device driver for regulators in Hisi IC
*
* Copyright (c) 2013 Linaro Ltd.
* Copyright (c) 2011 Hisilicon.
*
* Guodong Xu <guodong.xu@linaro.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.
*
* 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.
*
*/
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/regmap.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/regulator/of_regulator.h>
#include <linux/mfd/hisi_pmic.h>
#include <linux/delay.h>
#include <linux/time.h>
#include <linux/version.h>
#ifdef CONFIG_HISI_PMIC_DEBUG
#include <linux/debugfs.h>
#endif
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include <linux/spmi.h>
#if 1
#define BRAND_DEBUG(args...) pr_debug(args);
#else
#define BRAND_DEBUG(args...)
#endif
struct hisi_regulator_register_info {
u32 ctrl_reg;
u32 enable_mask;
u32 eco_mode_mask;
u32 vset_reg;
u32 vset_mask;
};
struct hisi_regulator {
const char *name;
struct hisi_regulator_register_info register_info;
struct timeval last_off_time;
u32 off_on_delay;
u32 eco_uA;
struct regulator_desc rdesc;
int (*dt_parse)(struct hisi_regulator *, struct spmi_device *);
};
static DEFINE_MUTEX(enable_mutex);
struct timeval last_enabled;
static inline struct hisi_pmic *rdev_to_pmic(struct regulator_dev *dev)
{
/* regulator_dev parent to->
* hisi regulator platform device_dev parent to->
* hisi pmic platform device_dev
*/
return dev_get_drvdata(rdev_get_dev(dev)->parent->parent);
}
/* helper function to ensure when it returns it is at least 'delay_us'
* microseconds after 'since'.
*/
static void ensured_time_after(struct timeval since, u32 delay_us)
{
struct timeval now;
u64 elapsed_ns64, delay_ns64;
u32 actual_us32;
delay_ns64 = delay_us * NSEC_PER_USEC;
do_gettimeofday(&now);
elapsed_ns64 = timeval_to_ns(&now) - timeval_to_ns(&since);
if (delay_ns64 > elapsed_ns64) {
actual_us32 = ((u32)(delay_ns64 - elapsed_ns64) /
NSEC_PER_USEC);
if (actual_us32 >= 1000) {
mdelay(actual_us32 / 1000); /*lint !e647 */
udelay(actual_us32 % 1000);
} else if (actual_us32 > 0) {
udelay(actual_us32);
}
}
return;
}
static int hisi_regulator_is_enabled(struct regulator_dev *dev)
{
u32 reg_val;
struct hisi_regulator *sreg = rdev_get_drvdata(dev);
struct hisi_pmic *pmic = rdev_to_pmic(dev);
reg_val = hisi_pmic_read(pmic, sreg->register_info.ctrl_reg);
BRAND_DEBUG("<[%s]: ctrl_reg=0x%x,enable_state=%d>\n", __func__, sreg->register_info.ctrl_reg,\
(reg_val & sreg->register_info.enable_mask));
return ((reg_val & sreg->register_info.enable_mask) != 0);
}
static int hisi_regulator_enable(struct regulator_dev *dev)
{
struct hisi_regulator *sreg = rdev_get_drvdata(dev);
struct hisi_pmic *pmic = rdev_to_pmic(dev);
/* keep a distance of off_on_delay from last time disabled */
ensured_time_after(sreg->last_off_time, sreg->off_on_delay);
BRAND_DEBUG("<[%s]: off_on_delay=%dus>\n", __func__, sreg->off_on_delay);
/* cannot enable more than one regulator at one time */
mutex_lock(&enable_mutex);
ensured_time_after(last_enabled, HISI_REGS_ENA_PROTECT_TIME);
/* set enable register */
hisi_pmic_rmw(pmic, sreg->register_info.ctrl_reg,
sreg->register_info.enable_mask,
sreg->register_info.enable_mask);
BRAND_DEBUG("<[%s]: ctrl_reg=0x%x,enable_mask=0x%x>\n", __func__, sreg->register_info.ctrl_reg,\
sreg->register_info.enable_mask);
do_gettimeofday(&last_enabled);
mutex_unlock(&enable_mutex);
return 0;
}
static int hisi_regulator_disable(struct regulator_dev *dev)
{
struct hisi_regulator *sreg = rdev_get_drvdata(dev);
struct hisi_pmic *pmic = rdev_to_pmic(dev);
/* set enable register to 0 */
hisi_pmic_rmw(pmic, sreg->register_info.ctrl_reg,
sreg->register_info.enable_mask, 0);
do_gettimeofday(&sreg->last_off_time);
return 0;
}
static int hisi_regulator_get_voltage(struct regulator_dev *dev)
{
struct hisi_regulator *sreg = rdev_get_drvdata(dev);
struct hisi_pmic *pmic = rdev_to_pmic(dev);
u32 reg_val, selector;
/* get voltage selector */
reg_val = hisi_pmic_read(pmic, sreg->register_info.vset_reg);
BRAND_DEBUG("<[%s]: vset_reg=0x%x>\n", __func__, sreg->register_info.vset_reg);
selector = (reg_val & sreg->register_info.vset_mask) >>
(ffs(sreg->register_info.vset_mask) - 1);
return sreg->rdesc.ops->list_voltage(dev, selector);
}
static int hisi_regulator_set_voltage(struct regulator_dev *dev,
int min_uV, int max_uV, unsigned *selector)
{
struct hisi_regulator *sreg = rdev_get_drvdata(dev);
struct hisi_pmic *pmic = rdev_to_pmic(dev);
u32 vsel;
int ret = 0;
for (vsel = 0; vsel < sreg->rdesc.n_voltages; vsel++) {
int uV = sreg->rdesc.volt_table[vsel];
/* Break at the first in-range value */
if (min_uV <= uV && uV <= max_uV)
break;
}
/* unlikely to happen. sanity test done by regulator core */
if (unlikely(vsel == sreg->rdesc.n_voltages))
return -EINVAL;
*selector = vsel;
/* set voltage selector */
hisi_pmic_rmw(pmic, sreg->register_info.vset_reg,
sreg->register_info.vset_mask,
vsel << (ffs(sreg->register_info.vset_mask) - 1));
BRAND_DEBUG("<[%s]: vset_reg=0x%x, vset_mask=0x%x, value=0x%x>\n", __func__,\
sreg->register_info.vset_reg,\
sreg->register_info.vset_mask,\
vsel << (ffs(sreg->register_info.vset_mask) - 1)\
);
return ret;
}
static unsigned int hisi_regulator_get_mode(struct regulator_dev *dev)
{
struct hisi_regulator *sreg = rdev_get_drvdata(dev);
struct hisi_pmic *pmic = rdev_to_pmic(dev);
u32 reg_val;
reg_val = hisi_pmic_read(pmic, sreg->register_info.ctrl_reg);
BRAND_DEBUG("<[%s]: reg_val=%d, ctrl_reg=0x%x, eco_mode_mask=0x%x>\n", __func__, reg_val,\
sreg->register_info.ctrl_reg,\
sreg->register_info.eco_mode_mask\
);
if (reg_val & sreg->register_info.eco_mode_mask)
return REGULATOR_MODE_IDLE;
else
return REGULATOR_MODE_NORMAL;
}
static int hisi_regulator_set_mode(struct regulator_dev *dev,
unsigned int mode)
{
struct hisi_regulator *sreg = rdev_get_drvdata(dev);
struct hisi_pmic *pmic = rdev_to_pmic(dev);
u32 eco_mode;
switch (mode) {
case REGULATOR_MODE_NORMAL:
eco_mode = HISI_ECO_MODE_DISABLE;
break;
case REGULATOR_MODE_IDLE:
eco_mode = HISI_ECO_MODE_ENABLE;
break;
default:
return -EINVAL;
}
/* set mode */
hisi_pmic_rmw(pmic, sreg->register_info.ctrl_reg,
sreg->register_info.eco_mode_mask,
eco_mode << (ffs(sreg->register_info.eco_mode_mask) - 1));
BRAND_DEBUG("<[%s]: ctrl_reg=0x%x, eco_mode_mask=0x%x, value=0x%x>\n", __func__,\
sreg->register_info.ctrl_reg,\
sreg->register_info.eco_mode_mask,\
eco_mode << (ffs(sreg->register_info.eco_mode_mask) - 1)\
);
return 0;
}
unsigned int hisi_regulator_get_optimum_mode(struct regulator_dev *dev,
int input_uV, int output_uV, int load_uA)
{
struct hisi_regulator *sreg = rdev_get_drvdata(dev);
if ((load_uA == 0) || ((unsigned int)load_uA > sreg->eco_uA))
return REGULATOR_MODE_NORMAL;
else
return REGULATOR_MODE_IDLE;
}
static int hisi_dt_parse_common(struct hisi_regulator *sreg,
struct spmi_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct regulator_desc *rdesc = &sreg->rdesc;
unsigned int register_info[3] = {0};
int ret = 0;
/* parse .register_info.ctrl_reg */
ret = of_property_read_u32_array(np, "hisilicon,hisi-ctrl",
register_info, 3);
if (ret) {
dev_err(dev, "no hisilicon,hisi-ctrl property set\n");
goto dt_parse_common_end;
}
sreg->register_info.ctrl_reg = register_info[0];
sreg->register_info.enable_mask = register_info[1];
sreg->register_info.eco_mode_mask = register_info[2];
/* parse .register_info.vset_reg */
ret = of_property_read_u32_array(np, "hisilicon,hisi-vset",
register_info, 2);
if (ret) {
dev_err(dev, "no hisilicon,hisi-vset property set\n");
goto dt_parse_common_end;
}
sreg->register_info.vset_reg = register_info[0];
sreg->register_info.vset_mask = register_info[1];
/* parse .off-on-delay */
ret = of_property_read_u32(np, "hisilicon,hisi-off-on-delay-us",
&sreg->off_on_delay);
if (ret) {
dev_err(dev, "no hisilicon,hisi-off-on-delay-us property set\n");
goto dt_parse_common_end;
}
/* parse .enable_time */
ret = of_property_read_u32(np, "hisilicon,hisi-enable-time-us",
&rdesc->enable_time);
if (ret) {
dev_err(dev, "no hisilicon,hisi-enable-time-us property set\n");
goto dt_parse_common_end;
}
/* parse .eco_uA */
ret = of_property_read_u32(np, "hisilicon,hisi-eco-microamp",
&sreg->eco_uA);
if (ret) {
sreg->eco_uA = 0;
ret = 0;
}
dt_parse_common_end:
return ret;
}
static int hisi_dt_parse_ldo(struct hisi_regulator *sreg,
struct spmi_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct regulator_desc *rdesc = &sreg->rdesc;
unsigned int *v_table;
int ret = 0;
/* parse .n_voltages, and .volt_table */
ret = of_property_read_u32(np, "hisilicon,hisi-n-voltages",
&rdesc->n_voltages);
if (ret) {
dev_err(dev, "no hisilicon,hisi-n-voltages property set\n");
goto dt_parse_ldo_end;
}
/* alloc space for .volt_table */
v_table = devm_kzalloc(dev, sizeof(unsigned int) * rdesc->n_voltages,
GFP_KERNEL);
if (unlikely(!v_table)) {
ret = -ENOMEM;
dev_err(dev, "no memory for .volt_table\n");
goto dt_parse_ldo_end;
}
ret = of_property_read_u32_array(np, "hisilicon,hisi-vset-table",
v_table, rdesc->n_voltages);
if (ret) {
dev_err(dev, "no hisilicon,hisi-vset-table property set\n");
goto dt_parse_ldo_end1;
}
rdesc->volt_table = v_table;
/* parse hisi regulator's dt common part */
ret = hisi_dt_parse_common(sreg, pdev);
if (ret) {
dev_err(dev, "failure in hisi_dt_parse_common\n");
goto dt_parse_ldo_end1;
}
return ret;
dt_parse_ldo_end1:
dt_parse_ldo_end:
return ret;
}
static struct regulator_ops hisi_ldo_rops = {
.is_enabled = hisi_regulator_is_enabled,
.enable = hisi_regulator_enable,
.disable = hisi_regulator_disable,
.list_voltage = regulator_list_voltage_table,
.get_voltage = hisi_regulator_get_voltage,
.set_voltage = hisi_regulator_set_voltage,
.get_mode = hisi_regulator_get_mode,
.set_mode = hisi_regulator_set_mode,
.get_optimum_mode = hisi_regulator_get_optimum_mode,
};
static const struct hisi_regulator hisi_regulator_ldo = {
.rdesc = {
.ops = &hisi_ldo_rops,
.type = REGULATOR_VOLTAGE,
.owner = THIS_MODULE,
},
.dt_parse = hisi_dt_parse_ldo,
};
static struct of_device_id of_hisi_regulator_match_tbl[] = {
{
.compatible = "hisilicon-hisi-ldo",
.data = &hisi_regulator_ldo,
},
{ /* end */ }
};
#ifdef CONFIG_HISI_PMIC_DEBUG
extern void get_current_regulator_dev(struct seq_file *s);
extern void set_regulator_state(char *ldo_name, int value);
extern void get_regulator_state(char *ldo_name);
extern int set_regulator_voltage(char *ldo_name, unsigned int vol_value);
u32 pmu_atoi(char *s)
{
char *p = s;
char c;
u64 ret = 0;
if (s == NULL)
return 0;
while ((c = *p++) != '\0') {
if ('0' <= c && c <= '9') {
ret *= 10;
ret += (u64)((unsigned char)c - '0');
if (ret > U32_MAX)
return 0;
} else {
break;
}
}
return (u32)ret;
}
static int dbg_hisi_regulator_show(struct seq_file *s, void *data)
{
seq_printf(s, "\n\r");
seq_printf(s, "%-13s %-15s %-15s %-15s %-15s\n\r",
"LDO_NAME", "ON/OFF", "Use_count", "Open_count", "Always_on");
seq_printf(s, "-----------------------------------------"
"-----------------------------------------------\n\r");
get_current_regulator_dev(s);
return 0;
}
static int dbg_hisi_regulator_open(struct inode *inode, struct file *file)
{
return single_open(file, dbg_hisi_regulator_show, inode->i_private);
}
static const struct file_operations debug_regulator_state_fops = {
.open = dbg_hisi_regulator_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int dbg_control_regulator_show(struct seq_file *s, void *data)
{
printk(" \n\r \
---------------------------------------------------------------------------------\n\r \
|usage: |\n\r \
| S = state R = read V = voltage |\n\r \
| set ldo state and voltage |\n\r \
| get ldo state and current voltage |\n\r \
|example: |\n\r \
| echo S ldo16 0 > control_regulator :disable ldo16 |\n\r \
| echo S ldo16 1 > control_regulator :enable ldo16 |\n\r \
| echo R ldo16 > control_regulator :get ldo16 state and voltage |\n\r \
| echo V ldo16 xxx > control_regulator :set ldo16 voltage |\n\r \
---------------------------------------------------------------------------------\n\r");
return 0;
}
static ssize_t dbg_control_regulator_set_value(struct file *filp, const char __user *buffer,
size_t count, loff_t *ppos)
{
char tmp[128] = {0};
char ptr[128] = {0};
char *vol = NULL;
char num = 0;
unsigned int i;
int next_flag = 1;
if (count >= 128) {
pr_info("error! buffer size big than internal buffer\n");
return -EFAULT;
}
if (copy_from_user(tmp, buffer, count)) {
pr_info("error!\n");
return -EFAULT;
}
if (tmp[0] == 'R' || tmp[0] == 'r') {
for (i = 2; i < (count - 1); i++) {
ptr[i - 2] = tmp[i];
}
ptr[i - 2] = '\0';
get_regulator_state(ptr);
} else if (tmp[0] == 'S' || tmp[0] == 's') {
for (i = 2; i < (count - 1); i++) {
if (tmp[i] == ' ') {
next_flag = 0;
ptr[i - 2] = '\0';
continue;
}
if (next_flag) {
ptr[i - 2] = tmp[i];
} else {
num = tmp[i] - 48;
}
}
set_regulator_state(ptr, num);
} else if (tmp[0] == 'V' || tmp[0] == 'v') {
for (i = 2; i < (count - 1); i++) {
if (tmp[i] == ' ') {
next_flag = 0;
ptr[i - 2] = '\0';
continue;
}
if (next_flag) {
ptr[i - 2] = tmp[i];
} else {
vol = &tmp[i];
break;
}
}
set_regulator_voltage(ptr, pmu_atoi(vol));
}
*ppos += count;
return count;
}
static int dbg_control_regulator_open(struct inode *inode, struct file *file)
{
file->private_data = inode->i_private;
return single_open(file, dbg_control_regulator_show, &inode->i_private);
}
static const struct file_operations set_control_regulator_fops = {
.open = dbg_control_regulator_open,
.read = seq_read,
.write = dbg_control_regulator_set_value,
.llseek = seq_lseek,
.release = single_release,
};
#endif
static int hisi_regulator_probe(struct spmi_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct regulator_desc *rdesc;
struct regulator_dev *rdev;
struct hisi_regulator *sreg = NULL;
struct regulator_init_data *initdata;
struct regulator_config config = { };
const struct of_device_id *match;
struct regulation_constraints *constraint;
const char *supplyname = NULL;
#ifdef CONFIG_HISI_PMIC_DEBUG
struct dentry *d;
static int debugfs_flag;
#endif
unsigned int temp_modes;
const struct hisi_regulator *template = NULL;
int ret = 0;
/* to check which type of regulator this is */
match = of_match_device(of_hisi_regulator_match_tbl, &pdev->dev);
if (NULL == match) {
pr_err("get hisi regulator fail!\n\r");
return -EINVAL;
}
template = match->data;
#if (LINUX_VERSION_CODE > KERNEL_VERSION(3, 13, 0))
initdata = of_get_regulator_init_data(dev, np, NULL);
#else
initdata = of_get_regulator_init_data(dev, np);
#endif
if (NULL == initdata) {
pr_err("get regulator init data error !\n");
return -EINVAL;
}
/* hisi regulator supports two modes */
constraint = &initdata->constraints;
ret = of_property_read_u32_array(np, "hisilicon,valid-modes-mask",
&(constraint->valid_modes_mask), 1);
if (ret) {
pr_err("no hisilicon,valid-modes-mask property set\n");
ret = -ENODEV;
return ret;
}
ret = of_property_read_u32_array(np, "hisilicon,valid-idle-mask",
&temp_modes, 1);
if (ret) {
pr_err("no hisilicon,valid-modes-mask property set\n");
ret = -ENODEV;
return ret;
}
constraint->valid_ops_mask |= temp_modes;
sreg = kmemdup(template, sizeof(*sreg), GFP_KERNEL);
if (!sreg) {
pr_err("template kememdup is fail. \n");
return -ENOMEM;
}
sreg->name = initdata->constraints.name;
rdesc = &sreg->rdesc;
rdesc->name = sreg->name;
rdesc->min_uV = initdata->constraints.min_uV;
supplyname = of_get_property(np, "hisilicon,supply_name", NULL);
if (supplyname != NULL) {
initdata->supply_regulator = supplyname;
}
/* to parse device tree data for regulator specific */
ret = sreg->dt_parse(sreg, pdev);
if (ret) {
dev_err(dev, "device tree parameter parse error!\n");
goto hisi_probe_end;
}
config.dev = &pdev->dev;
config.init_data = initdata;
config.driver_data = sreg;
config.of_node = pdev->dev.of_node;
/* register regulator */
rdev = regulator_register(rdesc, &config);
if (IS_ERR(rdev)) {
dev_err(dev, "failed to register %s\n",
rdesc->name);
ret = PTR_ERR(rdev);
goto hisi_probe_end;
}
BRAND_DEBUG("[%s]:valid_modes_mask[0x%x], valid_ops_mask[0x%x]\n", rdesc->name,\
constraint->valid_modes_mask, constraint->valid_ops_mask);
dev_set_drvdata(dev, rdev);
#ifdef CONFIG_HISI_PMIC_DEBUG
if (debugfs_flag == 0) {
d = debugfs_create_dir("hisi_regulator_debugfs", NULL);
if (!d) {
dev_err(dev, "failed to create hisi regulator debugfs dir !\n");
ret = -ENOMEM;
goto hisi_probe_fail;
}
(void) debugfs_create_file("regulator_state", S_IRUSR,
d, NULL, &debug_regulator_state_fops);
(void) debugfs_create_file("control_regulator", S_IRUSR,
d, NULL, &set_control_regulator_fops);
debugfs_flag = 1;
}
#endif
#ifdef CONFIG_HISI_PMIC_DEBUG
hisi_probe_fail:
if (ret)
regulator_unregister(rdev);
#endif
hisi_probe_end:
if (ret)
kfree(sreg);
return ret;
}
static void hisi_regulator_remove(struct spmi_device *pdev)
{
struct regulator_dev *rdev = dev_get_drvdata(&pdev->dev);
struct hisi_regulator *sreg = rdev_get_drvdata(rdev);
regulator_unregister(rdev);
/* TODO: should i worry about that? devm_kzalloc */
if (sreg->rdesc.volt_table)
devm_kfree(&pdev->dev, (unsigned int *)sreg->rdesc.volt_table);
kfree(sreg);
}
static int hisi_regulator_suspend(struct device *dev, pm_message_t state)
{
struct hisi_regulator *hisi_regulator = dev_get_drvdata(dev);
if (NULL == hisi_regulator) {
pr_err("%s:regulator is NULL\n", __func__);
return -ENOMEM;
}
pr_info("%s:+\n", __func__);
pr_info("%s:-\n", __func__);
return 0;
}/*lint !e715 */
static int hisi_regulator_resume(struct device *dev)
{
struct hisi_regulator *hisi_regulator = dev_get_drvdata(dev);
if (NULL == hisi_regulator) {
pr_err("%s:regulator is NULL\n", __func__);
return -ENOMEM;
}
pr_info("%s:+\n", __func__);
pr_info("%s:-\n", __func__);
return 0;
}
static struct spmi_driver hisi_pmic_driver = {
.driver = {
.name = "hisi_regulator",
.owner = THIS_MODULE,
.of_match_table = of_hisi_regulator_match_tbl,
.suspend = hisi_regulator_suspend,
.resume = hisi_regulator_resume,
},
.probe = hisi_regulator_probe,
.remove = hisi_regulator_remove,
};
static int __init hisi_regulator_init(void)
{
return spmi_driver_register(&hisi_pmic_driver);
}
static void __exit hisi_regulator_exit(void)
{
spmi_driver_unregister(&hisi_pmic_driver);
}
fs_initcall(hisi_regulator_init);
module_exit(hisi_regulator_exit);
MODULE_DESCRIPTION("Hisi regulator driver");
MODULE_LICENSE("GPL v2");
+390
View File
@@ -0,0 +1,390 @@
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <linux/spmi.h>
#include <linux/spmi.h>
#define SPMI_CONTROLLER_NAME "spmi_controller"
/*
* SPMI register addr
*/
#define SPMI_CHANNEL_OFFSET 0x0300
#define SPMI_SLAVE_OFFSET 0x20
#define SPMI_APB_SPMI_CMD_BASE_ADDR 0x0100
/*lint -e750 -esym(750,*)*/
#define SPMI_APB_SPMI_WDATA0_BASE_ADDR 0x0104
#define SPMI_APB_SPMI_WDATA1_BASE_ADDR 0x0108
#define SPMI_APB_SPMI_WDATA2_BASE_ADDR 0x010c
#define SPMI_APB_SPMI_WDATA3_BASE_ADDR 0x0110
#define SPMI_APB_SPMI_STATUS_BASE_ADDR 0x0200
#define SPMI_APB_SPMI_RDATA0_BASE_ADDR 0x0204
#define SPMI_APB_SPMI_RDATA1_BASE_ADDR 0x0208
#define SPMI_APB_SPMI_RDATA2_BASE_ADDR 0x020c
#define SPMI_APB_SPMI_RDATA3_BASE_ADDR 0x0210
/*lint +e750 -esym(750,*)*/
#define SPMI_PER_DATAREG_BYTE 4
/*
* SPMI cmd register
*/
#define SPMI_APB_SPMI_CMD_EN (1 << 31)
#define SPMI_APB_SPMI_CMD_TYPE_OFFSET 24
#define SPMI_APB_SPMI_CMD_LENGTH_OFFSET 20
#define SPMI_APB_SPMI_CMD_SLAVEID_OFFSET 16
#define SPMI_APB_SPMI_CMD_ADDR_OFFSET 0
#define Tranverse32(X) ((((u32)(X) & 0xff000000) >> 24) | \
(((u32)(X) & 0x00ff0000) >> 8) | \
(((u32)(X) & 0x0000ff00) << 8) | \
(((u32)(X) & 0x000000ff) << 24))
/* Command Opcodes */
/*lint -e749 -esym(749,*)*/
enum spmi_controller_cmd_op_code {
SPMI_CMD_REG_ZERO_WRITE = 0,
SPMI_CMD_REG_WRITE = 1,
SPMI_CMD_REG_READ = 2,
SPMI_CMD_EXT_REG_WRITE = 3,
SPMI_CMD_EXT_REG_READ = 4,
SPMI_CMD_EXT_REG_WRITE_L = 5,
SPMI_CMD_EXT_REG_READ_L = 6,
SPMI_CMD_REG_RESET = 7,
SPMI_CMD_REG_SLEEP = 8,
SPMI_CMD_REG_SHUTDOWN = 9,
SPMI_CMD_REG_WAKEUP = 10,
};
/*lint +e749 -esym(749,*)*/
/*
* SPMI status register
*/
#define SPMI_APB_TRANS_DONE (1 << 0)
#define SPMI_APB_TRANS_FAIL (1 << 2)
/* Command register fields */
#define SPMI_CONTROLLER_CMD_MAX_BYTE_COUNT 16
/* Maximum number of support PMIC peripherals */
#define SPMI_CONTROLLER_TIMEOUT_US 1000
#define SPMI_CONTROLLER_MAX_TRANS_BYTES (16)
#define SPMI_WRITEL( dev, reg, addr ) \
do { \
writel( ( reg ), ( addr ) ); \
} while (0)
#define SPMI_READL( dev, reg, addr ) \
do { \
reg = readl( addr ); \
} while (0)
/*
* @base base address of the PMIC Arbiter core registers.
* @rdbase, @wrbase base address of the PMIC Arbiter read core registers.
* For HW-v1 these are equal to base.
* For HW-v2, the value is the same in eeraly probing, in order to read
* PMIC_ARB_CORE registers, then chnls, and obsrvr are set to
* PMIC_ARB_CORE_REGISTERS and PMIC_ARB_CORE_REGISTERS_OBS respectivly.
* @intr base address of the SPMI interrupt control registers
* @ppid_2_chnl_tbl lookup table f(SID, Periph-ID) -> channel num
* entry is only valid if corresponding bit is set in valid_ppid_bitmap.
* @valid_ppid_bitmap bit is set only for valid ppids.
* @fmt_cmd formats a command to be set into PMIC_ARBq_CHNLn_CMD
* @chnl_ofst calculates offset of the base of a channel reg space
* @ee execution environment id
* @irq_acc0_init_val initial value of the interrupt accumulator at probe time.
* Use for an HW workaround. On handling interrupts, the first accumulator
* register will be compared against this value, and bits which are set at
* boot will be ignored.
* @reserved_chnl entry of ppid_2_chnl_tbl that this driver should never touch.
* value is positive channel number or negative to mark it unused.
*/
struct spmi_controller_dev {
struct spmi_controller *controller;
struct device *dev;
void __iomem *base;
spinlock_t lock;
u32 channel;
};
static int spmi_controller_wait_for_done(struct spmi_controller_dev *ctrl_dev,
void __iomem *base, u8 sid, u16 addr)
{
u32 status = 0;
u32 timeout = SPMI_CONTROLLER_TIMEOUT_US;
u32 offset = SPMI_APB_SPMI_STATUS_BASE_ADDR + SPMI_CHANNEL_OFFSET * ctrl_dev->channel
+ SPMI_SLAVE_OFFSET * sid;
while (timeout--) {
SPMI_READL(ctrl_dev->dev, status, base + offset);/*lint !e732 */
if (status & SPMI_APB_TRANS_DONE) {
if (status & SPMI_APB_TRANS_FAIL) {
dev_err(ctrl_dev->dev,
"%s: transaction failed (0x%x)\n",
__func__, status);
return -EIO;
}
return 0;
}
udelay(1);/*lint !e778 !e774 !e747*/
}
dev_err(ctrl_dev->dev,
"%s: timeout, status 0x%x\n",
__func__, status);
return -ETIMEDOUT;/*lint !e438*/
}/*lint !e715 !e529*/
static int spmi_read_cmd(struct spmi_controller *ctrl,
u8 opc, u8 sid, u16 addr, u8 *buf, size_t bc)
{
struct spmi_controller_dev *spmi_controller = dev_get_drvdata(&ctrl->dev);
unsigned long flags;
u32 cmd, data;
int rc;
u32 chnl_ofst = SPMI_CHANNEL_OFFSET*spmi_controller->channel;
u8 op_code, i;
if (bc > SPMI_CONTROLLER_MAX_TRANS_BYTES) {
dev_err(spmi_controller->dev
, "spmi_controller supports 1..%d bytes per trans, but:%ld requested"
, SPMI_CONTROLLER_MAX_TRANS_BYTES, bc);
return -EINVAL;
}
/* Check the opcode */
if (SPMI_CMD_READ == opc)
op_code = SPMI_CMD_REG_READ;
else if (SPMI_CMD_EXT_READ == opc)
op_code = SPMI_CMD_EXT_REG_READ;
else if (SPMI_CMD_EXT_READL == opc)
op_code = SPMI_CMD_EXT_REG_READ_L;
else {
dev_err(spmi_controller->dev, "invalid read cmd 0x%x", opc);
return -EINVAL;
}
cmd = SPMI_APB_SPMI_CMD_EN |/*lint !e648 !e701 */ /* cmd_en */
(op_code << SPMI_APB_SPMI_CMD_TYPE_OFFSET) |/*lint !e648 !e701 */ /* cmd_type */
((bc-1) << SPMI_APB_SPMI_CMD_LENGTH_OFFSET) |/*lint !e648 !e701 */ /* byte_cnt */
((sid & 0xf) << SPMI_APB_SPMI_CMD_SLAVEID_OFFSET) | /* slvid */
((addr & 0xffff) << SPMI_APB_SPMI_CMD_ADDR_OFFSET); /* slave_addr */
spin_lock_irqsave(&spmi_controller->lock, flags);/*lint !e550 */
SPMI_WRITEL(spmi_controller->dev, cmd, spmi_controller->base + chnl_ofst + SPMI_APB_SPMI_CMD_BASE_ADDR);
rc = spmi_controller_wait_for_done(spmi_controller, spmi_controller->base, sid, addr);
if (rc)
goto done;
i = 0;
do {
SPMI_READL(spmi_controller->dev, data, spmi_controller->base + chnl_ofst + SPMI_SLAVE_OFFSET*sid + SPMI_APB_SPMI_RDATA0_BASE_ADDR + i*SPMI_PER_DATAREG_BYTE);/*lint !e732 */
data = Tranverse32(data);
if ((bc - i*SPMI_PER_DATAREG_BYTE ) >> 2) {/*lint !e702 */
memcpy(buf, &data, sizeof(data));
buf += sizeof(data);
} else {
memcpy(buf, &data, bc%SPMI_PER_DATAREG_BYTE);/*lint !e747 */
buf += (bc%SPMI_PER_DATAREG_BYTE);
}
i++;
} while (bc > i*SPMI_PER_DATAREG_BYTE);
done:
spin_unlock_irqrestore(&spmi_controller->lock, flags);
if (rc)
dev_err(spmi_controller->dev, "spmi read wait timeout op:0x%x sid:%d addr:0x%x bc:%ld\n",
opc, sid, addr, bc + 1);
return rc;
}/*lint !e550 !e529*/
/*lint -e438 -esym(438,*)*/
static int spmi_write_cmd(struct spmi_controller *ctrl,
u8 opc, u8 sid, u16 addr, const u8 *buf, size_t bc)
{
struct spmi_controller_dev *spmi_controller = dev_get_drvdata(&ctrl->dev);
unsigned long flags;
u32 cmd;
u32 data = 0;
int rc;
u32 chnl_ofst = SPMI_CHANNEL_OFFSET*spmi_controller->channel;
u8 op_code, i;
if (bc > SPMI_CONTROLLER_MAX_TRANS_BYTES) {
dev_err(spmi_controller->dev
, "spmi_controller supports 1..%d bytes per trans, but:%ld requested"
, SPMI_CONTROLLER_MAX_TRANS_BYTES, bc);
return -EINVAL;
}
/* Check the opcode */
if (SPMI_CMD_WRITE == opc)
op_code = SPMI_CMD_REG_WRITE;
else if (SPMI_CMD_EXT_WRITE == opc)
op_code = SPMI_CMD_EXT_REG_WRITE;
else if (SPMI_CMD_EXT_WRITEL == opc)
op_code = SPMI_CMD_EXT_REG_WRITE_L;
else {
dev_err(spmi_controller->dev, "invalid write cmd 0x%x", opc);
return -EINVAL;
}
cmd = SPMI_APB_SPMI_CMD_EN |/*lint !e648 !e701 */ /* cmd_en */
(op_code << SPMI_APB_SPMI_CMD_TYPE_OFFSET) |/*lint !e648 !e701 */ /* cmd_type */
((bc-1) << SPMI_APB_SPMI_CMD_LENGTH_OFFSET) |/*lint !e648 !e701 */ /* byte_cnt */
((sid & 0xf) << SPMI_APB_SPMI_CMD_SLAVEID_OFFSET) | /* slvid */
((addr & 0xffff) << SPMI_APB_SPMI_CMD_ADDR_OFFSET); /* slave_addr */
/* Write data to FIFOs */
spin_lock_irqsave(&spmi_controller->lock, flags);/*lint !e550 */
i = 0;
do {
memset(&data, 0, sizeof(data));
if ((bc - i*SPMI_PER_DATAREG_BYTE ) >> 2) {/*lint !e702 */
memcpy(&data, buf, sizeof(data));
buf +=sizeof(data);
} else {
memcpy(&data, buf, bc%SPMI_PER_DATAREG_BYTE);/*lint !e747 */
buf +=(bc%SPMI_PER_DATAREG_BYTE);
}
data = Tranverse32(data);
SPMI_WRITEL(spmi_controller->dev, data, spmi_controller->base + chnl_ofst + SPMI_APB_SPMI_WDATA0_BASE_ADDR+SPMI_PER_DATAREG_BYTE*i);
i++;
} while (bc > i*SPMI_PER_DATAREG_BYTE);
/* Start the transaction */
SPMI_WRITEL(spmi_controller->dev, cmd, spmi_controller->base + chnl_ofst + SPMI_APB_SPMI_CMD_BASE_ADDR);
rc = spmi_controller_wait_for_done(spmi_controller, spmi_controller->base, sid, addr);
spin_unlock_irqrestore(&spmi_controller->lock, flags);
if (rc)
dev_err(spmi_controller->dev, "spmi write wait timeout op:0x%x sid:%d addr:0x%x bc:%ld\n",
opc, sid, addr, bc);
return rc;
}/*lint !e438 !e550 !e529*/
/*lint +e438 -esym(438,*)*/
static int spmi_controller_probe(struct platform_device *pdev)
{
struct spmi_controller_dev *spmi_controller;
struct spmi_controller *ctrl;
struct resource *iores;
int ret = 0;
printk(KERN_INFO "HISI SPMI probe\n");
ctrl = spmi_controller_alloc(&pdev->dev, sizeof(*spmi_controller));
if (!ctrl) {
dev_err(&pdev->dev, "can not allocate spmi_controller data\n");
return -ENOMEM; /*lint !e429*/
}
spmi_controller = spmi_controller_get_drvdata(ctrl);
spmi_controller->controller = ctrl;
/* NOTE: driver uses the static register mapping */
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!iores) {
dev_err(&pdev->dev, "can not get resource! \n");
return -EINVAL; /*lint !e429*/
}
spmi_controller->base = ioremap(iores->start, resource_size(iores));
if (!spmi_controller->base) {
dev_err(&pdev->dev, "can not remap base addr! \n");
return -EADDRNOTAVAIL; /*lint !e429*/
}
dev_dbg(&pdev->dev, "spmi_add_controller base addr=0x%lx!\n", (long unsigned int)spmi_controller->base);/*lint !e774*/
/* Get properties from the device tree */
ret = of_property_read_u32(pdev->dev.of_node, "spmi-channel",
&spmi_controller->channel);/*lint !e838*/
if (ret) {
dev_err(&pdev->dev, "can not get chanel \n");
return -ENODEV; /*lint !e429*/
}
platform_set_drvdata(pdev, spmi_controller);
dev_set_drvdata(&ctrl->dev, spmi_controller);
spin_lock_init(&spmi_controller->lock);
ctrl->nr = spmi_controller->channel;
ctrl->dev.parent = pdev->dev.parent;
ctrl->dev.of_node = of_node_get(pdev->dev.of_node);
/* Callbacks */
ctrl->read_cmd = spmi_read_cmd;
ctrl->write_cmd = spmi_write_cmd;
ret = spmi_controller_add(ctrl);
if (ret) {
dev_err(&pdev->dev, "spmi_add_controller failed!\n");
goto err_add_controller;
}
err_add_controller:
platform_set_drvdata(pdev, NULL);
return ret; /*lint !e429*/
}
static int spmi_del_controller(struct platform_device *pdev)
{
struct spmi_controller *ctrl = platform_get_drvdata(pdev);
platform_set_drvdata(pdev, NULL);
spmi_controller_remove(ctrl);
return 0;
}
static struct of_device_id spmi_controller_match_table[] = {
{ .compatible = "hisilicon,spmi-controller",
},/*lint !e785*/
{}/*lint !e785*/
};
static struct platform_driver spmi_controller_driver = {
.probe = spmi_controller_probe,
.remove = spmi_del_controller,
.driver = {
.name = SPMI_CONTROLLER_NAME,
.owner = THIS_MODULE,/*lint !e64*/
.of_match_table = spmi_controller_match_table,
},/*lint !e785*/
};/*lint !e785*/
/*lint -e528 -esym(528,*)*/
static int __init spmi_controller_init(void)
{
return platform_driver_register(&spmi_controller_driver);/*lint !e64*/
}
postcore_initcall(spmi_controller_init);
static void __exit spmi_controller_exit(void)
{
platform_driver_unregister(&spmi_controller_driver);
}
module_exit(spmi_controller_exit);
/*lint -e753 -esym(753,*)*/
MODULE_LICENSE("GPL v2");
MODULE_VERSION("1.0");/*lint !e785 !e64 !e528*/
MODULE_ALIAS("platform:spmi_controlller");
/*lint -e753 +esym(753,*)*/
/*lint -e528 +esym(528,*)*/
+165
View File
@@ -0,0 +1,165 @@
/*
* Header file for device driver Hi6421 PMIC
*
* Copyright (c) 2013 Linaro Ltd.
* Copyright (C) 2011 Hisilicon.
*
* Guodong Xu <guodong.xu@linaro.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.
*
* 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef __HISI_PMIC_H
#define __HISI_PMIC_H
#include <linux/irqdomain.h>
#define HISI_REGS_ENA_PROTECT_TIME (0) /* in microseconds */
#define HISI_ECO_MODE_ENABLE (1)
#define HISI_ECO_MODE_DISABLE (0)
typedef int (*pmic_ocp_callback)(char *);
extern int hisi_pmic_special_ocp_register(char *power_name, pmic_ocp_callback handler);
struct irq_mask_info {
int start_addr;
int array;
};
struct irq_info {
int start_addr;
int array;
};
struct bit_info {
int addr;
int bit;
};
struct write_lock {
int addr;
int val;
};
struct hisi_pmic {
struct resource *res;
struct device *dev;
void __iomem *regs;
spinlock_t lock;
struct irq_domain *domain;
int irq;
int gpio;
unsigned int *irqs;
int irqnum;
int irqarray;
struct irq_mask_info irq_mask_addr;
struct irq_info irq_addr;
int irqnum1;
int irqarray1;
struct irq_mask_info irq_mask_addr1;
struct irq_info irq_addr1;
struct write_lock normal_lock;
struct write_lock debug_lock;
};
/* 0:disable; 1:enable */
unsigned int get_uv_mntn_status(void);
void clear_uv_mntn_resered_reg_bit(void);
void set_uv_mntn_resered_reg_bit(void);
#if defined(CONFIG_HISI_PMIC) || defined(CONFIG_HISI_PMIC_PMU_SPMI)
/* Register Access Helpers */
u32 hisi_pmic_read(struct hisi_pmic *pmic, int reg);
void hisi_pmic_write(struct hisi_pmic *pmic, int reg, u32 val);
void hisi_pmic_rmw(struct hisi_pmic *pmic, int reg, u32 mask, u32 bits);
unsigned int hisi_pmic_reg_read(int addr);
void hisi_pmic_reg_write(int addr, int val);
void hisi_pmic_reg_write_lock(int addr, int val);
int hisi_pmic_array_read(int addr, char *buff, unsigned int len);
int hisi_pmic_array_write(int addr, char *buff, unsigned int len);
extern int hisi_get_pmic_irq_byname(unsigned int pmic_irq_list);
extern int hisi_pmic_get_vbus_status(void);
#if defined(CONFIG_HISI_DIEID)
u32 hisi_pmic_read_sub_pmu(u8 sid ,int reg);
void hisi_pmic_write_sub_pmu(u8 sid ,int reg, u32 val);
#endif
#else
static inline u32 hisi_pmic_read(struct hisi_pmic *pmic, int reg) { return 0; }
static inline void hisi_pmic_write(struct hisi_pmic *pmic, int reg, u32 val) {}
static inline void hisi_pmic_rmw(struct hisi_pmic *pmic, int reg, u32 mask, u32 bits) {}
static inline unsigned int hisi_pmic_reg_read(int addr) { return 0; }
static inline void hisi_pmic_reg_write(int addr, int val) {}
static inline void hisi_pmic_reg_write_lock(int addr, int val) {}
static inline int hisi_pmic_array_read(int addr, char *buff, unsigned int len) { return 0; }
static inline int hisi_pmic_array_write(int addr, char *buff, unsigned int len) { return 0; }
static inline int hisi_get_pmic_irq_byname(unsigned int pmic_irq_list) { return -1; }
static inline int hisi_pmic_get_vbus_status(void) { return 1; }
static inline u32 hisi_pmic_read_sub_pmu(u8 sid ,int reg) { return 0; }
static inline void hisi_pmic_write_sub_pmu(u8 sid ,int reg, u32 val) {}
#endif
#ifdef CONFIG_HISI_HI6421V500_PMU
enum pmic_irq_list {
POR_D45MR = 0,
VBUS_CONNECT,
VBUS_DISCONNECT,
ALARMON_R,
HOLD_6S,
HOLD_1S,
POWERKEY_UP,
POWERKEY_DOWN,
OCP_SCP_R,
COUL_R,
VSYS_OV,
VSYS_UV,
VSYS_PWROFF_ABS,
VSYS_PWROFF_DEB,
THSD_OTMP140,
THSD_OTMP125,
HRESETN,
SIM0_HPD_R = 24,
SIM0_HPD_F,
SIM0_HPD_H,
SIM0_HPD_L,
SIM1_HPD_R,
SIM1_HPD_F,
SIM1_HPD_H,
SIM1_HPD_L,
PMIC_IRQ_LIST_MAX,
};
#else
enum pmic_irq_list {
OTMP = 0,
VBUS_CONNECT,
VBUS_DISCONNECT,
ALARMON_R,
HOLD_6S,
HOLD_1S,
POWERKEY_UP,
POWERKEY_DOWN,
OCP_SCP_R,
COUL_R,
SIM0_HPD_R,
SIM0_HPD_F,
SIM1_HPD_R,
SIM1_HPD_F,
PMIC_IRQ_LIST_MAX,
};
#endif
#ifdef CONFIG_HISI_SR_DEBUG
extern void get_ip_regulator_state(void);
#endif
#endif /* __HISI_PMIC_H */