Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
linux
Commits
59aedb6d
Commit
59aedb6d
authored
Jul 01, 2013
by
Mark Brown
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'regulator/topic/abb' into regulator-next
parents
c84130e7
d26ec830
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
1049 additions
and
0 deletions
+1049
-0
Documentation/devicetree/bindings/regulator/ti-abb-regulator.txt
...tation/devicetree/bindings/regulator/ti-abb-regulator.txt
+128
-0
drivers/regulator/Kconfig
drivers/regulator/Kconfig
+10
-0
drivers/regulator/Makefile
drivers/regulator/Makefile
+1
-0
drivers/regulator/ti-abb-regulator.c
drivers/regulator/ti-abb-regulator.c
+910
-0
No files found.
Documentation/devicetree/bindings/regulator/ti-abb-regulator.txt
0 → 100644
View file @
59aedb6d
Adaptive Body Bias(ABB) SoC internal LDO regulator for Texas Instruments SoCs
Required Properties:
- compatible: Should be one of:
- "ti,abb-v1" for older SoCs like OMAP3
- "ti,abb-v2" for newer SoCs like OMAP4, OMAP5
- reg: Address and length of the register set for the device. It contains
the information of registers in the same order as described by reg-names
- reg-names: Should contain the reg names
- "base-address" - contains base address of ABB module
- "int-address" - contains address of interrupt register for ABB module
(also see Optional properties)
- #address-cell: should be 0
- #size-cell: should be 0
- clocks: should point to the clock node used by ABB module
- ti,settling-time: Settling time in uSecs from SoC documentation for ABB module
to settle down(target time for SR2_WTCNT_VALUE).
- ti,clock-cycles: SoC specific data about count of system ti,clock-cycles used for
computing settling time from SoC Documentation for ABB module(clock
cycles for SR2_WTCNT_VALUE).
- ti,tranxdone-status-mask: Mask to the int-register to write-to-clear mask
indicating LDO tranxdone (operation complete).
- ti,abb_info: An array of 6-tuples u32 items providing information about ABB
configuration needed per operational voltage of the device.
Each item consists of the following in the same order:
volt: voltage in uV - Only used to index ABB information.
ABB mode: one of the following:
0-bypass
1-Forward Body Bias(FBB)
3-Reverse Body Bias(RBB)
efuse: (see Optional properties)
RBB enable efuse Mask: (See Optional properties)
FBB enable efuse Mask: (See Optional properties)
Vset value efuse Mask: (See Optional properties)
NOTE: If more than 1 entry is present, then regulator is setup to change
voltage, allowing for various modes to be selected indexed off
the regulator. Further, ABB LDOs are considered always-on by
default.
Optional Properties:
- reg-names: In addition to the required properties, the following are optional
- "efuse-address" - Contains efuse base address used to pick up ABB info.
- "ldo-address" - Contains address of ABB LDO overide register address.
"efuse-address" is required for this.
- ti,ldovbb-vset-mask - Required if ldo-address is set, mask for LDO override
register to provide override vset value.
- ti,ldovbb-override-mask - Required if ldo-address is set, mask for LDO
override register to enable override vset value.
- ti,abb_opp_sel: Addendum to the description in required properties
efuse: Mandatory if 'efuse-address' register is defined. Provides offset
from efuse-address to pick up ABB characteristics. Set to 0 if
'efuse-address' is not defined.
RBB enable efuse Mask: Optional if 'efuse-address' register is defined.
'ABB mode' is force set to RBB mode if value at "efuse-address"
+ efuse maps to RBB mask. Set to 0 to ignore this.
FBB enable efuse Mask: Optional if 'efuse-address' register is defined.
'ABB mode' is force set to FBB mode if value at "efuse-address"
+ efuse maps to FBB mask (valid only if RBB mask does not match)
Set to 0 to ignore this.
Vset value efuse Mask: Mandatory if ldo-address is set. Picks up from
efuse the value to set in 'ti,ldovbb-vset-mask' at ldo-address.
Example #1: Simplest configuration (no efuse data, hard coded ABB table):
abb_x: regulator-abb-x {
compatible = "ti,abb-v1";
regulator-name = "abb_x";
#address-cell = <0>;
#size-cells = <0>;
reg = <0x483072f0 0x8>, <0x48306818 0x4>;
reg-names = "base-address", "int-address";
ti,tranxdone-status-mask = <0x4000000>;
clocks = <&sysclk>;
ti,settling-time = <30>;
ti,clock-cycles = <8>;
ti,abb_info = <
/* uV ABB efuse rbb_m fbb_m vset_m */
1012500 0 0 0 0 0 /* Bypass */
1200000 3 0 0 0 0 /* RBB mandatory */
1320000 1 0 0 0 0 /* FBB mandatory */
>;
};
Example #2: Efuse bits contain ABB mode setting (no LDO override capability)
abb_y: regulator-abb-y {
compatible = "ti,abb-v2";
regulator-name = "abb_y";
#address-cell = <0>;
#size-cells = <0>;
reg = <0x4a307bd0 0x8>, <0x4a306014 0x4>, <0x4A002268 0x8>;
reg-names = "base-address", "int-address", "efuse-address";
ti,tranxdone-status-mask = <0x4000000>;
clocks = <&sysclk>;
ti,settling-time = <50>;
ti,clock-cycles = <16>;
ti,abb_info = <
/* uV ABB efuse rbb_m fbb_m vset_m */
975000 0 0 0 0 0 /* Bypass */
1012500 0 0 0x40000 0 0 /* RBB optional */
1200000 0 0x4 0 0x40000 0 /* FBB optional */
1320000 1 0 0 0 0 /* FBB mandatory */
>;
};
Example #3: Efuse bits contain ABB mode setting and LDO override capability
abb_z: regulator-abb-z {
compatible = "ti,abb-v2";
regulator-name = "abb_z";
#address-cell = <0>;
#size-cells = <0>;
reg = <0x4ae07ce4 0x8>, <0x4ae06010 0x4>,
<0x4a002194 0x8>, <0x4ae0C314 0x4>;
reg-names = "base-address", "int-address",
"efuse-address", "ldo-address";
ti,tranxdone-status-mask = <0x8000000>;
/* LDOVBBMM_MUX_CTRL */
ti,ldovbb-override-mask = <0x400>;
/* LDOVBBMM_VSET_OUT */
ti,ldovbb-vset-mask = <0x1F>;
clocks = <&sysclk>;
ti,settling-time = <50>;
ti,clock-cycles = <16>;
ti,abb_info = <
/* uV ABB efuse rbb_m fbb_m vset_m */
975000 0 0 0 0 0 /* Bypass */
1200000 0 0x4 0 0x40000 0x1f00 /* FBB optional, vset */
>;
};
drivers/regulator/Kconfig
View file @
59aedb6d
...
...
@@ -481,6 +481,16 @@ config REGULATOR_TWL4030
This driver supports the voltage regulators provided by
this family of companion chips.
config REGULATOR_TI_ABB
bool "TI Adaptive Body Bias on-chip LDO"
depends on ARCH_OMAP
help
Select this option to support Texas Instruments' on-chip Adaptive Body
Bias (ABB) LDO regulators. It is recommended that this option be
enabled on required TI SoC. Certain Operating Performance Points
on TI SoCs may be unstable without enabling this as it provides
device specific optimized bias to allow/optimize functionality.
config REGULATOR_VEXPRESS
tristate "Versatile Express regulators"
depends on VEXPRESS_CONFIG
...
...
drivers/regulator/Makefile
View file @
59aedb6d
...
...
@@ -64,6 +64,7 @@ obj-$(CONFIG_REGULATOR_TPS65910) += tps65910-regulator.o
obj-$(CONFIG_REGULATOR_TPS65912)
+=
tps65912-regulator.o
obj-$(CONFIG_REGULATOR_TPS80031)
+=
tps80031-regulator.o
obj-$(CONFIG_REGULATOR_TWL4030)
+=
twl-regulator.o
obj-$(CONFIG_REGULATOR_TI_ABB)
+=
ti-abb-regulator.o
obj-$(CONFIG_REGULATOR_VEXPRESS)
+=
vexpress.o
obj-$(CONFIG_REGULATOR_WM831X)
+=
wm831x-dcdc.o
obj-$(CONFIG_REGULATOR_WM831X)
+=
wm831x-isink.o
...
...
drivers/regulator/ti-abb-regulator.c
0 → 100644
View file @
59aedb6d
/*
* Texas Instruments SoC Adaptive Body Bias(ABB) Regulator
*
* Copyright (C) 2011 Texas Instruments, Inc.
* Mike Turquette <mturquette@ti.com>
*
* Copyright (C) 2012-2013 Texas Instruments, Inc.
* Andrii Tseglytskyi <andrii.tseglytskyi@ti.com>
* Nishanth Menon <nm@ti.com>
*
* 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 "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/regulator/of_regulator.h>
/*
* ABB LDO operating states:
* NOMINAL_OPP: bypasses the ABB LDO
* FAST_OPP: sets ABB LDO to Forward Body-Bias
* SLOW_OPP: sets ABB LDO to Reverse Body-Bias
*/
#define TI_ABB_NOMINAL_OPP 0
#define TI_ABB_FAST_OPP 1
#define TI_ABB_SLOW_OPP 3
/**
* struct ti_abb_info - ABB information per voltage setting
* @opp_sel: one of TI_ABB macro
* @vset: (optional) vset value that LDOVBB needs to be overriden with.
*
* Array of per voltage entries organized in the same order as regulator_desc's
* volt_table list. (selector is used to index from this array)
*/
struct
ti_abb_info
{
u32
opp_sel
;
u32
vset
;
};
/**
* struct ti_abb_reg - Register description for ABB block
* @setup_reg: setup register offset from base
* @control_reg: control register offset from base
* @sr2_wtcnt_value_mask: setup register- sr2_wtcnt_value mask
* @fbb_sel_mask: setup register- FBB sel mask
* @rbb_sel_mask: setup register- RBB sel mask
* @sr2_en_mask: setup register- enable mask
* @opp_change_mask: control register - mask to trigger LDOVBB change
* @opp_sel_mask: control register - mask for mode to operate
*/
struct
ti_abb_reg
{
u32
setup_reg
;
u32
control_reg
;
/* Setup register fields */
u32
sr2_wtcnt_value_mask
;
u32
fbb_sel_mask
;
u32
rbb_sel_mask
;
u32
sr2_en_mask
;
/* Control register fields */
u32
opp_change_mask
;
u32
opp_sel_mask
;
};
/**
* struct ti_abb - ABB instance data
* @rdesc: regulator descriptor
* @clk: clock(usually sysclk) supplying ABB block
* @base: base address of ABB block
* @int_base: interrupt register base address
* @efuse_base: (optional) efuse base address for ABB modes
* @ldo_base: (optional) LDOVBB vset override base address
* @regs: pointer to struct ti_abb_reg for ABB block
* @txdone_mask: mask on int_base for tranxdone interrupt
* @ldovbb_override_mask: mask to ldo_base for overriding default LDO VBB
* vset with value from efuse
* @ldovbb_vset_mask: mask to ldo_base for providing the VSET override
* @info: array to per voltage ABB configuration
* @current_info_idx: current index to info
* @settling_time: SoC specific settling time for LDO VBB
*/
struct
ti_abb
{
struct
regulator_desc
rdesc
;
struct
clk
*
clk
;
void
__iomem
*
base
;
void
__iomem
*
int_base
;
void
__iomem
*
efuse_base
;
void
__iomem
*
ldo_base
;
const
struct
ti_abb_reg
*
regs
;
u32
txdone_mask
;
u32
ldovbb_override_mask
;
u32
ldovbb_vset_mask
;
struct
ti_abb_info
*
info
;
int
current_info_idx
;
u32
settling_time
;
};
/**
* ti_abb_rmw() - handy wrapper to set specific register bits
* @mask: mask for register field
* @value: value shifted to mask location and written
* @offset: offset of register
* @base: base address
*
* Return: final register value (may be unused)
*/
static
inline
u32
ti_abb_rmw
(
u32
mask
,
u32
value
,
u32
offset
,
void
__iomem
*
base
)
{
u32
val
;
val
=
readl
(
base
+
offset
);
val
&=
~
mask
;
val
|=
(
value
<<
__ffs
(
mask
))
&
mask
;
writel
(
val
,
base
+
offset
);
return
val
;
}
/**
* ti_abb_check_txdone() - handy wrapper to check ABB tranxdone status
* @abb: pointer to the abb instance
*
* Return: true or false
*/
static
inline
bool
ti_abb_check_txdone
(
const
struct
ti_abb
*
abb
)
{
return
!!
(
readl
(
abb
->
int_base
)
&
abb
->
txdone_mask
);
}
/**
* ti_abb_clear_txdone() - handy wrapper to clear ABB tranxdone status
* @abb: pointer to the abb instance
*/
static
inline
void
ti_abb_clear_txdone
(
const
struct
ti_abb
*
abb
)
{
writel
(
abb
->
txdone_mask
,
abb
->
int_base
);
};
/**
* ti_abb_wait_tranx() - waits for ABB tranxdone event
* @dev: device
* @abb: pointer to the abb instance
*
* Return: 0 on success or -ETIMEDOUT if the event is not cleared on time.
*/
static
int
ti_abb_wait_txdone
(
struct
device
*
dev
,
struct
ti_abb
*
abb
)
{
int
timeout
=
0
;
bool
status
;
while
(
timeout
++
<=
abb
->
settling_time
)
{
status
=
ti_abb_check_txdone
(
abb
);
if
(
status
)
break
;
udelay
(
1
);
}
if
(
timeout
>
abb
->
settling_time
)
{
dev_warn_ratelimited
(
dev
,
"%s:TRANXDONE timeout(%duS) int=0x%08x
\n
"
,
__func__
,
timeout
,
readl
(
abb
->
int_base
));
return
-
ETIMEDOUT
;
}
return
0
;
}
/**
* ti_abb_clear_all_txdone() - clears ABB tranxdone event
* @dev: device
* @abb: pointer to the abb instance
*
* Return: 0 on success or -ETIMEDOUT if the event is not cleared on time.
*/
static
int
ti_abb_clear_all_txdone
(
struct
device
*
dev
,
const
struct
ti_abb
*
abb
)
{
int
timeout
=
0
;
bool
status
;
while
(
timeout
++
<=
abb
->
settling_time
)
{
ti_abb_clear_txdone
(
abb
);
status
=
ti_abb_check_txdone
(
abb
);
if
(
!
status
)
break
;
udelay
(
1
);
}
if
(
timeout
>
abb
->
settling_time
)
{
dev_warn_ratelimited
(
dev
,
"%s:TRANXDONE timeout(%duS) int=0x%08x
\n
"
,
__func__
,
timeout
,
readl
(
abb
->
int_base
));
return
-
ETIMEDOUT
;
}
return
0
;
}
/**
* ti_abb_program_ldovbb() - program LDOVBB register for override value
* @dev: device
* @abb: pointer to the abb instance
* @info: ABB info to program
*/
static
void
ti_abb_program_ldovbb
(
struct
device
*
dev
,
const
struct
ti_abb
*
abb
,
struct
ti_abb_info
*
info
)
{
u32
val
;
val
=
readl
(
abb
->
ldo_base
);
/* clear up previous values */
val
&=
~
(
abb
->
ldovbb_override_mask
|
abb
->
ldovbb_vset_mask
);
switch
(
info
->
opp_sel
)
{
case
TI_ABB_SLOW_OPP
:
case
TI_ABB_FAST_OPP
:
val
|=
abb
->
ldovbb_override_mask
;
val
|=
info
->
vset
<<
__ffs
(
abb
->
ldovbb_vset_mask
);
break
;
}
writel
(
val
,
abb
->
ldo_base
);
}
/**
* ti_abb_set_opp() - Setup ABB and LDO VBB for required bias
* @rdev: regulator device
* @abb: pointer to the abb instance
* @info: ABB info to program
*
* Return: 0 on success or appropriate error value when fails
*/
static
int
ti_abb_set_opp
(
struct
regulator_dev
*
rdev
,
struct
ti_abb
*
abb
,
struct
ti_abb_info
*
info
)
{
const
struct
ti_abb_reg
*
regs
=
abb
->
regs
;
struct
device
*
dev
=
&
rdev
->
dev
;
int
ret
;
ret
=
ti_abb_clear_all_txdone
(
dev
,
abb
);
if
(
ret
)
goto
out
;
ti_abb_rmw
(
regs
->
fbb_sel_mask
|
regs
->
rbb_sel_mask
,
0
,
regs
->
setup_reg
,
abb
->
base
);
switch
(
info
->
opp_sel
)
{
case
TI_ABB_SLOW_OPP
:
ti_abb_rmw
(
regs
->
rbb_sel_mask
,
1
,
regs
->
setup_reg
,
abb
->
base
);
break
;
case
TI_ABB_FAST_OPP
:
ti_abb_rmw
(
regs
->
fbb_sel_mask
,
1
,
regs
->
setup_reg
,
abb
->
base
);
break
;
}
/* program next state of ABB ldo */
ti_abb_rmw
(
regs
->
opp_sel_mask
,
info
->
opp_sel
,
regs
->
control_reg
,
abb
->
base
);
/* program LDO VBB vset override if needed */
if
(
abb
->
ldo_base
)
ti_abb_program_ldovbb
(
dev
,
abb
,
info
);
/* Initiate ABB ldo change */
ti_abb_rmw
(
regs
->
opp_change_mask
,
1
,
regs
->
control_reg
,
abb
->
base
);
/* Wait for ABB LDO to complete transition to new Bias setting */
ret
=
ti_abb_wait_txdone
(
dev
,
abb
);
if
(
ret
)
goto
out
;
ret
=
ti_abb_clear_all_txdone
(
dev
,
abb
);
if
(
ret
)
goto
out
;
out:
return
ret
;
}
/**
* ti_abb_set_voltage_sel() - regulator accessor function to set ABB LDO
* @rdev: regulator device
* @sel: selector to index into required ABB LDO settings (maps to
* regulator descriptor's volt_table)
*
* Return: 0 on success or appropriate error value when fails
*/
static
int
ti_abb_set_voltage_sel
(
struct
regulator_dev
*
rdev
,
unsigned
sel
)
{
const
struct
regulator_desc
*
desc
=
rdev
->
desc
;
struct
ti_abb
*
abb
=
rdev_get_drvdata
(
rdev
);
struct
device
*
dev
=
&
rdev
->
dev
;
struct
ti_abb_info
*
info
,
*
oinfo
;
int
ret
=
0
;
if
(
!
abb
)
{
dev_err_ratelimited
(
dev
,
"%s: No regulator drvdata
\n
"
,
__func__
);
return
-
ENODEV
;
}
if
(
!
desc
->
n_voltages
||
!
abb
->
info
)
{
dev_err_ratelimited
(
dev
,
"%s: No valid voltage table entries?
\n
"
,
__func__
);
return
-
EINVAL
;
}
if
(
sel
>=
desc
->
n_voltages
)
{
dev_err
(
dev
,
"%s: sel idx(%d) >= n_voltages(%d)
\n
"
,
__func__
,
sel
,
desc
->
n_voltages
);
return
-
EINVAL
;
}
/* If we are in the same index as we were, nothing to do here! */
if
(
sel
==
abb
->
current_info_idx
)
{
dev_dbg
(
dev
,
"%s: Already at sel=%d
\n
"
,
__func__
,
sel
);
return
ret
;
}
/* If data is exactly the same, then just update index, no change */
info
=
&
abb
->
info
[
sel
];
oinfo
=
&
abb
->
info
[
abb
->
current_info_idx
];
if
(
!
memcmp
(
info
,
oinfo
,
sizeof
(
*
info
)))
{
dev_dbg
(
dev
,
"%s: Same data new idx=%d, old idx=%d
\n
"
,
__func__
,
sel
,
abb
->
current_info_idx
);
goto
out
;
}
ret
=
ti_abb_set_opp
(
rdev
,
abb
,
info
);
out:
if
(
!
ret
)
abb
->
current_info_idx
=
sel
;
else
dev_err_ratelimited
(
dev
,
"%s: Volt[%d] idx[%d] mode[%d] Fail(%d)
\n
"
,
__func__
,
desc
->
volt_table
[
sel
],
sel
,
info
->
opp_sel
,
ret
);
return
ret
;
}
/**
* ti_abb_get_voltage_sel() - Regulator accessor to get current ABB LDO setting
* @rdev: regulator device
*
* Return: 0 on success or appropriate error value when fails
*/
static
int
ti_abb_get_voltage_sel
(
struct
regulator_dev
*
rdev
)
{
const
struct
regulator_desc
*
desc
=
rdev
->
desc
;
struct
ti_abb
*
abb
=
rdev_get_drvdata
(
rdev
);
struct
device
*
dev
=
&
rdev
->
dev
;
if
(
!
abb
)
{
dev_err_ratelimited
(
dev
,
"%s: No regulator drvdata
\n
"
,
__func__
);
return
-
ENODEV
;
}
if
(
!
desc
->
n_voltages
||
!
abb
->
info
)
{
dev_err_ratelimited
(
dev
,
"%s: No valid voltage table entries?
\n
"
,
__func__
);
return
-
EINVAL
;
}
if
(
abb
->
current_info_idx
>=
(
int
)
desc
->
n_voltages
)
{
dev_err
(
dev
,
"%s: Corrupted data? idx(%d) >= n_voltages(%d)
\n
"
,
__func__
,
abb
->
current_info_idx
,
desc
->
n_voltages
);
return
-
EINVAL
;
}
return
abb
->
current_info_idx
;
}
/**
* ti_abb_init_timings() - setup ABB clock timing for the current platform
* @dev: device
* @abb: pointer to the abb instance
*
* Return: 0 if timing is updated, else returns error result.
*/
static
int
ti_abb_init_timings
(
struct
device
*
dev
,
struct
ti_abb
*
abb
)
{
u32
clock_cycles
;
u32
clk_rate
,
sr2_wt_cnt_val
,
cycle_rate
;
const
struct
ti_abb_reg
*
regs
=
abb
->
regs
;
int
ret
;
char
*
pname
=
"ti,settling-time"
;
/* read device tree properties */
ret
=
of_property_read_u32
(
dev
->
of_node
,
pname
,
&
abb
->
settling_time
);
if
(
ret
)
{
dev_err
(
dev
,
"Unable to get property '%s'(%d)
\n
"
,
pname
,
ret
);
return
ret
;
}
/* ABB LDO cannot be settle in 0 time */
if
(
!
abb
->
settling_time
)
{
dev_err
(
dev
,
"Invalid property:'%s' set as 0!
\n
"
,
pname
);
return
-
EINVAL
;
}
pname
=
"ti,clock-cycles"
;
ret
=
of_property_read_u32
(
dev
->
of_node
,
pname
,
&
clock_cycles
);
if
(
ret
)
{
dev_err
(
dev
,
"Unable to get property '%s'(%d)
\n
"
,
pname
,
ret
);
return
ret
;
}
/* ABB LDO cannot be settle in 0 clock cycles */
if
(
!
clock_cycles
)
{
dev_err
(
dev
,
"Invalid property:'%s' set as 0!
\n
"
,
pname
);
return
-
EINVAL
;
}
abb
->
clk
=
devm_clk_get
(
dev
,
NULL
);
if
(
IS_ERR
(
abb
->
clk
))
{
ret
=
PTR_ERR
(
abb
->
clk
);
dev_err
(
dev
,
"%s: Unable to get clk(%d)
\n
"
,
__func__
,
ret
);
return
ret
;
}
/*
* SR2_WTCNT_VALUE is the settling time for the ABB ldo after a
* transition and must be programmed with the correct time at boot.
* The value programmed into the register is the number of SYS_CLK
* clock cycles that match a given wall time profiled for the ldo.
* This value depends on:
* settling time of ldo in micro-seconds (varies per OMAP family)
* # of clock cycles per SYS_CLK period (varies per OMAP family)
* the SYS_CLK frequency in MHz (varies per board)
* The formula is:
*
* ldo settling time (in micro-seconds)
* SR2_WTCNT_VALUE = ------------------------------------------
* (# system clock cycles) * (sys_clk period)
*
* Put another way:
*
* SR2_WTCNT_VALUE = settling time / (# SYS_CLK cycles / SYS_CLK rate))
*
* To avoid dividing by zero multiply both "# clock cycles" and
* "settling time" by 10 such that the final result is the one we want.
*/
/* Convert SYS_CLK rate to MHz & prevent divide by zero */
clk_rate
=
DIV_ROUND_CLOSEST
(
clk_get_rate
(
abb
->
clk
),
1000000
);
/* Calculate cycle rate */
cycle_rate
=
DIV_ROUND_CLOSEST
(
clock_cycles
*
10
,
clk_rate
);
/* Calulate SR2_WTCNT_VALUE */
sr2_wt_cnt_val
=
DIV_ROUND_CLOSEST
(
abb
->
settling_time
*
10
,
cycle_rate
);
dev_dbg
(
dev
,
"%s: Clk_rate=%ld, sr2_cnt=0x%08x
\n
"
,
__func__
,
clk_get_rate
(
abb
->
clk
),
sr2_wt_cnt_val
);
ti_abb_rmw
(
regs
->
sr2_wtcnt_value_mask
,
sr2_wt_cnt_val
,
regs
->
setup_reg
,
abb
->
base
);
return
0
;
}
/**
* ti_abb_init_table() - Initialize ABB table from device tree
* @dev: device
* @abb: pointer to the abb instance
* @rinit_data: regulator initdata
*
* Return: 0 on success or appropriate error value when fails
*/
static
int
ti_abb_init_table
(
struct
device
*
dev
,
struct
ti_abb
*
abb
,
struct
regulator_init_data
*
rinit_data
)
{
struct
ti_abb_info
*
info
;
const
struct
property
*
prop
;
const
__be32
*
abb_info
;
const
u32
num_values
=
6
;
char
*
pname
=
"ti,abb_info"
;
u32
num_entries
,
i
;
unsigned
int
*
volt_table
;
int
min_uV
=
INT_MAX
,
max_uV
=
0
;
struct
regulation_constraints
*
c
=
&
rinit_data
->
constraints
;
prop
=
of_find_property
(
dev
->
of_node
,
pname
,
NULL
);
if
(
!
prop
)
{
dev_err
(
dev
,
"No '%s' property?
\n
"
,
pname
);
return
-
ENODEV
;
}
if
(
!
prop
->
value
)
{
dev_err
(
dev
,
"Empty '%s' property?
\n
"
,
pname
);
return
-
ENODATA
;
}
/*
* Each abb_info is a set of n-tuple, where n is num_values, consisting
* of voltage and a set of detection logic for ABB information for that
* voltage to apply.
*/
num_entries
=
prop
->
length
/
sizeof
(
u32
);
if
(
!
num_entries
||
(
num_entries
%
num_values
))
{
dev_err
(
dev
,
"All '%s' list entries need %d vals
\n
"
,
pname
,
num_values
);
return
-
EINVAL
;
}
num_entries
/=
num_values
;
info
=
devm_kzalloc
(
dev
,
sizeof
(
*
info
)
*
num_entries
,
GFP_KERNEL
);
if
(
!
info
)
{
dev_err
(
dev
,
"Can't allocate info table for '%s' property
\n
"
,
pname
);
return
-
ENOMEM
;
}
abb
->
info
=
info
;
volt_table
=
devm_kzalloc
(
dev
,
sizeof
(
unsigned
int
)
*
num_entries
,
GFP_KERNEL
);
if
(
!
volt_table
)
{
dev_err
(
dev
,
"Can't allocate voltage table for '%s' property
\n
"
,
pname
);
return
-
ENOMEM
;
}
abb
->
rdesc
.
n_voltages
=
num_entries
;
abb
->
rdesc
.
volt_table
=
volt_table
;
/* We do not know where the OPP voltage is at the moment */
abb
->
current_info_idx
=
-
EINVAL
;
abb_info
=
prop
->
value
;
for
(
i
=
0
;
i
<
num_entries
;
i
++
,
info
++
,
volt_table
++
)
{
u32
efuse_offset
,
rbb_mask
,
fbb_mask
,
vset_mask
;
u32
efuse_val
;
/* NOTE: num_values should equal to entries picked up here */
*
volt_table
=
be32_to_cpup
(
abb_info
++
);
info
->
opp_sel
=
be32_to_cpup
(
abb_info
++
);
efuse_offset
=
be32_to_cpup
(
abb_info
++
);
rbb_mask
=
be32_to_cpup
(
abb_info
++
);
fbb_mask
=
be32_to_cpup
(
abb_info
++
);
vset_mask
=
be32_to_cpup
(
abb_info
++
);
dev_dbg
(
dev
,
"[%d]v=%d ABB=%d ef=0x%x rbb=0x%x fbb=0x%x vset=0x%x
\n
"
,
i
,
*
volt_table
,
info
->
opp_sel
,
efuse_offset
,
rbb_mask
,
fbb_mask
,
vset_mask
);
/* Find min/max for voltage set */
if
(
min_uV
>
*
volt_table
)
min_uV
=
*
volt_table
;
if
(
max_uV
<
*
volt_table
)
max_uV
=
*
volt_table
;
if
(
!
abb
->
efuse_base
)
{
/* Ignore invalid data, but warn to help cleanup */
if
(
efuse_offset
||
rbb_mask
||
fbb_mask
||
vset_mask
)
dev_err
(
dev
,
"prop '%s': v=%d,bad efuse/mask
\n
"
,
pname
,
*
volt_table
);
goto
check_abb
;
}
efuse_val
=
readl
(
abb
->
efuse_base
+
efuse_offset
);
/* Use ABB recommendation from Efuse */
if
(
efuse_val
&
rbb_mask
)
info
->
opp_sel
=
TI_ABB_SLOW_OPP
;
else
if
(
efuse_val
&
fbb_mask
)
info
->
opp_sel
=
TI_ABB_FAST_OPP
;
else
if
(
rbb_mask
||
fbb_mask
)
info
->
opp_sel
=
TI_ABB_NOMINAL_OPP
;
dev_dbg
(
dev
,
"[%d]v=%d efusev=0x%x final ABB=%d
\n
"
,
i
,
*
volt_table
,
efuse_val
,
info
->
opp_sel
);
/* Use recommended Vset bits from Efuse */
if
(
!
abb
->
ldo_base
)
{
if
(
vset_mask
)
dev_err
(
dev
,
"prop'%s':v=%d vst=%x LDO base?
\n
"
,
pname
,
*
volt_table
,
vset_mask
);
continue
;
}
info
->
vset
=
efuse_val
&
vset_mask
>>
__ffs
(
vset_mask
);
dev_dbg
(
dev
,
"[%d]v=%d vset=%x
\n
"
,
i
,
*
volt_table
,
info
->
vset
);
check_abb:
switch
(
info
->
opp_sel
)
{
case
TI_ABB_NOMINAL_OPP
:
case
TI_ABB_FAST_OPP
:
case
TI_ABB_SLOW_OPP
:
/* Valid values */
break
;
default:
dev_err
(
dev
,
"%s:[%d]v=%d, ABB=%d is invalid! Abort!
\n
"
,
__func__
,
i
,
*
volt_table
,
info
->
opp_sel
);
return
-
EINVAL
;
}
}
/* Setup the min/max voltage constraints from the supported list */
c
->
min_uV
=
min_uV
;
c
->
max_uV
=
max_uV
;
return
0
;
}
static
struct
regulator_ops
ti_abb_reg_ops
=
{
.
list_voltage
=
regulator_list_voltage_table
,
.
set_voltage_sel
=
ti_abb_set_voltage_sel
,
.
get_voltage_sel
=
ti_abb_get_voltage_sel
,
};
/* Default ABB block offsets, IF this changes in future, create new one */
static
const
struct
ti_abb_reg
abb_regs_v1
=
{
/* WARNING: registers are wrongly documented in TRM */
.
setup_reg
=
0x04
,
.
control_reg
=
0x00
,
.
sr2_wtcnt_value_mask
=
(
0xff
<<
8
),
.
fbb_sel_mask
=
(
0x01
<<
2
),
.
rbb_sel_mask
=
(
0x01
<<
1
),
.
sr2_en_mask
=
(
0x01
<<
0
),
.
opp_change_mask
=
(
0x01
<<
2
),
.
opp_sel_mask
=
(
0x03
<<
0
),
};
static
const
struct
ti_abb_reg
abb_regs_v2
=
{
.
setup_reg
=
0x00
,
.
control_reg
=
0x04
,
.
sr2_wtcnt_value_mask
=
(
0xff
<<
8
),
.
fbb_sel_mask
=
(
0x01
<<
2
),
.
rbb_sel_mask
=
(
0x01
<<
1
),
.
sr2_en_mask
=
(
0x01
<<
0
),
.
opp_change_mask
=
(
0x01
<<
2
),
.
opp_sel_mask
=
(
0x03
<<
0
),
};
static
const
struct
of_device_id
ti_abb_of_match
[]
=
{
{.
compatible
=
"ti,abb-v1"
,
.
data
=
&
abb_regs_v1
},
{.
compatible
=
"ti,abb-v2"
,
.
data
=
&
abb_regs_v2
},
{
},
};
MODULE_DEVICE_TABLE
(
of
,
ti_abb_of_match
);
/**
* ti_abb_probe() - Initialize an ABB ldo instance
* @pdev: ABB platform device
*
* Initializes an individual ABB LDO for required Body-Bias. ABB is used to
* addional bias supply to SoC modules for power savings or mandatory stability
* configuration at certain Operating Performance Points(OPPs).
*
* Return: 0 on success or appropriate error value when fails
*/
static
int
ti_abb_probe
(
struct
platform_device
*
pdev
)
{
struct
device
*
dev
=
&
pdev
->
dev
;
const
struct
of_device_id
*
match
;
struct
resource
*
res
;
struct
ti_abb
*
abb
;
struct
regulator_init_data
*
initdata
=
NULL
;
struct
regulator_dev
*
rdev
=
NULL
;
struct
regulator_desc
*
desc
;
struct
regulation_constraints
*
c
;
struct
regulator_config
config
=
{
};
char
*
pname
;
int
ret
=
0
;
match
=
of_match_device
(
ti_abb_of_match
,
dev
);
if
(
!
match
)
{
/* We do not expect this to happen */
ret
=
-
ENODEV
;
dev_err
(
dev
,
"%s: Unable to match device
\n
"
,
__func__
);
goto
err
;
}
if
(
!
match
->
data
)
{
ret
=
-
EINVAL
;
dev_err
(
dev
,
"%s: Bad data in match
\n
"
,
__func__
);
goto
err
;
}
abb
=
devm_kzalloc
(
dev
,
sizeof
(
struct
ti_abb
),
GFP_KERNEL
);
if
(
!
abb
)
{
dev_err
(
dev
,
"%s: Unable to allocate ABB struct
\n
"
,
__func__
);
ret
=
-
ENOMEM
;
goto
err
;
}
abb
->
regs
=
match
->
data
;
/* Map ABB resources */
pname
=
"base-address"
;
res
=
platform_get_resource_byname
(
pdev
,
IORESOURCE_MEM
,
pname
);
if
(
!
res
)
{
dev_err
(
dev
,
"Missing '%s' IO resource
\n
"
,
pname
);
ret
=
-
ENODEV
;
goto
err
;
}
abb
->
base
=
devm_ioremap_resource
(
dev
,
res
);
if
(
IS_ERR
(
abb
->
base
))
{
ret
=
PTR_ERR
(
abb
->
base
);
goto
err
;
}
pname
=
"int-address"
;
res
=
platform_get_resource_byname
(
pdev
,
IORESOURCE_MEM
,
pname
);
if
(
!
res
)
{
dev_err
(
dev
,
"Missing '%s' IO resource
\n
"
,
pname
);
ret
=
-
ENODEV
;
goto
err
;
}
/*
* We may have shared interrupt register offsets which are
* write-1-to-clear between domains ensuring exclusivity.
*/
abb
->
int_base
=
devm_ioremap_nocache
(
dev
,
res
->
start
,
resource_size
(
res
));
if
(
!
abb
->
int_base
)
{
dev_err
(
dev
,
"Unable to map '%s'
\n
"
,
pname
);
ret
=
-
ENOMEM
;
goto
err
;
}
/* Map Optional resources */
pname
=
"efuse-address"
;
res
=
platform_get_resource_byname
(
pdev
,
IORESOURCE_MEM
,
pname
);
if
(
!
res
)
{
dev_dbg
(
dev
,
"Missing '%s' IO resource
\n
"
,
pname
);
ret
=
-
ENODEV
;
goto
skip_opt
;
}
/*
* We may have shared efuse register offsets which are read-only
* between domains
*/
abb
->
efuse_base
=
devm_ioremap_nocache
(
dev
,
res
->
start
,
resource_size
(
res
));
if
(
!
abb
->
efuse_base
)
{
dev_err
(
dev
,
"Unable to map '%s'
\n
"
,
pname
);
ret
=
-
ENOMEM
;
goto
err
;
}
pname
=
"ldo-address"
;
res
=
platform_get_resource_byname
(
pdev
,
IORESOURCE_MEM
,
pname
);
if
(
!
res
)
{
dev_dbg
(
dev
,
"Missing '%s' IO resource
\n
"
,
pname
);
ret
=
-
ENODEV
;
goto
skip_opt
;
}
abb
->
ldo_base
=
devm_ioremap_resource
(
dev
,
res
);
if
(
IS_ERR
(
abb
->
ldo_base
))
{
ret
=
PTR_ERR
(
abb
->
ldo_base
);
goto
err
;
}
/* IF ldo_base is set, the following are mandatory */
pname
=
"ti,ldovbb-override-mask"
;
ret
=
of_property_read_u32
(
pdev
->
dev
.
of_node
,
pname
,
&
abb
->
ldovbb_override_mask
);
if
(
ret
)
{
dev_err
(
dev
,
"Missing '%s' (%d)
\n
"
,
pname
,
ret
);
goto
err
;
}
if
(
!
abb
->
ldovbb_override_mask
)
{
dev_err
(
dev
,
"Invalid property:'%s' set as 0!
\n
"
,
pname
);
ret
=
-
EINVAL
;
goto
err
;
}
pname
=
"ti,ldovbb-vset-mask"
;
ret
=
of_property_read_u32
(
pdev
->
dev
.
of_node
,
pname
,
&
abb
->
ldovbb_vset_mask
);
if
(
ret
)
{
dev_err
(
dev
,
"Missing '%s' (%d)
\n
"
,
pname
,
ret
);
goto
err
;
}
if
(
!
abb
->
ldovbb_vset_mask
)
{
dev_err
(
dev
,
"Invalid property:'%s' set as 0!
\n
"
,
pname
);
ret
=
-
EINVAL
;
goto
err
;
}
skip_opt:
pname
=
"ti,tranxdone-status-mask"
;
ret
=
of_property_read_u32
(
pdev
->
dev
.
of_node
,
pname
,
&
abb
->
txdone_mask
);
if
(
ret
)
{
dev_err
(
dev
,
"Missing '%s' (%d)
\n
"
,
pname
,
ret
);
goto
err
;
}
if
(
!
abb
->
txdone_mask
)
{
dev_err
(
dev
,
"Invalid property:'%s' set as 0!
\n
"
,
pname
);
ret
=
-
EINVAL
;
goto
err
;
}
initdata
=
of_get_regulator_init_data
(
dev
,
pdev
->
dev
.
of_node
);
if
(
!
initdata
)
{
ret
=
-
ENOMEM
;
dev_err
(
dev
,
"%s: Unable to alloc regulator init data
\n
"
,
__func__
);
goto
err
;
}
/* init ABB opp_sel table */
ret
=
ti_abb_init_table
(
dev
,
abb
,
initdata
);
if
(
ret
)
goto
err
;
/* init ABB timing */
ret
=
ti_abb_init_timings
(
dev
,
abb
);
if
(
ret
)
goto
err
;
desc
=
&
abb
->
rdesc
;
desc
->
name
=
dev_name
(
dev
);
desc
->
owner
=
THIS_MODULE
;
desc
->
type
=
REGULATOR_VOLTAGE
;
desc
->
ops
=
&
ti_abb_reg_ops
;
c
=
&
initdata
->
constraints
;
if
(
desc
->
n_voltages
>
1
)
c
->
valid_ops_mask
|=
REGULATOR_CHANGE_VOLTAGE
;
c
->
always_on
=
true
;
config
.
dev
=
dev
;
config
.
init_data
=
initdata
;
config
.
driver_data
=
abb
;
config
.
of_node
=
pdev
->
dev
.
of_node
;
rdev
=
regulator_register
(
desc
,
&
config
);
if
(
IS_ERR
(
rdev
))
{
ret
=
PTR_ERR
(
rdev
);
dev_err
(
dev
,
"%s: failed to register regulator(%d)
\n
"
,
__func__
,
ret
);
goto
err
;
}
platform_set_drvdata
(
pdev
,
rdev
);
/* Enable the ldo if not already done by bootloader */
ti_abb_rmw
(
abb
->
regs
->
sr2_en_mask
,
1
,
abb
->
regs
->
setup_reg
,
abb
->
base
);
return
0
;
err:
dev_err
(
dev
,
"%s: Failed to initialize(%d)
\n
"
,
__func__
,
ret
);
return
ret
;
}
/**
* ti_abb_remove() - cleanups
* @pdev: ABB platform device
*
* Return: 0
*/
static
int
ti_abb_remove
(
struct
platform_device
*
pdev
)
{
struct
regulator_dev
*
rdev
=
platform_get_drvdata
(
pdev
);
regulator_unregister
(
rdev
);
return
0
;
}
MODULE_ALIAS
(
"platform:ti_abb"
);
static
struct
platform_driver
ti_abb_driver
=
{
.
probe
=
ti_abb_probe
,
.
remove
=
ti_abb_remove
,
.
driver
=
{
.
name
=
"ti_abb"
,
.
owner
=
THIS_MODULE
,
.
of_match_table
=
of_match_ptr
(
ti_abb_of_match
),
},
};
module_platform_driver
(
ti_abb_driver
);
MODULE_DESCRIPTION
(
"Texas Instruments ABB LDO regulator driver"
);
MODULE_AUTHOR
(
"Texas Instruments Inc."
);
MODULE_LICENSE
(
"GPL v2"
);
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment