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
Kirill Smelkov
linux
Commits
f6d29536
Commit
f6d29536
authored
Nov 01, 2017
by
Wolfram Sang
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'i2c/sbs-manager' into i2c/for-4.15
parents
4ee045f4
4cf419a2
Changes
14
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
675 additions
and
148 deletions
+675
-148
Documentation/devicetree/bindings/i2c/i2c.txt
Documentation/devicetree/bindings/i2c/i2c.txt
+2
-2
Documentation/devicetree/bindings/power/supply/sbs,sbs-manager.txt
...tion/devicetree/bindings/power/supply/sbs,sbs-manager.txt
+66
-0
drivers/i2c/busses/i2c-parport-light.c
drivers/i2c/busses/i2c-parport-light.c
+0
-1
drivers/i2c/busses/i2c-parport.c
drivers/i2c/busses/i2c-parport.c
+0
-1
drivers/i2c/busses/i2c-thunderx-pcidrv.c
drivers/i2c/busses/i2c-thunderx-pcidrv.c
+0
-6
drivers/i2c/i2c-core-base.c
drivers/i2c/i2c-core-base.c
+9
-0
drivers/i2c/i2c-core-smbus.c
drivers/i2c/i2c-core-smbus.c
+55
-0
drivers/i2c/i2c-smbus.c
drivers/i2c/i2c-smbus.c
+25
-56
drivers/i2c/muxes/i2c-mux-pca954x.c
drivers/i2c/muxes/i2c-mux-pca954x.c
+32
-63
drivers/power/supply/Kconfig
drivers/power/supply/Kconfig
+14
-0
drivers/power/supply/Makefile
drivers/power/supply/Makefile
+1
-0
drivers/power/supply/sbs-battery.c
drivers/power/supply/sbs-battery.c
+17
-18
drivers/power/supply/sbs-manager.c
drivers/power/supply/sbs-manager.c
+445
-0
include/linux/i2c-smbus.h
include/linux/i2c-smbus.h
+9
-1
No files found.
Documentation/devicetree/bindings/i2c/i2c.txt
View file @
f6d29536
...
...
@@ -59,8 +59,8 @@ wants to support one of the below features, it should adapt the bindings below.
interrupts used by the device.
- interrupt-names
"irq"
and "wakeup" names are recognized by I2C core, other names are
left to individual drivers.
"irq"
, "wakeup" and "smbus_alert" names are recognized by I2C core,
other names are
left to individual drivers.
- host-notify
device uses SMBus host notify protocol instead of interrupt line.
...
...
Documentation/devicetree/bindings/power/supply/sbs,sbs-manager.txt
0 → 100644
View file @
f6d29536
Binding for sbs-manager
Required properties:
- compatible: "<vendor>,<part-number>", "sbs,sbs-charger" as fallback. The part
number compatible string might be used in order to take care of vendor
specific registers.
- reg: integer, i2c address of the device. Should be <0xa>.
Optional properties:
- gpio-controller: Marks the port as GPIO controller.
See "gpio-specifier" in .../devicetree/bindings/gpio/gpio.txt.
- #gpio-cells: Should be <2>. The first cell is the pin number, the second cell
is used to specify optional parameters:
See "gpio-specifier" in .../devicetree/bindings/gpio/gpio.txt.
From OS view the device is basically an i2c-mux used to communicate with up to
four smart battery devices at address 0xb. The driver actually implements this
behaviour. So standard i2c-mux nodes can be used to register up to four slave
batteries. Channels will be numerated starting from 1 to 4.
Example:
batman@a {
compatible = "lltc,ltc1760", "sbs,sbs-manager";
reg = <0x0a>;
#address-cells = <1>;
#size-cells = <0>;
gpio-controller;
#gpio-cells = <2>;
i2c@1 {
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
battery@b {
compatible = "ti,bq2060", "sbs,sbs-battery";
reg = <0x0b>;
sbs,battery-detect-gpios = <&batman 1 1>;
};
};
i2c@2 {
#address-cells = <1>;
#size-cells = <0>;
reg = <2>;
battery@b {
compatible = "ti,bq2060", "sbs,sbs-battery";
reg = <0x0b>;
sbs,battery-detect-gpios = <&batman 2 1>;
};
};
i2c@3 {
#address-cells = <1>;
#size-cells = <0>;
reg = <3>;
battery@b {
compatible = "ti,bq2060", "sbs,sbs-battery";
reg = <0x0b>;
sbs,battery-detect-gpios = <&batman 3 1>;
};
};
};
drivers/i2c/busses/i2c-parport-light.c
View file @
f6d29536
...
...
@@ -123,7 +123,6 @@ static struct i2c_adapter parport_adapter = {
/* SMBus alert support */
static
struct
i2c_smbus_alert_setup
alert_data
=
{
.
alert_edge_triggered
=
1
,
};
static
struct
i2c_client
*
ara
;
static
struct
lineop
parport_ctrl_irq
=
{
...
...
drivers/i2c/busses/i2c-parport.c
View file @
f6d29536
...
...
@@ -237,7 +237,6 @@ static void i2c_parport_attach(struct parport *port)
/* Setup SMBus alert if supported */
if
(
adapter_parm
[
type
].
smbus_alert
)
{
adapter
->
alert_data
.
alert_edge_triggered
=
1
;
adapter
->
ara
=
i2c_setup_smbus_alert
(
&
adapter
->
adapter
,
&
adapter
->
alert_data
);
if
(
adapter
->
ara
)
...
...
drivers/i2c/busses/i2c-thunderx-pcidrv.c
View file @
f6d29536
...
...
@@ -118,8 +118,6 @@ static void thunder_i2c_clock_disable(struct device *dev, struct clk *clk)
static
int
thunder_i2c_smbus_setup_of
(
struct
octeon_i2c
*
i2c
,
struct
device_node
*
node
)
{
u32
type
;
if
(
!
node
)
return
-
EINVAL
;
...
...
@@ -127,10 +125,6 @@ static int thunder_i2c_smbus_setup_of(struct octeon_i2c *i2c,
if
(
!
i2c
->
alert_data
.
irq
)
return
-
EINVAL
;
type
=
irqd_get_trigger_type
(
irq_get_irq_data
(
i2c
->
alert_data
.
irq
));
i2c
->
alert_data
.
alert_edge_triggered
=
(
type
&
IRQ_TYPE_LEVEL_MASK
)
?
1
:
0
;
i2c
->
ara
=
i2c_setup_smbus_alert
(
&
i2c
->
adap
,
&
i2c
->
alert_data
);
if
(
!
i2c
->
ara
)
return
-
ENODEV
;
...
...
drivers/i2c/i2c-core-base.c
View file @
f6d29536
...
...
@@ -29,6 +29,7 @@
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/i2c-smbus.h>
#include <linux/idr.h>
#include <linux/init.h>
#include <linux/irqflags.h>
...
...
@@ -1269,6 +1270,10 @@ static int i2c_register_adapter(struct i2c_adapter *adap)
goto
out_list
;
}
res
=
of_i2c_setup_smbus_alert
(
adap
);
if
(
res
)
goto
out_reg
;
dev_dbg
(
&
adap
->
dev
,
"adapter [%s] registered
\n
"
,
adap
->
name
);
pm_runtime_no_callbacks
(
&
adap
->
dev
);
...
...
@@ -1300,6 +1305,10 @@ static int i2c_register_adapter(struct i2c_adapter *adap)
return
0
;
out_reg:
init_completion
(
&
adap
->
dev_released
);
device_unregister
(
&
adap
->
dev
);
wait_for_completion
(
&
adap
->
dev_released
);
out_list:
mutex_lock
(
&
core_lock
);
idr_remove
(
&
i2c_adapter_idr
,
adap
->
nr
);
...
...
drivers/i2c/i2c-core-smbus.c
View file @
f6d29536
...
...
@@ -17,6 +17,7 @@
#include <linux/device.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/i2c-smbus.h>
#define CREATE_TRACE_POINTS
#include <trace/events/smbus.h>
...
...
@@ -592,3 +593,57 @@ s32 i2c_smbus_read_i2c_block_data_or_emulated(const struct i2c_client *client,
return
i
;
}
EXPORT_SYMBOL
(
i2c_smbus_read_i2c_block_data_or_emulated
);
/**
* i2c_setup_smbus_alert - Setup SMBus alert support
* @adapter: the target adapter
* @setup: setup data for the SMBus alert handler
* Context: can sleep
*
* Setup handling of the SMBus alert protocol on a given I2C bus segment.
*
* Handling can be done either through our IRQ handler, or by the
* adapter (from its handler, periodic polling, or whatever).
*
* NOTE that if we manage the IRQ, we *MUST* know if it's level or
* edge triggered in order to hand it to the workqueue correctly.
* If triggering the alert seems to wedge the system, you probably
* should have said it's level triggered.
*
* This returns the ara client, which should be saved for later use with
* i2c_handle_smbus_alert() and ultimately i2c_unregister_device(); or NULL
* to indicate an error.
*/
struct
i2c_client
*
i2c_setup_smbus_alert
(
struct
i2c_adapter
*
adapter
,
struct
i2c_smbus_alert_setup
*
setup
)
{
struct
i2c_board_info
ara_board_info
=
{
I2C_BOARD_INFO
(
"smbus_alert"
,
0x0c
),
.
platform_data
=
setup
,
};
return
i2c_new_device
(
adapter
,
&
ara_board_info
);
}
EXPORT_SYMBOL_GPL
(
i2c_setup_smbus_alert
);
#if IS_ENABLED(CONFIG_I2C_SMBUS) && IS_ENABLED(CONFIG_OF)
int
of_i2c_setup_smbus_alert
(
struct
i2c_adapter
*
adapter
)
{
struct
i2c_client
*
client
;
int
irq
;
irq
=
of_property_match_string
(
adapter
->
dev
.
of_node
,
"interrupt-names"
,
"smbus_alert"
);
if
(
irq
==
-
EINVAL
||
irq
==
-
ENODATA
)
return
0
;
else
if
(
irq
<
0
)
return
irq
;
client
=
i2c_setup_smbus_alert
(
adapter
,
NULL
);
if
(
!
client
)
return
-
ENODEV
;
return
0
;
}
EXPORT_SYMBOL_GPL
(
of_i2c_setup_smbus_alert
);
#endif
drivers/i2c/i2c-smbus.c
View file @
f6d29536
...
...
@@ -21,12 +21,11 @@
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
struct
i2c_smbus_alert
{
unsigned
int
alert_edge_triggered
:
1
;
int
irq
;
struct
work_struct
alert
;
struct
i2c_client
*
ara
;
/* Alert response address */
};
...
...
@@ -72,13 +71,12 @@ static int smbus_do_alert(struct device *dev, void *addrp)
* The alert IRQ handler needs to hand work off to a task which can issue
* SMBus calls, because those sleeping calls can't be made in IRQ context.
*/
static
void
smbus_alert
(
struct
work_struct
*
work
)
static
irqreturn_t
smbus_alert
(
int
irq
,
void
*
d
)
{
struct
i2c_smbus_alert
*
alert
;
struct
i2c_smbus_alert
*
alert
=
d
;
struct
i2c_client
*
ara
;
unsigned
short
prev_addr
=
0
;
/* Not a valid address */
alert
=
container_of
(
work
,
struct
i2c_smbus_alert
,
alert
);
ara
=
alert
->
ara
;
for
(;;)
{
...
...
@@ -115,21 +113,17 @@ static void smbus_alert(struct work_struct *work)
prev_addr
=
data
.
addr
;
}
/* We handled all alerts; re-enable level-triggered IRQs */
if
(
!
alert
->
alert_edge_triggered
)
enable_irq
(
alert
->
irq
);
return
IRQ_HANDLED
;
}
static
irqreturn_t
smbalert_irq
(
int
irq
,
void
*
d
)
static
void
smbalert_work
(
struct
work_struct
*
work
)
{
struct
i2c_smbus_alert
*
alert
=
d
;
struct
i2c_smbus_alert
*
alert
;
/* Disable level-triggered IRQs until we handle them */
if
(
!
alert
->
alert_edge_triggered
)
disable_irq_nosync
(
irq
);
alert
=
container_of
(
work
,
struct
i2c_smbus_alert
,
alert
);
smbus_alert
(
0
,
alert
);
schedule_work
(
&
alert
->
alert
);
return
IRQ_HANDLED
;
}
/* Setup SMBALERT# infrastructure */
...
...
@@ -139,28 +133,35 @@ static int smbalert_probe(struct i2c_client *ara,
struct
i2c_smbus_alert_setup
*
setup
=
dev_get_platdata
(
&
ara
->
dev
);
struct
i2c_smbus_alert
*
alert
;
struct
i2c_adapter
*
adapter
=
ara
->
adapter
;
int
res
;
int
res
,
irq
;
alert
=
devm_kzalloc
(
&
ara
->
dev
,
sizeof
(
struct
i2c_smbus_alert
),
GFP_KERNEL
);
if
(
!
alert
)
return
-
ENOMEM
;
alert
->
alert_edge_triggered
=
setup
->
alert_edge_triggered
;
alert
->
irq
=
setup
->
irq
;
INIT_WORK
(
&
alert
->
alert
,
smbus_alert
);
if
(
setup
)
{
irq
=
setup
->
irq
;
}
else
{
irq
=
of_irq_get_byname
(
adapter
->
dev
.
of_node
,
"smbus_alert"
);
if
(
irq
<=
0
)
return
irq
;
}
INIT_WORK
(
&
alert
->
alert
,
smbalert_work
);
alert
->
ara
=
ara
;
if
(
setup
->
irq
>
0
)
{
res
=
devm_request_irq
(
&
ara
->
dev
,
setup
->
irq
,
smbalert_irq
,
0
,
"smbus_alert"
,
alert
);
if
(
irq
>
0
)
{
res
=
devm_request_threaded_irq
(
&
ara
->
dev
,
irq
,
NULL
,
smbus_alert
,
IRQF_SHARED
|
IRQF_ONESHOT
,
"smbus_alert"
,
alert
);
if
(
res
)
return
res
;
}
i2c_set_clientdata
(
ara
,
alert
);
dev_info
(
&
adapter
->
dev
,
"supports SMBALERT#, %s trigger
\n
"
,
setup
->
alert_edge_triggered
?
"edge"
:
"level"
);
dev_info
(
&
adapter
->
dev
,
"supports SMBALERT#
\n
"
);
return
0
;
}
...
...
@@ -189,38 +190,6 @@ static struct i2c_driver smbalert_driver = {
.
id_table
=
smbalert_ids
,
};
/**
* i2c_setup_smbus_alert - Setup SMBus alert support
* @adapter: the target adapter
* @setup: setup data for the SMBus alert handler
* Context: can sleep
*
* Setup handling of the SMBus alert protocol on a given I2C bus segment.
*
* Handling can be done either through our IRQ handler, or by the
* adapter (from its handler, periodic polling, or whatever).
*
* NOTE that if we manage the IRQ, we *MUST* know if it's level or
* edge triggered in order to hand it to the workqueue correctly.
* If triggering the alert seems to wedge the system, you probably
* should have said it's level triggered.
*
* This returns the ara client, which should be saved for later use with
* i2c_handle_smbus_alert() and ultimately i2c_unregister_device(); or NULL
* to indicate an error.
*/
struct
i2c_client
*
i2c_setup_smbus_alert
(
struct
i2c_adapter
*
adapter
,
struct
i2c_smbus_alert_setup
*
setup
)
{
struct
i2c_board_info
ara_board_info
=
{
I2C_BOARD_INFO
(
"smbus_alert"
,
0x0c
),
.
platform_data
=
setup
,
};
return
i2c_new_device
(
adapter
,
&
ara_board_info
);
}
EXPORT_SYMBOL_GPL
(
i2c_setup_smbus_alert
);
/**
* i2c_handle_smbus_alert - Handle an SMBus alert
* @ara: the ARA client on the relevant adapter
...
...
drivers/i2c/muxes/i2c-mux-pca954x.c
View file @
f6d29536
...
...
@@ -246,36 +246,6 @@ static irqreturn_t pca954x_irq_handler(int irq, void *dev_id)
return
handled
?
IRQ_HANDLED
:
IRQ_NONE
;
}
static
void
pca954x_irq_mask
(
struct
irq_data
*
idata
)
{
struct
pca954x
*
data
=
irq_data_get_irq_chip_data
(
idata
);
unsigned
int
pos
=
idata
->
hwirq
;
unsigned
long
flags
;
raw_spin_lock_irqsave
(
&
data
->
lock
,
flags
);
data
->
irq_mask
&=
~
BIT
(
pos
);
if
(
!
data
->
irq_mask
)
disable_irq
(
data
->
client
->
irq
);
raw_spin_unlock_irqrestore
(
&
data
->
lock
,
flags
);
}
static
void
pca954x_irq_unmask
(
struct
irq_data
*
idata
)
{
struct
pca954x
*
data
=
irq_data_get_irq_chip_data
(
idata
);
unsigned
int
pos
=
idata
->
hwirq
;
unsigned
long
flags
;
raw_spin_lock_irqsave
(
&
data
->
lock
,
flags
);
if
(
!
data
->
irq_mask
)
enable_irq
(
data
->
client
->
irq
);
data
->
irq_mask
|=
BIT
(
pos
);
raw_spin_unlock_irqrestore
(
&
data
->
lock
,
flags
);
}
static
int
pca954x_irq_set_type
(
struct
irq_data
*
idata
,
unsigned
int
type
)
{
if
((
type
&
IRQ_TYPE_SENSE_MASK
)
!=
IRQ_TYPE_LEVEL_LOW
)
...
...
@@ -285,8 +255,6 @@ static int pca954x_irq_set_type(struct irq_data *idata, unsigned int type)
static
struct
irq_chip
pca954x_irq_chip
=
{
.
name
=
"i2c-mux-pca954x"
,
.
irq_mask
=
pca954x_irq_mask
,
.
irq_unmask
=
pca954x_irq_unmask
,
.
irq_set_type
=
pca954x_irq_set_type
,
};
...
...
@@ -294,7 +262,7 @@ static int pca954x_irq_setup(struct i2c_mux_core *muxc)
{
struct
pca954x
*
data
=
i2c_mux_priv
(
muxc
);
struct
i2c_client
*
client
=
data
->
client
;
int
c
,
err
,
irq
;
int
c
,
irq
;
if
(
!
data
->
chip
->
has_irq
||
client
->
irq
<=
0
)
return
0
;
...
...
@@ -309,29 +277,31 @@ static int pca954x_irq_setup(struct i2c_mux_core *muxc)
for
(
c
=
0
;
c
<
data
->
chip
->
nchans
;
c
++
)
{
irq
=
irq_create_mapping
(
data
->
irq
,
c
);
if
(
!
irq
)
{
dev_err
(
&
client
->
dev
,
"failed irq create map
\n
"
);
return
-
EINVAL
;
}
irq_set_chip_data
(
irq
,
data
);
irq_set_chip_and_handler
(
irq
,
&
pca954x_irq_chip
,
handle_simple_irq
);
}
err
=
devm_request_threaded_irq
(
&
client
->
dev
,
data
->
client
->
irq
,
NULL
,
pca954x_irq_handler
,
IRQF_ONESHOT
|
IRQF_SHARED
,
"pca954x"
,
data
);
if
(
err
)
goto
err_req_irq
;
return
0
;
}
disable_irq
(
data
->
client
->
irq
);
static
void
pca954x_cleanup
(
struct
i2c_mux_core
*
muxc
)
{
struct
pca954x
*
data
=
i2c_mux_priv
(
muxc
);
int
c
,
irq
;
return
0
;
err_req_irq:
if
(
data
->
irq
)
{
for
(
c
=
0
;
c
<
data
->
chip
->
nchans
;
c
++
)
{
irq
=
irq_find_mapping
(
data
->
irq
,
c
);
irq_dispose_mapping
(
irq
);
}
irq_domain_remove
(
data
->
irq
);
return
err
;
}
i2c_mux_del_adapters
(
muxc
)
;
}
/*
...
...
@@ -391,7 +361,7 @@ static int pca954x_probe(struct i2c_client *client,
ret
=
pca954x_irq_setup
(
muxc
);
if
(
ret
)
goto
fail_
del_adapters
;
goto
fail_
cleanup
;
/* Now create an adapter for each channel */
for
(
num
=
0
;
num
<
data
->
chip
->
nchans
;
num
++
)
{
...
...
@@ -414,7 +384,16 @@ static int pca954x_probe(struct i2c_client *client,
ret
=
i2c_mux_add_adapter
(
muxc
,
force
,
num
,
class
);
if
(
ret
)
goto
fail_del_adapters
;
goto
fail_cleanup
;
}
if
(
data
->
irq
)
{
ret
=
devm_request_threaded_irq
(
&
client
->
dev
,
data
->
client
->
irq
,
NULL
,
pca954x_irq_handler
,
IRQF_ONESHOT
|
IRQF_SHARED
,
"pca954x"
,
data
);
if
(
ret
)
goto
fail_cleanup
;
}
dev_info
(
&
client
->
dev
,
...
...
@@ -424,26 +403,16 @@ static int pca954x_probe(struct i2c_client *client,
return
0
;
fail_
del_adapters
:
i2c_mux_del_adapters
(
muxc
);
fail_
cleanup
:
pca954x_cleanup
(
muxc
);
return
ret
;
}
static
int
pca954x_remove
(
struct
i2c_client
*
client
)
{
struct
i2c_mux_core
*
muxc
=
i2c_get_clientdata
(
client
);
struct
pca954x
*
data
=
i2c_mux_priv
(
muxc
);
int
c
,
irq
;
if
(
data
->
irq
)
{
for
(
c
=
0
;
c
<
data
->
chip
->
nchans
;
c
++
)
{
irq
=
irq_find_mapping
(
data
->
irq
,
c
);
irq_dispose_mapping
(
irq
);
}
irq_domain_remove
(
data
->
irq
);
}
i2c_mux_del_adapters
(
muxc
);
pca954x_cleanup
(
muxc
);
return
0
;
}
...
...
drivers/power/supply/Kconfig
View file @
f6d29536
...
...
@@ -184,6 +184,20 @@ config CHARGER_SBS
help
Say Y to include support for SBS compilant battery chargers.
config MANAGER_SBS
tristate "Smart Battery System Manager"
depends on I2C && I2C_MUX && GPIOLIB
select I2C_SMBUS
help
Say Y here to include support for Smart Battery System Manager
ICs. The driver reports online and charging status via sysfs.
It presents itself also as I2C mux which allows to bind
smart battery driver to its ports.
Supported is for example LTC1760.
This driver can also be built as a module. If so, the module will be
called sbs-manager.
config BATTERY_BQ27XXX
tristate "BQ27xxx battery driver"
help
...
...
drivers/power/supply/Makefile
View file @
f6d29536
...
...
@@ -36,6 +36,7 @@ obj-$(CONFIG_BATTERY_IPAQ_MICRO) += ipaq_micro_battery.o
obj-$(CONFIG_BATTERY_WM97XX)
+=
wm97xx_battery.o
obj-$(CONFIG_BATTERY_SBS)
+=
sbs-battery.o
obj-$(CONFIG_CHARGER_SBS)
+=
sbs-charger.o
obj-$(CONFIG_MANAGER_SBS)
+=
sbs-manager.o
obj-$(CONFIG_BATTERY_BQ27XXX)
+=
bq27xxx_battery.o
obj-$(CONFIG_BATTERY_BQ27XXX_I2C)
+=
bq27xxx_battery_i2c.o
obj-$(CONFIG_BATTERY_BQ27XXX_HDQ)
+=
bq27xxx_battery_hdq.o
...
...
drivers/power/supply/sbs-battery.c
View file @
f6d29536
...
...
@@ -177,10 +177,8 @@ static bool force_load;
static
int
sbs_read_word_data
(
struct
i2c_client
*
client
,
u8
address
)
{
struct
sbs_info
*
chip
=
i2c_get_clientdata
(
client
);
int
retries
=
chip
->
i2c_retry_count
;
s32
ret
=
0
;
int
retries
=
1
;
retries
=
chip
->
i2c_retry_count
;
while
(
retries
>
0
)
{
ret
=
i2c_smbus_read_word_data
(
client
,
address
);
...
...
@@ -204,7 +202,7 @@ static int sbs_read_string_data(struct i2c_client *client, u8 address,
{
struct
sbs_info
*
chip
=
i2c_get_clientdata
(
client
);
s32
ret
=
0
,
block_length
=
0
;
int
retries_length
=
1
,
retries_block
=
1
;
int
retries_length
,
retries_block
;
u8
block_buffer
[
I2C_SMBUS_BLOCK_MAX
+
1
];
retries_length
=
chip
->
i2c_retry_count
;
...
...
@@ -269,10 +267,8 @@ static int sbs_write_word_data(struct i2c_client *client, u8 address,
u16
value
)
{
struct
sbs_info
*
chip
=
i2c_get_clientdata
(
client
);
int
retries
=
chip
->
i2c_retry_count
;
s32
ret
=
0
;
int
retries
=
1
;
retries
=
chip
->
i2c_retry_count
;
while
(
retries
>
0
)
{
ret
=
i2c_smbus_write_word_data
(
client
,
address
,
value
);
...
...
@@ -321,16 +317,6 @@ static int sbs_get_battery_presence_and_health(
union
power_supply_propval
*
val
)
{
s32
ret
;
struct
sbs_info
*
chip
=
i2c_get_clientdata
(
client
);
if
(
psp
==
POWER_SUPPLY_PROP_PRESENT
&&
chip
->
gpio_detect
)
{
ret
=
gpiod_get_value_cansleep
(
chip
->
gpio_detect
);
if
(
ret
<
0
)
return
ret
;
val
->
intval
=
ret
;
chip
->
is_present
=
val
->
intval
;
return
ret
;
}
/*
* Write to ManufacturerAccess with ManufacturerAccess command
...
...
@@ -570,7 +556,7 @@ static int sbs_get_battery_serial_number(struct i2c_client *client,
if
(
ret
<
0
)
return
ret
;
ret
=
sprintf
(
sbs_serial
,
"%04x"
,
ret
);
sprintf
(
sbs_serial
,
"%04x"
,
ret
);
val
->
strval
=
sbs_serial
;
return
0
;
...
...
@@ -598,6 +584,19 @@ static int sbs_get_property(struct power_supply *psy,
struct
sbs_info
*
chip
=
power_supply_get_drvdata
(
psy
);
struct
i2c_client
*
client
=
chip
->
client
;
if
(
chip
->
gpio_detect
)
{
ret
=
gpiod_get_value_cansleep
(
chip
->
gpio_detect
);
if
(
ret
<
0
)
return
ret
;
if
(
psp
==
POWER_SUPPLY_PROP_PRESENT
)
{
val
->
intval
=
ret
;
chip
->
is_present
=
val
->
intval
;
return
0
;
}
if
(
ret
==
0
)
return
-
ENODATA
;
}
switch
(
psp
)
{
case
POWER_SUPPLY_PROP_PRESENT
:
case
POWER_SUPPLY_PROP_HEALTH
:
...
...
drivers/power/supply/sbs-manager.c
0 → 100644
View file @
f6d29536
/*
* Driver for SBS compliant Smart Battery System Managers
*
* The device communicates via i2c at address 0x0a and multiplexes access to up
* to four smart batteries at address 0x0b.
*
* Via sysfs interface the online state and charge type are presented.
*
* Datasheet SBSM: http://sbs-forum.org/specs/sbsm100b.pdf
* Datasheet LTC1760: http://cds.linear.com/docs/en/datasheet/1760fb.pdf
*
* Karl-Heinz Schneider <karl-heinz@schneider-inet.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/gpio.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/i2c-mux.h>
#include <linux/power_supply.h>
#include <linux/property.h>
#define SBSM_MAX_BATS 4
#define SBSM_RETRY_CNT 3
/* registers addresses */
#define SBSM_CMD_BATSYSSTATE 0x01
#define SBSM_CMD_BATSYSSTATECONT 0x02
#define SBSM_CMD_BATSYSINFO 0x04
#define SBSM_CMD_LTC 0x3c
#define SBSM_MASK_BAT_SUPPORTED GENMASK(3, 0)
#define SBSM_MASK_CHARGE_BAT GENMASK(7, 4)
#define SBSM_BIT_AC_PRESENT BIT(0)
#define SBSM_BIT_TURBO BIT(7)
#define SBSM_SMB_BAT_OFFSET 11
struct
sbsm_data
{
struct
i2c_client
*
client
;
struct
i2c_mux_core
*
muxc
;
struct
power_supply
*
psy
;
u8
cur_chan
;
/* currently selected channel */
struct
gpio_chip
chip
;
bool
is_ltc1760
;
/* special capabilities */
unsigned
int
supported_bats
;
unsigned
int
last_state
;
unsigned
int
last_state_cont
;
};
static
enum
power_supply_property
sbsm_props
[]
=
{
POWER_SUPPLY_PROP_ONLINE
,
POWER_SUPPLY_PROP_CHARGE_TYPE
,
};
static
int
sbsm_read_word
(
struct
i2c_client
*
client
,
u8
address
)
{
int
reg
,
retries
;
for
(
retries
=
SBSM_RETRY_CNT
;
retries
>
0
;
retries
--
)
{
reg
=
i2c_smbus_read_word_data
(
client
,
address
);
if
(
reg
>=
0
)
break
;
}
if
(
reg
<
0
)
{
dev_err
(
&
client
->
dev
,
"failed to read register 0x%02x
\n
"
,
address
);
}
return
reg
;
}
static
int
sbsm_write_word
(
struct
i2c_client
*
client
,
u8
address
,
u16
word
)
{
int
ret
,
retries
;
for
(
retries
=
SBSM_RETRY_CNT
;
retries
>
0
;
retries
--
)
{
ret
=
i2c_smbus_write_word_data
(
client
,
address
,
word
);
if
(
ret
>=
0
)
break
;
}
if
(
ret
<
0
)
dev_err
(
&
client
->
dev
,
"failed to write to register 0x%02x
\n
"
,
address
);
return
ret
;
}
static
int
sbsm_get_property
(
struct
power_supply
*
psy
,
enum
power_supply_property
psp
,
union
power_supply_propval
*
val
)
{
struct
sbsm_data
*
data
=
power_supply_get_drvdata
(
psy
);
int
regval
=
0
;
switch
(
psp
)
{
case
POWER_SUPPLY_PROP_ONLINE
:
regval
=
sbsm_read_word
(
data
->
client
,
SBSM_CMD_BATSYSSTATECONT
);
if
(
regval
<
0
)
return
regval
;
val
->
intval
=
!!
(
regval
&
SBSM_BIT_AC_PRESENT
);
break
;
case
POWER_SUPPLY_PROP_CHARGE_TYPE
:
regval
=
sbsm_read_word
(
data
->
client
,
SBSM_CMD_BATSYSSTATE
);
if
(
regval
<
0
)
return
regval
;
if
((
regval
&
SBSM_MASK_CHARGE_BAT
)
==
0
)
{
val
->
intval
=
POWER_SUPPLY_CHARGE_TYPE_NONE
;
return
0
;
}
val
->
intval
=
POWER_SUPPLY_CHARGE_TYPE_TRICKLE
;
if
(
data
->
is_ltc1760
)
{
/* charge mode fast if turbo is active */
regval
=
sbsm_read_word
(
data
->
client
,
SBSM_CMD_LTC
);
if
(
regval
<
0
)
return
regval
;
else
if
(
regval
&
SBSM_BIT_TURBO
)
val
->
intval
=
POWER_SUPPLY_CHARGE_TYPE_FAST
;
}
break
;
default:
return
-
EINVAL
;
}
return
0
;
}
static
int
sbsm_prop_is_writeable
(
struct
power_supply
*
psy
,
enum
power_supply_property
psp
)
{
struct
sbsm_data
*
data
=
power_supply_get_drvdata
(
psy
);
return
(
psp
==
POWER_SUPPLY_PROP_CHARGE_TYPE
)
&&
data
->
is_ltc1760
;
}
static
int
sbsm_set_property
(
struct
power_supply
*
psy
,
enum
power_supply_property
psp
,
const
union
power_supply_propval
*
val
)
{
struct
sbsm_data
*
data
=
power_supply_get_drvdata
(
psy
);
int
ret
=
-
EINVAL
;
u16
regval
;
switch
(
psp
)
{
case
POWER_SUPPLY_PROP_CHARGE_TYPE
:
/* write 1 to TURBO if type fast is given */
if
(
!
data
->
is_ltc1760
)
break
;
regval
=
val
->
intval
==
POWER_SUPPLY_CHARGE_TYPE_FAST
?
SBSM_BIT_TURBO
:
0
;
ret
=
sbsm_write_word
(
data
->
client
,
SBSM_CMD_LTC
,
regval
);
break
;
default:
break
;
}
return
ret
;
}
/*
* Switch to battery
* Parameter chan is directly the content of SMB_BAT* nibble
*/
static
int
sbsm_select
(
struct
i2c_mux_core
*
muxc
,
u32
chan
)
{
struct
sbsm_data
*
data
=
i2c_mux_priv
(
muxc
);
struct
device
*
dev
=
&
data
->
client
->
dev
;
int
ret
=
0
;
u16
reg
;
if
(
data
->
cur_chan
==
chan
)
return
ret
;
/* chan goes from 1 ... 4 */
reg
=
1
<<
BIT
(
SBSM_SMB_BAT_OFFSET
+
chan
);
ret
=
sbsm_write_word
(
data
->
client
,
SBSM_CMD_BATSYSSTATE
,
reg
);
if
(
ret
)
dev_err
(
dev
,
"Failed to select channel %i
\n
"
,
chan
);
else
data
->
cur_chan
=
chan
;
return
ret
;
}
static
int
sbsm_gpio_get_value
(
struct
gpio_chip
*
gc
,
unsigned
int
off
)
{
struct
sbsm_data
*
data
=
gpiochip_get_data
(
gc
);
int
ret
;
ret
=
sbsm_read_word
(
data
->
client
,
SBSM_CMD_BATSYSSTATE
);
if
(
ret
<
0
)
return
ret
;
return
ret
&
BIT
(
off
);
}
/*
* This needs to be defined or the GPIO lib fails to register the pin.
* But the 'gpio' is always an input.
*/
static
int
sbsm_gpio_direction_input
(
struct
gpio_chip
*
gc
,
unsigned
int
off
)
{
return
0
;
}
static
int
sbsm_do_alert
(
struct
device
*
dev
,
void
*
d
)
{
struct
i2c_client
*
client
=
i2c_verify_client
(
dev
);
struct
i2c_driver
*
driver
;
if
(
!
client
||
client
->
addr
!=
0x0b
)
return
0
;
device_lock
(
dev
);
if
(
client
->
dev
.
driver
)
{
driver
=
to_i2c_driver
(
client
->
dev
.
driver
);
if
(
driver
->
alert
)
driver
->
alert
(
client
,
I2C_PROTOCOL_SMBUS_ALERT
,
0
);
else
dev_warn
(
&
client
->
dev
,
"no driver alert()!
\n
"
);
}
else
{
dev_dbg
(
&
client
->
dev
,
"alert with no driver
\n
"
);
}
device_unlock
(
dev
);
return
-
EBUSY
;
}
static
void
sbsm_alert
(
struct
i2c_client
*
client
,
enum
i2c_alert_protocol
prot
,
unsigned
int
d
)
{
struct
sbsm_data
*
sbsm
=
i2c_get_clientdata
(
client
);
int
ret
,
i
,
irq_bat
=
0
,
state
=
0
;
ret
=
sbsm_read_word
(
sbsm
->
client
,
SBSM_CMD_BATSYSSTATE
);
if
(
ret
>=
0
)
{
irq_bat
=
ret
^
sbsm
->
last_state
;
sbsm
->
last_state
=
ret
;
state
=
ret
;
}
ret
=
sbsm_read_word
(
sbsm
->
client
,
SBSM_CMD_BATSYSSTATECONT
);
if
((
ret
>=
0
)
&&
((
ret
^
sbsm
->
last_state_cont
)
&
SBSM_BIT_AC_PRESENT
))
{
irq_bat
|=
sbsm
->
supported_bats
&
state
;
power_supply_changed
(
sbsm
->
psy
);
}
sbsm
->
last_state_cont
=
ret
;
for
(
i
=
0
;
i
<
SBSM_MAX_BATS
;
i
++
)
{
if
(
irq_bat
&
BIT
(
i
))
{
device_for_each_child
(
&
sbsm
->
muxc
->
adapter
[
i
]
->
dev
,
NULL
,
sbsm_do_alert
);
}
}
}
static
int
sbsm_gpio_setup
(
struct
sbsm_data
*
data
)
{
struct
gpio_chip
*
gc
=
&
data
->
chip
;
struct
i2c_client
*
client
=
data
->
client
;
struct
device
*
dev
=
&
client
->
dev
;
int
ret
;
if
(
!
device_property_present
(
dev
,
"gpio-controller"
))
return
0
;
ret
=
sbsm_read_word
(
client
,
SBSM_CMD_BATSYSSTATE
);
if
(
ret
<
0
)
return
ret
;
data
->
last_state
=
ret
;
ret
=
sbsm_read_word
(
client
,
SBSM_CMD_BATSYSSTATECONT
);
if
(
ret
<
0
)
return
ret
;
data
->
last_state_cont
=
ret
;
gc
->
get
=
sbsm_gpio_get_value
;
gc
->
direction_input
=
sbsm_gpio_direction_input
;
gc
->
can_sleep
=
true
;
gc
->
base
=
-
1
;
gc
->
ngpio
=
SBSM_MAX_BATS
;
gc
->
label
=
client
->
name
;
gc
->
parent
=
dev
;
gc
->
owner
=
THIS_MODULE
;
ret
=
devm_gpiochip_add_data
(
dev
,
gc
,
data
);
if
(
ret
)
{
dev_err
(
dev
,
"devm_gpiochip_add_data failed: %d
\n
"
,
ret
);
return
ret
;
}
return
ret
;
}
static
const
struct
power_supply_desc
sbsm_default_psy_desc
=
{
.
type
=
POWER_SUPPLY_TYPE_MAINS
,
.
properties
=
sbsm_props
,
.
num_properties
=
ARRAY_SIZE
(
sbsm_props
),
.
get_property
=
&
sbsm_get_property
,
.
set_property
=
&
sbsm_set_property
,
.
property_is_writeable
=
&
sbsm_prop_is_writeable
,
};
static
int
sbsm_probe
(
struct
i2c_client
*
client
,
const
struct
i2c_device_id
*
id
)
{
struct
i2c_adapter
*
adapter
=
to_i2c_adapter
(
client
->
dev
.
parent
);
struct
sbsm_data
*
data
;
struct
device
*
dev
=
&
client
->
dev
;
struct
power_supply_desc
*
psy_desc
;
struct
power_supply_config
psy_cfg
=
{};
int
ret
=
0
,
i
;
/* Device listens only at address 0x0a */
if
(
client
->
addr
!=
0x0a
)
return
-
EINVAL
;
if
(
!
i2c_check_functionality
(
adapter
,
I2C_FUNC_SMBUS_WORD_DATA
))
return
-
EPFNOSUPPORT
;
data
=
devm_kzalloc
(
dev
,
sizeof
(
*
data
),
GFP_KERNEL
);
if
(
!
data
)
return
-
ENOMEM
;
i2c_set_clientdata
(
client
,
data
);
data
->
client
=
client
;
data
->
is_ltc1760
=
!!
strstr
(
id
->
name
,
"ltc1760"
);
ret
=
sbsm_read_word
(
client
,
SBSM_CMD_BATSYSINFO
);
if
(
ret
<
0
)
return
ret
;
data
->
supported_bats
=
ret
&
SBSM_MASK_BAT_SUPPORTED
;
data
->
muxc
=
i2c_mux_alloc
(
adapter
,
dev
,
SBSM_MAX_BATS
,
0
,
I2C_MUX_LOCKED
,
&
sbsm_select
,
NULL
);
if
(
!
data
->
muxc
)
{
dev_err
(
dev
,
"failed to alloc i2c mux
\n
"
);
ret
=
-
ENOMEM
;
goto
err_mux_alloc
;
}
data
->
muxc
->
priv
=
data
;
/* register muxed i2c channels. One for each supported battery */
for
(
i
=
0
;
i
<
SBSM_MAX_BATS
;
++
i
)
{
if
(
data
->
supported_bats
&
BIT
(
i
))
{
ret
=
i2c_mux_add_adapter
(
data
->
muxc
,
0
,
i
+
1
,
0
);
if
(
ret
)
break
;
}
}
if
(
ret
)
{
dev_err
(
dev
,
"failed to register i2c mux channel %d
\n
"
,
i
+
1
);
goto
err_mux_register
;
}
psy_desc
=
devm_kmemdup
(
dev
,
&
sbsm_default_psy_desc
,
sizeof
(
struct
power_supply_desc
),
GFP_KERNEL
);
if
(
!
psy_desc
)
{
ret
=
-
ENOMEM
;
goto
err_psy
;
}
psy_desc
->
name
=
devm_kasprintf
(
dev
,
GFP_KERNEL
,
"sbsm-%s"
,
dev_name
(
&
client
->
dev
));
if
(
!
psy_desc
->
name
)
{
ret
=
-
ENOMEM
;
goto
err_psy
;
}
ret
=
sbsm_gpio_setup
(
data
);
if
(
ret
<
0
)
goto
err_psy
;
psy_cfg
.
drv_data
=
data
;
psy_cfg
.
of_node
=
dev
->
of_node
;
data
->
psy
=
devm_power_supply_register
(
dev
,
psy_desc
,
&
psy_cfg
);
if
(
IS_ERR
(
data
->
psy
))
{
ret
=
PTR_ERR
(
data
->
psy
);
dev_err
(
dev
,
"failed to register power supply %s
\n
"
,
psy_desc
->
name
);
goto
err_psy
;
}
return
0
;
err_psy:
err_mux_register:
i2c_mux_del_adapters
(
data
->
muxc
);
err_mux_alloc:
return
ret
;
}
static
int
sbsm_remove
(
struct
i2c_client
*
client
)
{
struct
sbsm_data
*
data
=
i2c_get_clientdata
(
client
);
i2c_mux_del_adapters
(
data
->
muxc
);
return
0
;
}
static
const
struct
i2c_device_id
sbsm_ids
[]
=
{
{
"sbs-manager"
,
0
},
{
"ltc1760"
,
0
},
{
}
};
MODULE_DEVICE_TABLE
(
i2c
,
sbsm_ids
);
#ifdef CONFIG_OF
static
const
struct
of_device_id
sbsm_dt_ids
[]
=
{
{
.
compatible
=
"sbs,sbs-manager"
},
{
.
compatible
=
"lltc,ltc1760"
},
{
}
};
MODULE_DEVICE_TABLE
(
of
,
sbsm_dt_ids
);
#endif
static
struct
i2c_driver
sbsm_driver
=
{
.
driver
=
{
.
name
=
"sbsm"
,
.
of_match_table
=
of_match_ptr
(
sbsm_dt_ids
),
},
.
probe
=
sbsm_probe
,
.
remove
=
sbsm_remove
,
.
alert
=
sbsm_alert
,
.
id_table
=
sbsm_ids
};
module_i2c_driver
(
sbsm_driver
);
MODULE_LICENSE
(
"GPL"
);
MODULE_AUTHOR
(
"Karl-Heinz Schneider <karl-heinz@schneider-inet.de>"
);
MODULE_DESCRIPTION
(
"SBSM Smart Battery System Manager"
);
include/linux/i2c-smbus.h
View file @
f6d29536
...
...
@@ -42,7 +42,6 @@
* properly set.
*/
struct
i2c_smbus_alert_setup
{
unsigned
int
alert_edge_triggered
:
1
;
int
irq
;
};
...
...
@@ -50,4 +49,13 @@ struct i2c_client *i2c_setup_smbus_alert(struct i2c_adapter *adapter,
struct
i2c_smbus_alert_setup
*
setup
);
int
i2c_handle_smbus_alert
(
struct
i2c_client
*
ara
);
#if IS_ENABLED(CONFIG_I2C_SMBUS) && IS_ENABLED(CONFIG_OF)
int
of_i2c_setup_smbus_alert
(
struct
i2c_adapter
*
adap
);
#else
static
inline
int
of_i2c_setup_smbus_alert
(
struct
i2c_adapter
*
adap
)
{
return
0
;
}
#endif
#endif
/* _LINUX_I2C_SMBUS_H */
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