Immutable branch between MFD, Regulator and Clk, due for v3.19
-----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJUdKaZAAoJEFGvii+H/HdhPj8P/1Jl60LnYPozG0eu6+rNaLDj AL8bNLWh0pANQzpYstE4aHDmrEWTArJ37eIet6BrPN/nv/ej2DR7hgK7Eh8h4P1t 2LtBpxCT3f/Md92iBTLa/bMFm4VuuT/GiOurb1vR9BtTyH1bD8ftmXwhmzHskBuT wK0lfdvLEMRI2YIkgnLcuto07kMAIbLAQ4TANWvnHL2XLGd0/aPn68q3jNgwPjgf ufi5/eV8y+M/oopYPjU4Mt4W5fOTCZ6yINsp3tkOhPk9G28wJSmAiDkm9bn7KVrz Jwy60tpO9Z2+kff62uMjcWvMEvnMcrfQJ3cGm+Si64DuM+s2hmWpA/rXeO+Ep7VX jk3aiYXkFmUfgFXz7qMhFEERXu4nwhSGgdbEZWRmG8rcSaS3KnBNDaSvZOOH2mCq 8iQmMzSMDK6CnW2wIr+2AsrrizPEPrvMOs7GLiUiyXlOjkUSR1XjJ6HlIfXKiMYg bWliacyGm3v+K3g41u9ULHNmLi779SlsUrH+Z6NARN5kRKXvsPntDA+8em11euvt 7h1/wyPpjvJi8Cj5VJzinh4l/9t9O2GNeDuKExuOnaRHoOhzGkFwwMBgUkEdb5lX 1il7JIXb/MfrwDbd4EZ/G0Ju2icFOfuwCAjad0upBfGkU11zJEa/KozN1ymjq/bD whTJLra7D6P0E8DBdS/0 =WTv4 -----END PGP SIGNATURE----- Merge branches 'ib-mfd-gpio-i2c-3.19', 'ib-mfd-iio-3.19' and 'ib-mfd-regulator-v3.19', tag 'ib-mfd-regulator-clk-v3.19' into ibs-for-mfd-merged Immutable branch between MFD, Regulator and Clk, due for v3.19
This commit is contained in:
commit
a9e2e4733c
|
@ -1,5 +1,5 @@
|
|||
|
||||
* Samsung S2MPS11, S2MPS14 and S2MPU02 Voltage and Current Regulator
|
||||
* Samsung S2MPS11, S2MPS13, S2MPS14 and S2MPU02 Voltage and Current Regulator
|
||||
|
||||
The Samsung S2MPS11 is a multi-function device which includes voltage and
|
||||
current regulators, RTC, charger controller and other sub-blocks. It is
|
||||
|
@ -7,8 +7,8 @@ interfaced to the host controller using an I2C interface. Each sub-block is
|
|||
addressed by the host system using different I2C slave addresses.
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "samsung,s2mps11-pmic" or "samsung,s2mps14-pmic"
|
||||
or "samsung,s2mpu02-pmic".
|
||||
- compatible: Should be "samsung,s2mps11-pmic" or "samsung,s2mps13-pmic"
|
||||
or "samsung,s2mps14-pmic" or "samsung,s2mpu02-pmic".
|
||||
- reg: Specifies the I2C slave address of the pmic block. It should be 0x66.
|
||||
|
||||
Optional properties:
|
||||
|
@ -17,8 +17,8 @@ Optional properties:
|
|||
- interrupts: Interrupt specifiers for interrupt sources.
|
||||
|
||||
Optional nodes:
|
||||
- clocks: s2mps11 and s5m8767 provide three(AP/CP/BT) buffered 32.768 KHz
|
||||
outputs, so to register these as clocks with common clock framework
|
||||
- clocks: s2mps11, s2mps13 and s5m8767 provide three(AP/CP/BT) buffered 32.768
|
||||
KHz outputs, so to register these as clocks with common clock framework
|
||||
instantiate a sub-node named "clocks". It uses the common clock binding
|
||||
documented in :
|
||||
[Documentation/devicetree/bindings/clock/clock-bindings.txt]
|
||||
|
@ -30,12 +30,12 @@ Optional nodes:
|
|||
the clock which they consume.
|
||||
Clock ID Devices
|
||||
----------------------------------------------------------
|
||||
32KhzAP 0 S2MPS11, S2MPS14, S5M8767
|
||||
32KhzCP 1 S2MPS11, S5M8767
|
||||
32KhzBT 2 S2MPS11, S2MPS14, S5M8767
|
||||
32KhzAP 0 S2MPS11, S2MPS13, S2MPS14, S5M8767
|
||||
32KhzCP 1 S2MPS11, S2MPS13, S5M8767
|
||||
32KhzBT 2 S2MPS11, S2MPS13, S2MPS14, S5M8767
|
||||
|
||||
- compatible: Should be one of: "samsung,s2mps11-clk", "samsung,s2mps14-clk",
|
||||
"samsung,s5m8767-clk"
|
||||
- compatible: Should be one of: "samsung,s2mps11-clk", "samsung,s2mps13-clk",
|
||||
"samsung,s2mps14-clk", "samsung,s5m8767-clk"
|
||||
|
||||
- regulators: The regulators of s2mps11 that have to be instantiated should be
|
||||
included in a sub-node named 'regulators'. Regulator nodes included in this
|
||||
|
@ -81,12 +81,14 @@ as per the datasheet of s2mps11.
|
|||
- LDOn
|
||||
- valid values for n are:
|
||||
- S2MPS11: 1 to 38
|
||||
- S2MPS13: 1 to 40
|
||||
- S2MPS14: 1 to 25
|
||||
- S2MPU02: 1 to 28
|
||||
- Example: LDO1, LDO2, LDO28
|
||||
- BUCKn
|
||||
- valid values for n are:
|
||||
- S2MPS11: 1 to 10
|
||||
- S2MPS13: 1 to 10
|
||||
- S2MPS14: 1 to 5
|
||||
- S2MPU02: 1 to 7
|
||||
- Example: BUCK1, BUCK2, BUCK9
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <linux/clk-provider.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mfd/samsung/s2mps11.h>
|
||||
#include <linux/mfd/samsung/s2mps13.h>
|
||||
#include <linux/mfd/samsung/s2mps14.h>
|
||||
#include <linux/mfd/samsung/s5m8767.h>
|
||||
#include <linux/mfd/samsung/core.h>
|
||||
|
@ -120,6 +121,24 @@ static struct clk_init_data s2mps11_clks_init[S2MPS11_CLKS_NUM] = {
|
|||
},
|
||||
};
|
||||
|
||||
static struct clk_init_data s2mps13_clks_init[S2MPS11_CLKS_NUM] = {
|
||||
[S2MPS11_CLK_AP] = {
|
||||
.name = "s2mps13_ap",
|
||||
.ops = &s2mps11_clk_ops,
|
||||
.flags = CLK_IS_ROOT,
|
||||
},
|
||||
[S2MPS11_CLK_CP] = {
|
||||
.name = "s2mps13_cp",
|
||||
.ops = &s2mps11_clk_ops,
|
||||
.flags = CLK_IS_ROOT,
|
||||
},
|
||||
[S2MPS11_CLK_BT] = {
|
||||
.name = "s2mps13_bt",
|
||||
.ops = &s2mps11_clk_ops,
|
||||
.flags = CLK_IS_ROOT,
|
||||
},
|
||||
};
|
||||
|
||||
static struct clk_init_data s2mps14_clks_init[S2MPS11_CLKS_NUM] = {
|
||||
[S2MPS11_CLK_AP] = {
|
||||
.name = "s2mps14_ap",
|
||||
|
@ -184,6 +203,10 @@ static int s2mps11_clk_probe(struct platform_device *pdev)
|
|||
s2mps11_reg = S2MPS11_REG_RTC_CTRL;
|
||||
clks_init = s2mps11_clks_init;
|
||||
break;
|
||||
case S2MPS13X:
|
||||
s2mps11_reg = S2MPS13_REG_RTCCTRL;
|
||||
clks_init = s2mps13_clks_init;
|
||||
break;
|
||||
case S2MPS14X:
|
||||
s2mps11_reg = S2MPS14_REG_RTCCTRL;
|
||||
clks_init = s2mps14_clks_init;
|
||||
|
@ -279,6 +302,7 @@ static int s2mps11_clk_remove(struct platform_device *pdev)
|
|||
|
||||
static const struct platform_device_id s2mps11_clk_id[] = {
|
||||
{ "s2mps11-clk", S2MPS11X},
|
||||
{ "s2mps13-clk", S2MPS13X},
|
||||
{ "s2mps14-clk", S2MPS14X},
|
||||
{ "s5m8767-clk", S5M8767X},
|
||||
{ },
|
||||
|
|
|
@ -905,4 +905,16 @@ config GPIO_VIPERBOARD
|
|||
River Tech's viperboard.h for detailed meaning
|
||||
of the module parameters.
|
||||
|
||||
config GPIO_DLN2
|
||||
tristate "Diolan DLN2 GPIO support"
|
||||
depends on MFD_DLN2
|
||||
select GPIOLIB_IRQCHIP
|
||||
|
||||
help
|
||||
Select this option to enable GPIO driver for the Diolan DLN2
|
||||
board.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called gpio-dln2.
|
||||
|
||||
endif
|
||||
|
|
|
@ -26,6 +26,7 @@ obj-$(CONFIG_GPIO_CRYSTAL_COVE) += gpio-crystalcove.o
|
|||
obj-$(CONFIG_GPIO_DA9052) += gpio-da9052.o
|
||||
obj-$(CONFIG_GPIO_DA9055) += gpio-da9055.o
|
||||
obj-$(CONFIG_GPIO_DAVINCI) += gpio-davinci.o
|
||||
obj-$(CONFIG_GPIO_DLN2) += gpio-dln2.o
|
||||
obj-$(CONFIG_GPIO_DWAPB) += gpio-dwapb.o
|
||||
obj-$(CONFIG_GPIO_EM) += gpio-em.o
|
||||
obj-$(CONFIG_GPIO_EP93XX) += gpio-ep93xx.o
|
||||
|
|
|
@ -0,0 +1,553 @@
|
|||
/*
|
||||
* Driver for the Diolan DLN-2 USB-GPIO adapter
|
||||
*
|
||||
* Copyright (c) 2014 Intel Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqchip/chained_irq.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mfd/dln2.h>
|
||||
|
||||
#define DLN2_GPIO_ID 0x01
|
||||
|
||||
#define DLN2_GPIO_GET_PIN_COUNT DLN2_CMD(0x01, DLN2_GPIO_ID)
|
||||
#define DLN2_GPIO_SET_DEBOUNCE DLN2_CMD(0x04, DLN2_GPIO_ID)
|
||||
#define DLN2_GPIO_GET_DEBOUNCE DLN2_CMD(0x05, DLN2_GPIO_ID)
|
||||
#define DLN2_GPIO_PORT_GET_VAL DLN2_CMD(0x06, DLN2_GPIO_ID)
|
||||
#define DLN2_GPIO_PIN_GET_VAL DLN2_CMD(0x0B, DLN2_GPIO_ID)
|
||||
#define DLN2_GPIO_PIN_SET_OUT_VAL DLN2_CMD(0x0C, DLN2_GPIO_ID)
|
||||
#define DLN2_GPIO_PIN_GET_OUT_VAL DLN2_CMD(0x0D, DLN2_GPIO_ID)
|
||||
#define DLN2_GPIO_CONDITION_MET_EV DLN2_CMD(0x0F, DLN2_GPIO_ID)
|
||||
#define DLN2_GPIO_PIN_ENABLE DLN2_CMD(0x10, DLN2_GPIO_ID)
|
||||
#define DLN2_GPIO_PIN_DISABLE DLN2_CMD(0x11, DLN2_GPIO_ID)
|
||||
#define DLN2_GPIO_PIN_SET_DIRECTION DLN2_CMD(0x13, DLN2_GPIO_ID)
|
||||
#define DLN2_GPIO_PIN_GET_DIRECTION DLN2_CMD(0x14, DLN2_GPIO_ID)
|
||||
#define DLN2_GPIO_PIN_SET_EVENT_CFG DLN2_CMD(0x1E, DLN2_GPIO_ID)
|
||||
#define DLN2_GPIO_PIN_GET_EVENT_CFG DLN2_CMD(0x1F, DLN2_GPIO_ID)
|
||||
|
||||
#define DLN2_GPIO_EVENT_NONE 0
|
||||
#define DLN2_GPIO_EVENT_CHANGE 1
|
||||
#define DLN2_GPIO_EVENT_LVL_HIGH 2
|
||||
#define DLN2_GPIO_EVENT_LVL_LOW 3
|
||||
#define DLN2_GPIO_EVENT_CHANGE_RISING 0x11
|
||||
#define DLN2_GPIO_EVENT_CHANGE_FALLING 0x21
|
||||
#define DLN2_GPIO_EVENT_MASK 0x0F
|
||||
|
||||
#define DLN2_GPIO_MAX_PINS 32
|
||||
|
||||
struct dln2_irq_work {
|
||||
struct work_struct work;
|
||||
struct dln2_gpio *dln2;
|
||||
int pin;
|
||||
int type;
|
||||
};
|
||||
|
||||
struct dln2_gpio {
|
||||
struct platform_device *pdev;
|
||||
struct gpio_chip gpio;
|
||||
|
||||
/*
|
||||
* Cache pin direction to save us one transfer, since the hardware has
|
||||
* separate commands to read the in and out values.
|
||||
*/
|
||||
DECLARE_BITMAP(output_enabled, DLN2_GPIO_MAX_PINS);
|
||||
|
||||
DECLARE_BITMAP(irqs_masked, DLN2_GPIO_MAX_PINS);
|
||||
DECLARE_BITMAP(irqs_enabled, DLN2_GPIO_MAX_PINS);
|
||||
DECLARE_BITMAP(irqs_pending, DLN2_GPIO_MAX_PINS);
|
||||
struct dln2_irq_work *irq_work;
|
||||
};
|
||||
|
||||
struct dln2_gpio_pin {
|
||||
__le16 pin;
|
||||
};
|
||||
|
||||
struct dln2_gpio_pin_val {
|
||||
__le16 pin __packed;
|
||||
u8 value;
|
||||
};
|
||||
|
||||
static int dln2_gpio_get_pin_count(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
__le16 count;
|
||||
int len = sizeof(count);
|
||||
|
||||
ret = dln2_transfer_rx(pdev, DLN2_GPIO_GET_PIN_COUNT, &count, &len);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (len < sizeof(count))
|
||||
return -EPROTO;
|
||||
|
||||
return le16_to_cpu(count);
|
||||
}
|
||||
|
||||
static int dln2_gpio_pin_cmd(struct dln2_gpio *dln2, int cmd, unsigned pin)
|
||||
{
|
||||
struct dln2_gpio_pin req = {
|
||||
.pin = cpu_to_le16(pin),
|
||||
};
|
||||
|
||||
return dln2_transfer_tx(dln2->pdev, cmd, &req, sizeof(req));
|
||||
}
|
||||
|
||||
static int dln2_gpio_pin_val(struct dln2_gpio *dln2, int cmd, unsigned int pin)
|
||||
{
|
||||
int ret;
|
||||
struct dln2_gpio_pin req = {
|
||||
.pin = cpu_to_le16(pin),
|
||||
};
|
||||
struct dln2_gpio_pin_val rsp;
|
||||
int len = sizeof(rsp);
|
||||
|
||||
ret = dln2_transfer(dln2->pdev, cmd, &req, sizeof(req), &rsp, &len);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (len < sizeof(rsp) || req.pin != rsp.pin)
|
||||
return -EPROTO;
|
||||
|
||||
return rsp.value;
|
||||
}
|
||||
|
||||
static int dln2_gpio_pin_get_in_val(struct dln2_gpio *dln2, unsigned int pin)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = dln2_gpio_pin_val(dln2, DLN2_GPIO_PIN_GET_VAL, pin);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return !!ret;
|
||||
}
|
||||
|
||||
static int dln2_gpio_pin_get_out_val(struct dln2_gpio *dln2, unsigned int pin)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = dln2_gpio_pin_val(dln2, DLN2_GPIO_PIN_GET_OUT_VAL, pin);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return !!ret;
|
||||
}
|
||||
|
||||
static void dln2_gpio_pin_set_out_val(struct dln2_gpio *dln2,
|
||||
unsigned int pin, int value)
|
||||
{
|
||||
struct dln2_gpio_pin_val req = {
|
||||
.pin = cpu_to_le16(pin),
|
||||
.value = value,
|
||||
};
|
||||
|
||||
dln2_transfer_tx(dln2->pdev, DLN2_GPIO_PIN_SET_OUT_VAL, &req,
|
||||
sizeof(req));
|
||||
}
|
||||
|
||||
#define DLN2_GPIO_DIRECTION_IN 0
|
||||
#define DLN2_GPIO_DIRECTION_OUT 1
|
||||
|
||||
static int dln2_gpio_request(struct gpio_chip *chip, unsigned offset)
|
||||
{
|
||||
struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
|
||||
struct dln2_gpio_pin req = {
|
||||
.pin = cpu_to_le16(offset),
|
||||
};
|
||||
struct dln2_gpio_pin_val rsp;
|
||||
int len = sizeof(rsp);
|
||||
int ret;
|
||||
|
||||
ret = dln2_gpio_pin_cmd(dln2, DLN2_GPIO_PIN_ENABLE, offset);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* cache the pin direction */
|
||||
ret = dln2_transfer(dln2->pdev, DLN2_GPIO_PIN_GET_DIRECTION,
|
||||
&req, sizeof(req), &rsp, &len);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (len < sizeof(rsp) || req.pin != rsp.pin) {
|
||||
ret = -EPROTO;
|
||||
goto out_disable;
|
||||
}
|
||||
|
||||
switch (rsp.value) {
|
||||
case DLN2_GPIO_DIRECTION_IN:
|
||||
clear_bit(offset, dln2->output_enabled);
|
||||
return 0;
|
||||
case DLN2_GPIO_DIRECTION_OUT:
|
||||
set_bit(offset, dln2->output_enabled);
|
||||
return 0;
|
||||
default:
|
||||
ret = -EPROTO;
|
||||
goto out_disable;
|
||||
}
|
||||
|
||||
out_disable:
|
||||
dln2_gpio_pin_cmd(dln2, DLN2_GPIO_PIN_DISABLE, offset);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void dln2_gpio_free(struct gpio_chip *chip, unsigned offset)
|
||||
{
|
||||
struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
|
||||
|
||||
dln2_gpio_pin_cmd(dln2, DLN2_GPIO_PIN_DISABLE, offset);
|
||||
}
|
||||
|
||||
static int dln2_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
|
||||
{
|
||||
struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
|
||||
|
||||
if (test_bit(offset, dln2->output_enabled))
|
||||
return GPIOF_DIR_OUT;
|
||||
|
||||
return GPIOF_DIR_IN;
|
||||
}
|
||||
|
||||
static int dln2_gpio_get(struct gpio_chip *chip, unsigned int offset)
|
||||
{
|
||||
struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
|
||||
int dir;
|
||||
|
||||
dir = dln2_gpio_get_direction(chip, offset);
|
||||
if (dir < 0)
|
||||
return dir;
|
||||
|
||||
if (dir == GPIOF_DIR_IN)
|
||||
return dln2_gpio_pin_get_in_val(dln2, offset);
|
||||
|
||||
return dln2_gpio_pin_get_out_val(dln2, offset);
|
||||
}
|
||||
|
||||
static void dln2_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
|
||||
{
|
||||
struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
|
||||
|
||||
dln2_gpio_pin_set_out_val(dln2, offset, value);
|
||||
}
|
||||
|
||||
static int dln2_gpio_set_direction(struct gpio_chip *chip, unsigned offset,
|
||||
unsigned dir)
|
||||
{
|
||||
struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
|
||||
struct dln2_gpio_pin_val req = {
|
||||
.pin = cpu_to_le16(offset),
|
||||
.value = dir,
|
||||
};
|
||||
int ret;
|
||||
|
||||
ret = dln2_transfer_tx(dln2->pdev, DLN2_GPIO_PIN_SET_DIRECTION,
|
||||
&req, sizeof(req));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (dir == DLN2_GPIO_DIRECTION_OUT)
|
||||
set_bit(offset, dln2->output_enabled);
|
||||
else
|
||||
clear_bit(offset, dln2->output_enabled);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dln2_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
|
||||
{
|
||||
return dln2_gpio_set_direction(chip, offset, DLN2_GPIO_DIRECTION_IN);
|
||||
}
|
||||
|
||||
static int dln2_gpio_direction_output(struct gpio_chip *chip, unsigned offset,
|
||||
int value)
|
||||
{
|
||||
return dln2_gpio_set_direction(chip, offset, DLN2_GPIO_DIRECTION_OUT);
|
||||
}
|
||||
|
||||
static int dln2_gpio_set_debounce(struct gpio_chip *chip, unsigned offset,
|
||||
unsigned debounce)
|
||||
{
|
||||
struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
|
||||
__le32 duration = cpu_to_le32(debounce);
|
||||
|
||||
return dln2_transfer_tx(dln2->pdev, DLN2_GPIO_SET_DEBOUNCE,
|
||||
&duration, sizeof(duration));
|
||||
}
|
||||
|
||||
static int dln2_gpio_set_event_cfg(struct dln2_gpio *dln2, unsigned pin,
|
||||
unsigned type, unsigned period)
|
||||
{
|
||||
struct {
|
||||
__le16 pin;
|
||||
u8 type;
|
||||
__le16 period;
|
||||
} __packed req = {
|
||||
.pin = cpu_to_le16(pin),
|
||||
.type = type,
|
||||
.period = cpu_to_le16(period),
|
||||
};
|
||||
|
||||
return dln2_transfer_tx(dln2->pdev, DLN2_GPIO_PIN_SET_EVENT_CFG,
|
||||
&req, sizeof(req));
|
||||
}
|
||||
|
||||
static void dln2_irq_work(struct work_struct *w)
|
||||
{
|
||||
struct dln2_irq_work *iw = container_of(w, struct dln2_irq_work, work);
|
||||
struct dln2_gpio *dln2 = iw->dln2;
|
||||
u8 type = iw->type & DLN2_GPIO_EVENT_MASK;
|
||||
|
||||
if (test_bit(iw->pin, dln2->irqs_enabled))
|
||||
dln2_gpio_set_event_cfg(dln2, iw->pin, type, 0);
|
||||
else
|
||||
dln2_gpio_set_event_cfg(dln2, iw->pin, DLN2_GPIO_EVENT_NONE, 0);
|
||||
}
|
||||
|
||||
static void dln2_irq_enable(struct irq_data *irqd)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
|
||||
struct dln2_gpio *dln2 = container_of(gc, struct dln2_gpio, gpio);
|
||||
int pin = irqd_to_hwirq(irqd);
|
||||
|
||||
set_bit(pin, dln2->irqs_enabled);
|
||||
schedule_work(&dln2->irq_work[pin].work);
|
||||
}
|
||||
|
||||
static void dln2_irq_disable(struct irq_data *irqd)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
|
||||
struct dln2_gpio *dln2 = container_of(gc, struct dln2_gpio, gpio);
|
||||
int pin = irqd_to_hwirq(irqd);
|
||||
|
||||
clear_bit(pin, dln2->irqs_enabled);
|
||||
schedule_work(&dln2->irq_work[pin].work);
|
||||
}
|
||||
|
||||
static void dln2_irq_mask(struct irq_data *irqd)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
|
||||
struct dln2_gpio *dln2 = container_of(gc, struct dln2_gpio, gpio);
|
||||
int pin = irqd_to_hwirq(irqd);
|
||||
|
||||
set_bit(pin, dln2->irqs_masked);
|
||||
}
|
||||
|
||||
static void dln2_irq_unmask(struct irq_data *irqd)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
|
||||
struct dln2_gpio *dln2 = container_of(gc, struct dln2_gpio, gpio);
|
||||
struct device *dev = dln2->gpio.dev;
|
||||
int pin = irqd_to_hwirq(irqd);
|
||||
|
||||
if (test_and_clear_bit(pin, dln2->irqs_pending)) {
|
||||
int irq;
|
||||
|
||||
irq = irq_find_mapping(dln2->gpio.irqdomain, pin);
|
||||
if (!irq) {
|
||||
dev_err(dev, "pin %d not mapped to IRQ\n", pin);
|
||||
return;
|
||||
}
|
||||
|
||||
generic_handle_irq(irq);
|
||||
}
|
||||
}
|
||||
|
||||
static int dln2_irq_set_type(struct irq_data *irqd, unsigned type)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
|
||||
struct dln2_gpio *dln2 = container_of(gc, struct dln2_gpio, gpio);
|
||||
int pin = irqd_to_hwirq(irqd);
|
||||
|
||||
switch (type) {
|
||||
case IRQ_TYPE_LEVEL_HIGH:
|
||||
dln2->irq_work[pin].type = DLN2_GPIO_EVENT_LVL_HIGH;
|
||||
break;
|
||||
case IRQ_TYPE_LEVEL_LOW:
|
||||
dln2->irq_work[pin].type = DLN2_GPIO_EVENT_LVL_LOW;
|
||||
break;
|
||||
case IRQ_TYPE_EDGE_BOTH:
|
||||
dln2->irq_work[pin].type = DLN2_GPIO_EVENT_CHANGE;
|
||||
break;
|
||||
case IRQ_TYPE_EDGE_RISING:
|
||||
dln2->irq_work[pin].type = DLN2_GPIO_EVENT_CHANGE_RISING;
|
||||
break;
|
||||
case IRQ_TYPE_EDGE_FALLING:
|
||||
dln2->irq_work[pin].type = DLN2_GPIO_EVENT_CHANGE_FALLING;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct irq_chip dln2_gpio_irqchip = {
|
||||
.name = "dln2-irq",
|
||||
.irq_enable = dln2_irq_enable,
|
||||
.irq_disable = dln2_irq_disable,
|
||||
.irq_mask = dln2_irq_mask,
|
||||
.irq_unmask = dln2_irq_unmask,
|
||||
.irq_set_type = dln2_irq_set_type,
|
||||
};
|
||||
|
||||
static void dln2_gpio_event(struct platform_device *pdev, u16 echo,
|
||||
const void *data, int len)
|
||||
{
|
||||
int pin, irq;
|
||||
const struct {
|
||||
__le16 count;
|
||||
__u8 type;
|
||||
__le16 pin;
|
||||
__u8 value;
|
||||
} __packed *event = data;
|
||||
struct dln2_gpio *dln2 = platform_get_drvdata(pdev);
|
||||
|
||||
if (len < sizeof(*event)) {
|
||||
dev_err(dln2->gpio.dev, "short event message\n");
|
||||
return;
|
||||
}
|
||||
|
||||
pin = le16_to_cpu(event->pin);
|
||||
if (pin >= dln2->gpio.ngpio) {
|
||||
dev_err(dln2->gpio.dev, "out of bounds pin %d\n", pin);
|
||||
return;
|
||||
}
|
||||
|
||||
irq = irq_find_mapping(dln2->gpio.irqdomain, pin);
|
||||
if (!irq) {
|
||||
dev_err(dln2->gpio.dev, "pin %d not mapped to IRQ\n", pin);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!test_bit(pin, dln2->irqs_enabled))
|
||||
return;
|
||||
if (test_bit(pin, dln2->irqs_masked)) {
|
||||
set_bit(pin, dln2->irqs_pending);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (dln2->irq_work[pin].type) {
|
||||
case DLN2_GPIO_EVENT_CHANGE_RISING:
|
||||
if (event->value)
|
||||
generic_handle_irq(irq);
|
||||
break;
|
||||
case DLN2_GPIO_EVENT_CHANGE_FALLING:
|
||||
if (!event->value)
|
||||
generic_handle_irq(irq);
|
||||
break;
|
||||
default:
|
||||
generic_handle_irq(irq);
|
||||
}
|
||||
}
|
||||
|
||||
static int dln2_gpio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct dln2_gpio *dln2;
|
||||
struct device *dev = &pdev->dev;
|
||||
int pins;
|
||||
int i, ret;
|
||||
|
||||
pins = dln2_gpio_get_pin_count(pdev);
|
||||
if (pins < 0) {
|
||||
dev_err(dev, "failed to get pin count: %d\n", pins);
|
||||
return pins;
|
||||
}
|
||||
if (pins > DLN2_GPIO_MAX_PINS) {
|
||||
pins = DLN2_GPIO_MAX_PINS;
|
||||
dev_warn(dev, "clamping pins to %d\n", DLN2_GPIO_MAX_PINS);
|
||||
}
|
||||
|
||||
dln2 = devm_kzalloc(&pdev->dev, sizeof(*dln2), GFP_KERNEL);
|
||||
if (!dln2)
|
||||
return -ENOMEM;
|
||||
|
||||
dln2->irq_work = devm_kcalloc(&pdev->dev, pins,
|
||||
sizeof(struct dln2_irq_work), GFP_KERNEL);
|
||||
if (!dln2->irq_work)
|
||||
return -ENOMEM;
|
||||
for (i = 0; i < pins; i++) {
|
||||
INIT_WORK(&dln2->irq_work[i].work, dln2_irq_work);
|
||||
dln2->irq_work[i].pin = i;
|
||||
dln2->irq_work[i].dln2 = dln2;
|
||||
}
|
||||
|
||||
dln2->pdev = pdev;
|
||||
|
||||
dln2->gpio.label = "dln2";
|
||||
dln2->gpio.dev = dev;
|
||||
dln2->gpio.owner = THIS_MODULE;
|
||||
dln2->gpio.base = -1;
|
||||
dln2->gpio.ngpio = pins;
|
||||
dln2->gpio.exported = true;
|
||||
dln2->gpio.can_sleep = true;
|
||||
dln2->gpio.irq_not_threaded = true;
|
||||
dln2->gpio.set = dln2_gpio_set;
|
||||
dln2->gpio.get = dln2_gpio_get;
|
||||
dln2->gpio.request = dln2_gpio_request;
|
||||
dln2->gpio.free = dln2_gpio_free;
|
||||
dln2->gpio.get_direction = dln2_gpio_get_direction;
|
||||
dln2->gpio.direction_input = dln2_gpio_direction_input;
|
||||
dln2->gpio.direction_output = dln2_gpio_direction_output;
|
||||
dln2->gpio.set_debounce = dln2_gpio_set_debounce;
|
||||
|
||||
platform_set_drvdata(pdev, dln2);
|
||||
|
||||
ret = gpiochip_add(&dln2->gpio);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to add gpio chip: %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = gpiochip_irqchip_add(&dln2->gpio, &dln2_gpio_irqchip, 0,
|
||||
handle_simple_irq, IRQ_TYPE_NONE);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to add irq chip: %d\n", ret);
|
||||
goto out_gpiochip_remove;
|
||||
}
|
||||
|
||||
ret = dln2_register_event_cb(pdev, DLN2_GPIO_CONDITION_MET_EV,
|
||||
dln2_gpio_event);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to register event cb: %d\n", ret);
|
||||
goto out_gpiochip_remove;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_gpiochip_remove:
|
||||
gpiochip_remove(&dln2->gpio);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dln2_gpio_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct dln2_gpio *dln2 = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
dln2_unregister_event_cb(pdev, DLN2_GPIO_CONDITION_MET_EV);
|
||||
for (i = 0; i < dln2->gpio.ngpio; i++)
|
||||
flush_work(&dln2->irq_work[i].work);
|
||||
gpiochip_remove(&dln2->gpio);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver dln2_gpio_driver = {
|
||||
.driver.name = "dln2-gpio",
|
||||
.probe = dln2_gpio_probe,
|
||||
.remove = dln2_gpio_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(dln2_gpio_driver);
|
||||
|
||||
MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com");
|
||||
MODULE_DESCRIPTION("Driver for the Diolan DLN2 GPIO interface");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:dln2-gpio");
|
|
@ -881,6 +881,16 @@ config I2C_DIOLAN_U2C
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called i2c-diolan-u2c.
|
||||
|
||||
config I2C_DLN2
|
||||
tristate "Diolan DLN-2 USB I2C adapter"
|
||||
depends on MFD_DLN2
|
||||
help
|
||||
If you say yes to this option, support will be included for Diolan
|
||||
DLN2, a USB to I2C interface.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called i2c-dln2.
|
||||
|
||||
config I2C_PARPORT
|
||||
tristate "Parallel port adapter"
|
||||
depends on PARPORT
|
||||
|
|
|
@ -87,6 +87,7 @@ obj-$(CONFIG_I2C_RCAR) += i2c-rcar.o
|
|||
|
||||
# External I2C/SMBus adapter drivers
|
||||
obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o
|
||||
obj-$(CONFIG_I2C_DLN2) += i2c-dln2.o
|
||||
obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o
|
||||
obj-$(CONFIG_I2C_PARPORT_LIGHT) += i2c-parport-light.o
|
||||
obj-$(CONFIG_I2C_ROBOTFUZZ_OSIF) += i2c-robotfuzz-osif.o
|
||||
|
|
|
@ -0,0 +1,262 @@
|
|||
/*
|
||||
* Driver for the Diolan DLN-2 USB-I2C adapter
|
||||
*
|
||||
* Copyright (c) 2014 Intel Corporation
|
||||
*
|
||||
* Derived from:
|
||||
* i2c-diolan-u2c.c
|
||||
* Copyright (c) 2010-2011 Ericsson AB
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mfd/dln2.h>
|
||||
|
||||
#define DLN2_I2C_MODULE_ID 0x03
|
||||
#define DLN2_I2C_CMD(cmd) DLN2_CMD(cmd, DLN2_I2C_MODULE_ID)
|
||||
|
||||
/* I2C commands */
|
||||
#define DLN2_I2C_GET_PORT_COUNT DLN2_I2C_CMD(0x00)
|
||||
#define DLN2_I2C_ENABLE DLN2_I2C_CMD(0x01)
|
||||
#define DLN2_I2C_DISABLE DLN2_I2C_CMD(0x02)
|
||||
#define DLN2_I2C_IS_ENABLED DLN2_I2C_CMD(0x03)
|
||||
#define DLN2_I2C_WRITE DLN2_I2C_CMD(0x06)
|
||||
#define DLN2_I2C_READ DLN2_I2C_CMD(0x07)
|
||||
#define DLN2_I2C_SCAN_DEVICES DLN2_I2C_CMD(0x08)
|
||||
#define DLN2_I2C_PULLUP_ENABLE DLN2_I2C_CMD(0x09)
|
||||
#define DLN2_I2C_PULLUP_DISABLE DLN2_I2C_CMD(0x0A)
|
||||
#define DLN2_I2C_PULLUP_IS_ENABLED DLN2_I2C_CMD(0x0B)
|
||||
#define DLN2_I2C_TRANSFER DLN2_I2C_CMD(0x0C)
|
||||
#define DLN2_I2C_SET_MAX_REPLY_COUNT DLN2_I2C_CMD(0x0D)
|
||||
#define DLN2_I2C_GET_MAX_REPLY_COUNT DLN2_I2C_CMD(0x0E)
|
||||
|
||||
#define DLN2_I2C_MAX_XFER_SIZE 256
|
||||
#define DLN2_I2C_BUF_SIZE (DLN2_I2C_MAX_XFER_SIZE + 16)
|
||||
|
||||
struct dln2_i2c {
|
||||
struct platform_device *pdev;
|
||||
struct i2c_adapter adapter;
|
||||
u8 port;
|
||||
/*
|
||||
* Buffer to hold the packet for read or write transfers. One is enough
|
||||
* since we can't have multiple transfers in parallel on the i2c bus.
|
||||
*/
|
||||
void *buf;
|
||||
};
|
||||
|
||||
static int dln2_i2c_enable(struct dln2_i2c *dln2, bool enable)
|
||||
{
|
||||
u16 cmd;
|
||||
struct {
|
||||
u8 port;
|
||||
} tx;
|
||||
|
||||
tx.port = dln2->port;
|
||||
|
||||
if (enable)
|
||||
cmd = DLN2_I2C_ENABLE;
|
||||
else
|
||||
cmd = DLN2_I2C_DISABLE;
|
||||
|
||||
return dln2_transfer_tx(dln2->pdev, cmd, &tx, sizeof(tx));
|
||||
}
|
||||
|
||||
static int dln2_i2c_write(struct dln2_i2c *dln2, u8 addr,
|
||||
u8 *data, u16 data_len)
|
||||
{
|
||||
int ret;
|
||||
struct {
|
||||
u8 port;
|
||||
u8 addr;
|
||||
u8 mem_addr_len;
|
||||
__le32 mem_addr;
|
||||
__le16 buf_len;
|
||||
u8 buf[DLN2_I2C_MAX_XFER_SIZE];
|
||||
} __packed *tx = dln2->buf;
|
||||
unsigned len;
|
||||
|
||||
BUILD_BUG_ON(sizeof(*tx) > DLN2_I2C_BUF_SIZE);
|
||||
|
||||
tx->port = dln2->port;
|
||||
tx->addr = addr;
|
||||
tx->mem_addr_len = 0;
|
||||
tx->mem_addr = 0;
|
||||
tx->buf_len = cpu_to_le16(data_len);
|
||||
memcpy(tx->buf, data, data_len);
|
||||
|
||||
len = sizeof(*tx) + data_len - DLN2_I2C_MAX_XFER_SIZE;
|
||||
ret = dln2_transfer_tx(dln2->pdev, DLN2_I2C_WRITE, tx, len);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return data_len;
|
||||
}
|
||||
|
||||
static int dln2_i2c_read(struct dln2_i2c *dln2, u16 addr, u8 *data,
|
||||
u16 data_len)
|
||||
{
|
||||
int ret;
|
||||
struct {
|
||||
u8 port;
|
||||
u8 addr;
|
||||
u8 mem_addr_len;
|
||||
__le32 mem_addr;
|
||||
__le16 buf_len;
|
||||
} __packed tx;
|
||||
struct {
|
||||
__le16 buf_len;
|
||||
u8 buf[DLN2_I2C_MAX_XFER_SIZE];
|
||||
} __packed *rx = dln2->buf;
|
||||
unsigned rx_len = sizeof(*rx);
|
||||
|
||||
BUILD_BUG_ON(sizeof(*rx) > DLN2_I2C_BUF_SIZE);
|
||||
|
||||
tx.port = dln2->port;
|
||||
tx.addr = addr;
|
||||
tx.mem_addr_len = 0;
|
||||
tx.mem_addr = 0;
|
||||
tx.buf_len = cpu_to_le16(data_len);
|
||||
|
||||
ret = dln2_transfer(dln2->pdev, DLN2_I2C_READ, &tx, sizeof(tx),
|
||||
rx, &rx_len);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (rx_len < sizeof(rx->buf_len) + data_len)
|
||||
return -EPROTO;
|
||||
if (le16_to_cpu(rx->buf_len) != data_len)
|
||||
return -EPROTO;
|
||||
|
||||
memcpy(data, rx->buf, data_len);
|
||||
|
||||
return data_len;
|
||||
}
|
||||
|
||||
static int dln2_i2c_xfer(struct i2c_adapter *adapter,
|
||||
struct i2c_msg *msgs, int num)
|
||||
{
|
||||
struct dln2_i2c *dln2 = i2c_get_adapdata(adapter);
|
||||
struct i2c_msg *pmsg;
|
||||
struct device *dev = &dln2->adapter.dev;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
int ret;
|
||||
|
||||
pmsg = &msgs[i];
|
||||
|
||||
if (pmsg->len > DLN2_I2C_MAX_XFER_SIZE) {
|
||||
dev_warn(dev, "maximum transfer size exceeded\n");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
if (pmsg->flags & I2C_M_RD) {
|
||||
ret = dln2_i2c_read(dln2, pmsg->addr, pmsg->buf,
|
||||
pmsg->len);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
pmsg->len = ret;
|
||||
} else {
|
||||
ret = dln2_i2c_write(dln2, pmsg->addr, pmsg->buf,
|
||||
pmsg->len);
|
||||
if (ret != pmsg->len)
|
||||
return -EPROTO;
|
||||
}
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
static u32 dln2_i2c_func(struct i2c_adapter *a)
|
||||
{
|
||||
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_PROC_CALL |
|
||||
I2C_FUNC_SMBUS_I2C_BLOCK;
|
||||
}
|
||||
|
||||
static const struct i2c_algorithm dln2_i2c_usb_algorithm = {
|
||||
.master_xfer = dln2_i2c_xfer,
|
||||
.functionality = dln2_i2c_func,
|
||||
};
|
||||
|
||||
static int dln2_i2c_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct dln2_i2c *dln2;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct dln2_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
|
||||
dln2 = devm_kzalloc(dev, sizeof(*dln2), GFP_KERNEL);
|
||||
if (!dln2)
|
||||
return -ENOMEM;
|
||||
|
||||
dln2->buf = devm_kmalloc(dev, DLN2_I2C_BUF_SIZE, GFP_KERNEL);
|
||||
if (!dln2->buf)
|
||||
return -ENOMEM;
|
||||
|
||||
dln2->pdev = pdev;
|
||||
dln2->port = pdata->port;
|
||||
|
||||
/* setup i2c adapter description */
|
||||
dln2->adapter.owner = THIS_MODULE;
|
||||
dln2->adapter.class = I2C_CLASS_HWMON;
|
||||
dln2->adapter.algo = &dln2_i2c_usb_algorithm;
|
||||
dln2->adapter.dev.parent = dev;
|
||||
i2c_set_adapdata(&dln2->adapter, dln2);
|
||||
snprintf(dln2->adapter.name, sizeof(dln2->adapter.name), "%s-%s-%d",
|
||||
"dln2-i2c", dev_name(pdev->dev.parent), dln2->port);
|
||||
|
||||
platform_set_drvdata(pdev, dln2);
|
||||
|
||||
/* initialize the i2c interface */
|
||||
ret = dln2_i2c_enable(dln2, true);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to initialize adapter: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* and finally attach to i2c layer */
|
||||
ret = i2c_add_adapter(&dln2->adapter);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to add I2C adapter: %d\n", ret);
|
||||
goto out_disable;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_disable:
|
||||
dln2_i2c_enable(dln2, false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dln2_i2c_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct dln2_i2c *dln2 = platform_get_drvdata(pdev);
|
||||
|
||||
i2c_del_adapter(&dln2->adapter);
|
||||
dln2_i2c_enable(dln2, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver dln2_i2c_driver = {
|
||||
.driver.name = "dln2-i2c",
|
||||
.probe = dln2_i2c_probe,
|
||||
.remove = dln2_i2c_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(dln2_i2c_driver);
|
||||
|
||||
MODULE_AUTHOR("Laurentiu Palcu <laurentiu.palcu@intel.com>");
|
||||
MODULE_DESCRIPTION("Driver for the Diolan DLN2 I2C master interface");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:dln2-i2c");
|
|
@ -127,6 +127,14 @@ config AT91_ADC
|
|||
help
|
||||
Say yes here to build support for Atmel AT91 ADC.
|
||||
|
||||
config AXP288_ADC
|
||||
tristate "X-Powers AXP288 ADC driver"
|
||||
depends on MFD_AXP20X
|
||||
help
|
||||
Say yes here to have support for X-Powers power management IC (PMIC) ADC
|
||||
device. Depending on platform configuration, this general purpose ADC can
|
||||
be used for sampling sensors such as thermal resistors.
|
||||
|
||||
config EXYNOS_ADC
|
||||
tristate "Exynos ADC driver support"
|
||||
depends on ARCH_EXYNOS || ARCH_S3C24XX || ARCH_S3C64XX || (OF && COMPILE_TEST)
|
||||
|
|
|
@ -14,6 +14,7 @@ obj-$(CONFIG_AD7793) += ad7793.o
|
|||
obj-$(CONFIG_AD7887) += ad7887.o
|
||||
obj-$(CONFIG_AD799X) += ad799x.o
|
||||
obj-$(CONFIG_AT91_ADC) += at91_adc.o
|
||||
obj-$(CONFIG_AXP288_ADC) += axp288_adc.o
|
||||
obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o
|
||||
obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
|
||||
obj-$(CONFIG_MAX1027) += max1027.o
|
||||
|
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* axp288_adc.c - X-Powers AXP288 PMIC ADC Driver
|
||||
*
|
||||
* Copyright (C) 2014 Intel Corporation
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; version 2 of the License.
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/mfd/axp20x.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/machine.h>
|
||||
#include <linux/iio/driver.h>
|
||||
|
||||
#define AXP288_ADC_EN_MASK 0xF1
|
||||
#define AXP288_ADC_TS_PIN_GPADC 0xF2
|
||||
#define AXP288_ADC_TS_PIN_ON 0xF3
|
||||
|
||||
enum axp288_adc_id {
|
||||
AXP288_ADC_TS,
|
||||
AXP288_ADC_PMIC,
|
||||
AXP288_ADC_GP,
|
||||
AXP288_ADC_BATT_CHRG_I,
|
||||
AXP288_ADC_BATT_DISCHRG_I,
|
||||
AXP288_ADC_BATT_V,
|
||||
AXP288_ADC_NR_CHAN,
|
||||
};
|
||||
|
||||
struct axp288_adc_info {
|
||||
int irq;
|
||||
struct regmap *regmap;
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec const axp288_adc_channels[] = {
|
||||
{
|
||||
.indexed = 1,
|
||||
.type = IIO_TEMP,
|
||||
.channel = 0,
|
||||
.address = AXP288_TS_ADC_H,
|
||||
.datasheet_name = "TS_PIN",
|
||||
}, {
|
||||
.indexed = 1,
|
||||
.type = IIO_TEMP,
|
||||
.channel = 1,
|
||||
.address = AXP288_PMIC_ADC_H,
|
||||
.datasheet_name = "PMIC_TEMP",
|
||||
}, {
|
||||
.indexed = 1,
|
||||
.type = IIO_TEMP,
|
||||
.channel = 2,
|
||||
.address = AXP288_GP_ADC_H,
|
||||
.datasheet_name = "GPADC",
|
||||
}, {
|
||||
.indexed = 1,
|
||||
.type = IIO_CURRENT,
|
||||
.channel = 3,
|
||||
.address = AXP20X_BATT_CHRG_I_H,
|
||||
.datasheet_name = "BATT_CHG_I",
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
|
||||
}, {
|
||||
.indexed = 1,
|
||||
.type = IIO_CURRENT,
|
||||
.channel = 4,
|
||||
.address = AXP20X_BATT_DISCHRG_I_H,
|
||||
.datasheet_name = "BATT_DISCHRG_I",
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
|
||||
}, {
|
||||
.indexed = 1,
|
||||
.type = IIO_VOLTAGE,
|
||||
.channel = 5,
|
||||
.address = AXP20X_BATT_V_H,
|
||||
.datasheet_name = "BATT_V",
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
|
||||
},
|
||||
};
|
||||
|
||||
#define AXP288_ADC_MAP(_adc_channel_label, _consumer_dev_name, \
|
||||
_consumer_channel) \
|
||||
{ \
|
||||
.adc_channel_label = _adc_channel_label, \
|
||||
.consumer_dev_name = _consumer_dev_name, \
|
||||
.consumer_channel = _consumer_channel, \
|
||||
}
|
||||
|
||||
/* for consumer drivers */
|
||||
static struct iio_map axp288_adc_default_maps[] = {
|
||||
AXP288_ADC_MAP("TS_PIN", "axp288-batt", "axp288-batt-temp"),
|
||||
AXP288_ADC_MAP("PMIC_TEMP", "axp288-pmic", "axp288-pmic-temp"),
|
||||
AXP288_ADC_MAP("GPADC", "axp288-gpadc", "axp288-system-temp"),
|
||||
AXP288_ADC_MAP("BATT_CHG_I", "axp288-chrg", "axp288-chrg-curr"),
|
||||
AXP288_ADC_MAP("BATT_DISCHRG_I", "axp288-chrg", "axp288-chrg-d-curr"),
|
||||
AXP288_ADC_MAP("BATT_V", "axp288-batt", "axp288-batt-volt"),
|
||||
{},
|
||||
};
|
||||
|
||||
static int axp288_adc_read_channel(int *val, unsigned long address,
|
||||
struct regmap *regmap)
|
||||
{
|
||||
u8 buf[2];
|
||||
|
||||
if (regmap_bulk_read(regmap, address, buf, 2))
|
||||
return -EIO;
|
||||
*val = (buf[0] << 4) + ((buf[1] >> 4) & 0x0F);
|
||||
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
|
||||
static int axp288_adc_set_ts(struct regmap *regmap, unsigned int mode,
|
||||
unsigned long address)
|
||||
{
|
||||
/* channels other than GPADC do not need to switch TS pin */
|
||||
if (address != AXP288_GP_ADC_H)
|
||||
return 0;
|
||||
|
||||
return regmap_write(regmap, AXP288_ADC_TS_PIN_CTRL, mode);
|
||||
}
|
||||
|
||||
static int axp288_adc_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
int ret;
|
||||
struct axp288_adc_info *info = iio_priv(indio_dev);
|
||||
|
||||
mutex_lock(&indio_dev->mlock);
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
if (axp288_adc_set_ts(info->regmap, AXP288_ADC_TS_PIN_GPADC,
|
||||
chan->address)) {
|
||||
dev_err(&indio_dev->dev, "GPADC mode\n");
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
ret = axp288_adc_read_channel(val, chan->address, info->regmap);
|
||||
if (axp288_adc_set_ts(info->regmap, AXP288_ADC_TS_PIN_ON,
|
||||
chan->address))
|
||||
dev_err(&indio_dev->dev, "TS pin restore\n");
|
||||
break;
|
||||
case IIO_CHAN_INFO_PROCESSED:
|
||||
ret = axp288_adc_read_channel(val, chan->address, info->regmap);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
mutex_unlock(&indio_dev->mlock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int axp288_adc_set_state(struct regmap *regmap)
|
||||
{
|
||||
/* ADC should be always enabled for internal FG to function */
|
||||
if (regmap_write(regmap, AXP288_ADC_TS_PIN_CTRL, AXP288_ADC_TS_PIN_ON))
|
||||
return -EIO;
|
||||
|
||||
return regmap_write(regmap, AXP20X_ADC_EN1, AXP288_ADC_EN_MASK);
|
||||
}
|
||||
|
||||
static const struct iio_info axp288_adc_iio_info = {
|
||||
.read_raw = &axp288_adc_read_raw,
|
||||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int axp288_adc_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct axp288_adc_info *info;
|
||||
struct iio_dev *indio_dev;
|
||||
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
info = iio_priv(indio_dev);
|
||||
info->irq = platform_get_irq(pdev, 0);
|
||||
if (info->irq < 0) {
|
||||
dev_err(&pdev->dev, "no irq resource?\n");
|
||||
return info->irq;
|
||||
}
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
info->regmap = axp20x->regmap;
|
||||
/*
|
||||
* Set ADC to enabled state at all time, including system suspend.
|
||||
* otherwise internal fuel gauge functionality may be affected.
|
||||
*/
|
||||
ret = axp288_adc_set_state(axp20x->regmap);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "unable to enable ADC device\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
indio_dev->dev.parent = &pdev->dev;
|
||||
indio_dev->name = pdev->name;
|
||||
indio_dev->channels = axp288_adc_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(axp288_adc_channels);
|
||||
indio_dev->info = &axp288_adc_iio_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
ret = iio_map_array_register(indio_dev, axp288_adc_default_maps);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "unable to register iio device\n");
|
||||
goto err_array_unregister;
|
||||
}
|
||||
return 0;
|
||||
|
||||
err_array_unregister:
|
||||
iio_map_array_unregister(indio_dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int axp288_adc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_map_array_unregister(indio_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_device_id axp288_adc_id_table[] = {
|
||||
{ .name = "axp288_adc" },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver axp288_adc_driver = {
|
||||
.probe = axp288_adc_probe,
|
||||
.remove = axp288_adc_remove,
|
||||
.id_table = axp288_adc_id_table,
|
||||
.driver = {
|
||||
.name = "axp288_adc",
|
||||
},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(platform, axp288_adc_id_table);
|
||||
|
||||
module_platform_driver(axp288_adc_driver);
|
||||
|
||||
MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@linux.intel.com>");
|
||||
MODULE_DESCRIPTION("X-Powers AXP288 ADC Driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -74,7 +74,8 @@ config MFD_AXP20X
|
|||
select REGMAP_IRQ
|
||||
depends on I2C=y
|
||||
help
|
||||
If you say Y here you get support for the X-Powers AXP202 and AXP209.
|
||||
If you say Y here you get support for the X-Powers AXP202, AXP209 and
|
||||
AXP288 power management IC (PMIC).
|
||||
This driver include only the core APIs. You have to select individual
|
||||
components like regulators or the PEK (Power Enable Key) under the
|
||||
corresponding menus.
|
||||
|
@ -183,6 +184,16 @@ config MFD_DA9063
|
|||
Additional drivers must be enabled in order to use the functionality
|
||||
of the device.
|
||||
|
||||
config MFD_DLN2
|
||||
tristate "Diolan DLN2 support"
|
||||
select MFD_CORE
|
||||
depends on USB
|
||||
help
|
||||
This adds support for Diolan USB-I2C/SPI/GPIO Master Adapter
|
||||
DLN-2. Additional drivers such as I2C_DLN2, GPIO_DLN2,
|
||||
etc. must be enabled in order to use the functionality of
|
||||
the device.
|
||||
|
||||
config MFD_MC13XXX
|
||||
tristate
|
||||
depends on (SPI_MASTER || I2C)
|
||||
|
|
|
@ -174,6 +174,7 @@ obj-$(CONFIG_MFD_STW481X) += stw481x.o
|
|||
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_DLN2) += dln2.o
|
||||
|
||||
intel-soc-pmic-objs := intel_soc_pmic_core.o intel_soc_pmic_crc.o
|
||||
obj-$(CONFIG_INTEL_SOC_PMIC) += intel-soc-pmic.o
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/*
|
||||
* axp20x.c - MFD core driver for the X-Powers AXP202 and AXP209
|
||||
* axp20x.c - MFD core driver for the X-Powers' Power Management ICs
|
||||
*
|
||||
* AXP20x comprises an adaptive USB-Compatible PWM charger, 2 BUCK DC-DC
|
||||
* converters, 5 LDOs, multiple 12-bit ADCs of voltage, current and temperature
|
||||
* as well as 4 configurable GPIOs.
|
||||
* AXP20x typically comprises an adaptive USB-Compatible PWM charger, BUCK DC-DC
|
||||
* converters, LDOs, multiple 12-bit ADCs of voltage, current and temperature
|
||||
* as well as configurable GPIOs.
|
||||
*
|
||||
* Author: Carlo Caione <carlo@caione.org>
|
||||
*
|
||||
|
@ -25,9 +25,16 @@
|
|||
#include <linux/mfd/core.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/acpi.h>
|
||||
|
||||
#define AXP20X_OFF 0x80
|
||||
|
||||
static const char const *axp20x_model_names[] = {
|
||||
"AXP202",
|
||||
"AXP209",
|
||||
"AXP288",
|
||||
};
|
||||
|
||||
static const struct regmap_range axp20x_writeable_ranges[] = {
|
||||
regmap_reg_range(AXP20X_DATACACHE(0), AXP20X_IRQ5_STATE),
|
||||
regmap_reg_range(AXP20X_DCDC_MODE, AXP20X_FG_RES),
|
||||
|
@ -47,6 +54,25 @@ static const struct regmap_access_table axp20x_volatile_table = {
|
|||
.n_yes_ranges = ARRAY_SIZE(axp20x_volatile_ranges),
|
||||
};
|
||||
|
||||
static const struct regmap_range axp288_writeable_ranges[] = {
|
||||
regmap_reg_range(AXP20X_DATACACHE(0), AXP20X_IRQ6_STATE),
|
||||
regmap_reg_range(AXP20X_DCDC_MODE, AXP288_FG_TUNE5),
|
||||
};
|
||||
|
||||
static const struct regmap_range axp288_volatile_ranges[] = {
|
||||
regmap_reg_range(AXP20X_IRQ1_EN, AXP20X_IPSOUT_V_HIGH_L),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table axp288_writeable_table = {
|
||||
.yes_ranges = axp288_writeable_ranges,
|
||||
.n_yes_ranges = ARRAY_SIZE(axp288_writeable_ranges),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table axp288_volatile_table = {
|
||||
.yes_ranges = axp288_volatile_ranges,
|
||||
.n_yes_ranges = ARRAY_SIZE(axp288_volatile_ranges),
|
||||
};
|
||||
|
||||
static struct resource axp20x_pek_resources[] = {
|
||||
{
|
||||
.name = "PEK_DBR",
|
||||
|
@ -61,6 +87,39 @@ static struct resource axp20x_pek_resources[] = {
|
|||
},
|
||||
};
|
||||
|
||||
static struct resource axp288_battery_resources[] = {
|
||||
{
|
||||
.start = AXP288_IRQ_QWBTU,
|
||||
.end = AXP288_IRQ_QWBTU,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
{
|
||||
.start = AXP288_IRQ_WBTU,
|
||||
.end = AXP288_IRQ_WBTU,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
{
|
||||
.start = AXP288_IRQ_QWBTO,
|
||||
.end = AXP288_IRQ_QWBTO,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
{
|
||||
.start = AXP288_IRQ_WBTO,
|
||||
.end = AXP288_IRQ_WBTO,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
{
|
||||
.start = AXP288_IRQ_WL2,
|
||||
.end = AXP288_IRQ_WL2,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
{
|
||||
.start = AXP288_IRQ_WL1,
|
||||
.end = AXP288_IRQ_WL1,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct regmap_config axp20x_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
|
@ -70,47 +129,96 @@ static const struct regmap_config axp20x_regmap_config = {
|
|||
.cache_type = REGCACHE_RBTREE,
|
||||
};
|
||||
|
||||
#define AXP20X_IRQ(_irq, _off, _mask) \
|
||||
[AXP20X_IRQ_##_irq] = { .reg_offset = (_off), .mask = BIT(_mask) }
|
||||
static const struct regmap_config axp288_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.wr_table = &axp288_writeable_table,
|
||||
.volatile_table = &axp288_volatile_table,
|
||||
.max_register = AXP288_FG_TUNE5,
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
};
|
||||
|
||||
#define INIT_REGMAP_IRQ(_variant, _irq, _off, _mask) \
|
||||
[_variant##_IRQ_##_irq] = { .reg_offset = (_off), .mask = BIT(_mask) }
|
||||
|
||||
static const struct regmap_irq axp20x_regmap_irqs[] = {
|
||||
AXP20X_IRQ(ACIN_OVER_V, 0, 7),
|
||||
AXP20X_IRQ(ACIN_PLUGIN, 0, 6),
|
||||
AXP20X_IRQ(ACIN_REMOVAL, 0, 5),
|
||||
AXP20X_IRQ(VBUS_OVER_V, 0, 4),
|
||||
AXP20X_IRQ(VBUS_PLUGIN, 0, 3),
|
||||
AXP20X_IRQ(VBUS_REMOVAL, 0, 2),
|
||||
AXP20X_IRQ(VBUS_V_LOW, 0, 1),
|
||||
AXP20X_IRQ(BATT_PLUGIN, 1, 7),
|
||||
AXP20X_IRQ(BATT_REMOVAL, 1, 6),
|
||||
AXP20X_IRQ(BATT_ENT_ACT_MODE, 1, 5),
|
||||
AXP20X_IRQ(BATT_EXIT_ACT_MODE, 1, 4),
|
||||
AXP20X_IRQ(CHARG, 1, 3),
|
||||
AXP20X_IRQ(CHARG_DONE, 1, 2),
|
||||
AXP20X_IRQ(BATT_TEMP_HIGH, 1, 1),
|
||||
AXP20X_IRQ(BATT_TEMP_LOW, 1, 0),
|
||||
AXP20X_IRQ(DIE_TEMP_HIGH, 2, 7),
|
||||
AXP20X_IRQ(CHARG_I_LOW, 2, 6),
|
||||
AXP20X_IRQ(DCDC1_V_LONG, 2, 5),
|
||||
AXP20X_IRQ(DCDC2_V_LONG, 2, 4),
|
||||
AXP20X_IRQ(DCDC3_V_LONG, 2, 3),
|
||||
AXP20X_IRQ(PEK_SHORT, 2, 1),
|
||||
AXP20X_IRQ(PEK_LONG, 2, 0),
|
||||
AXP20X_IRQ(N_OE_PWR_ON, 3, 7),
|
||||
AXP20X_IRQ(N_OE_PWR_OFF, 3, 6),
|
||||
AXP20X_IRQ(VBUS_VALID, 3, 5),
|
||||
AXP20X_IRQ(VBUS_NOT_VALID, 3, 4),
|
||||
AXP20X_IRQ(VBUS_SESS_VALID, 3, 3),
|
||||
AXP20X_IRQ(VBUS_SESS_END, 3, 2),
|
||||
AXP20X_IRQ(LOW_PWR_LVL1, 3, 1),
|
||||
AXP20X_IRQ(LOW_PWR_LVL2, 3, 0),
|
||||
AXP20X_IRQ(TIMER, 4, 7),
|
||||
AXP20X_IRQ(PEK_RIS_EDGE, 4, 6),
|
||||
AXP20X_IRQ(PEK_FAL_EDGE, 4, 5),
|
||||
AXP20X_IRQ(GPIO3_INPUT, 4, 3),
|
||||
AXP20X_IRQ(GPIO2_INPUT, 4, 2),
|
||||
AXP20X_IRQ(GPIO1_INPUT, 4, 1),
|
||||
AXP20X_IRQ(GPIO0_INPUT, 4, 0),
|
||||
INIT_REGMAP_IRQ(AXP20X, ACIN_OVER_V, 0, 7),
|
||||
INIT_REGMAP_IRQ(AXP20X, ACIN_PLUGIN, 0, 6),
|
||||
INIT_REGMAP_IRQ(AXP20X, ACIN_REMOVAL, 0, 5),
|
||||
INIT_REGMAP_IRQ(AXP20X, VBUS_OVER_V, 0, 4),
|
||||
INIT_REGMAP_IRQ(AXP20X, VBUS_PLUGIN, 0, 3),
|
||||
INIT_REGMAP_IRQ(AXP20X, VBUS_REMOVAL, 0, 2),
|
||||
INIT_REGMAP_IRQ(AXP20X, VBUS_V_LOW, 0, 1),
|
||||
INIT_REGMAP_IRQ(AXP20X, BATT_PLUGIN, 1, 7),
|
||||
INIT_REGMAP_IRQ(AXP20X, BATT_REMOVAL, 1, 6),
|
||||
INIT_REGMAP_IRQ(AXP20X, BATT_ENT_ACT_MODE, 1, 5),
|
||||
INIT_REGMAP_IRQ(AXP20X, BATT_EXIT_ACT_MODE, 1, 4),
|
||||
INIT_REGMAP_IRQ(AXP20X, CHARG, 1, 3),
|
||||
INIT_REGMAP_IRQ(AXP20X, CHARG_DONE, 1, 2),
|
||||
INIT_REGMAP_IRQ(AXP20X, BATT_TEMP_HIGH, 1, 1),
|
||||
INIT_REGMAP_IRQ(AXP20X, BATT_TEMP_LOW, 1, 0),
|
||||
INIT_REGMAP_IRQ(AXP20X, DIE_TEMP_HIGH, 2, 7),
|
||||
INIT_REGMAP_IRQ(AXP20X, CHARG_I_LOW, 2, 6),
|
||||
INIT_REGMAP_IRQ(AXP20X, DCDC1_V_LONG, 2, 5),
|
||||
INIT_REGMAP_IRQ(AXP20X, DCDC2_V_LONG, 2, 4),
|
||||
INIT_REGMAP_IRQ(AXP20X, DCDC3_V_LONG, 2, 3),
|
||||
INIT_REGMAP_IRQ(AXP20X, PEK_SHORT, 2, 1),
|
||||
INIT_REGMAP_IRQ(AXP20X, PEK_LONG, 2, 0),
|
||||
INIT_REGMAP_IRQ(AXP20X, N_OE_PWR_ON, 3, 7),
|
||||
INIT_REGMAP_IRQ(AXP20X, N_OE_PWR_OFF, 3, 6),
|
||||
INIT_REGMAP_IRQ(AXP20X, VBUS_VALID, 3, 5),
|
||||
INIT_REGMAP_IRQ(AXP20X, VBUS_NOT_VALID, 3, 4),
|
||||
INIT_REGMAP_IRQ(AXP20X, VBUS_SESS_VALID, 3, 3),
|
||||
INIT_REGMAP_IRQ(AXP20X, VBUS_SESS_END, 3, 2),
|
||||
INIT_REGMAP_IRQ(AXP20X, LOW_PWR_LVL1, 3, 1),
|
||||
INIT_REGMAP_IRQ(AXP20X, LOW_PWR_LVL2, 3, 0),
|
||||
INIT_REGMAP_IRQ(AXP20X, TIMER, 4, 7),
|
||||
INIT_REGMAP_IRQ(AXP20X, PEK_RIS_EDGE, 4, 6),
|
||||
INIT_REGMAP_IRQ(AXP20X, PEK_FAL_EDGE, 4, 5),
|
||||
INIT_REGMAP_IRQ(AXP20X, GPIO3_INPUT, 4, 3),
|
||||
INIT_REGMAP_IRQ(AXP20X, GPIO2_INPUT, 4, 2),
|
||||
INIT_REGMAP_IRQ(AXP20X, GPIO1_INPUT, 4, 1),
|
||||
INIT_REGMAP_IRQ(AXP20X, GPIO0_INPUT, 4, 0),
|
||||
};
|
||||
|
||||
/* some IRQs are compatible with axp20x models */
|
||||
static const struct regmap_irq axp288_regmap_irqs[] = {
|
||||
INIT_REGMAP_IRQ(AXP288, VBUS_FALL, 0, 2),
|
||||
INIT_REGMAP_IRQ(AXP288, VBUS_RISE, 0, 3),
|
||||
INIT_REGMAP_IRQ(AXP288, OV, 0, 4),
|
||||
|
||||
INIT_REGMAP_IRQ(AXP288, DONE, 1, 2),
|
||||
INIT_REGMAP_IRQ(AXP288, CHARGING, 1, 3),
|
||||
INIT_REGMAP_IRQ(AXP288, SAFE_QUIT, 1, 4),
|
||||
INIT_REGMAP_IRQ(AXP288, SAFE_ENTER, 1, 5),
|
||||
INIT_REGMAP_IRQ(AXP288, ABSENT, 1, 6),
|
||||
INIT_REGMAP_IRQ(AXP288, APPEND, 1, 7),
|
||||
|
||||
INIT_REGMAP_IRQ(AXP288, QWBTU, 2, 0),
|
||||
INIT_REGMAP_IRQ(AXP288, WBTU, 2, 1),
|
||||
INIT_REGMAP_IRQ(AXP288, QWBTO, 2, 2),
|
||||
INIT_REGMAP_IRQ(AXP288, WBTO, 2, 3),
|
||||
INIT_REGMAP_IRQ(AXP288, QCBTU, 2, 4),
|
||||
INIT_REGMAP_IRQ(AXP288, CBTU, 2, 5),
|
||||
INIT_REGMAP_IRQ(AXP288, QCBTO, 2, 6),
|
||||
INIT_REGMAP_IRQ(AXP288, CBTO, 2, 7),
|
||||
|
||||
INIT_REGMAP_IRQ(AXP288, WL2, 3, 0),
|
||||
INIT_REGMAP_IRQ(AXP288, WL1, 3, 1),
|
||||
INIT_REGMAP_IRQ(AXP288, GPADC, 3, 2),
|
||||
INIT_REGMAP_IRQ(AXP288, OT, 3, 7),
|
||||
|
||||
INIT_REGMAP_IRQ(AXP288, GPIO0, 4, 0),
|
||||
INIT_REGMAP_IRQ(AXP288, GPIO1, 4, 1),
|
||||
INIT_REGMAP_IRQ(AXP288, POKO, 4, 2),
|
||||
INIT_REGMAP_IRQ(AXP288, POKL, 4, 3),
|
||||
INIT_REGMAP_IRQ(AXP288, POKS, 4, 4),
|
||||
INIT_REGMAP_IRQ(AXP288, POKN, 4, 5),
|
||||
INIT_REGMAP_IRQ(AXP288, POKP, 4, 6),
|
||||
INIT_REGMAP_IRQ(AXP288, TIMER, 4, 7),
|
||||
|
||||
INIT_REGMAP_IRQ(AXP288, MV_CHNG, 5, 0),
|
||||
INIT_REGMAP_IRQ(AXP288, BC_USB_CHNG, 5, 1),
|
||||
};
|
||||
|
||||
static const struct of_device_id axp20x_of_match[] = {
|
||||
|
@ -128,16 +236,39 @@ static const struct i2c_device_id axp20x_i2c_id[] = {
|
|||
};
|
||||
MODULE_DEVICE_TABLE(i2c, axp20x_i2c_id);
|
||||
|
||||
static struct acpi_device_id axp20x_acpi_match[] = {
|
||||
{
|
||||
.id = "INT33F4",
|
||||
.driver_data = AXP288_ID,
|
||||
},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, axp20x_acpi_match);
|
||||
|
||||
static const struct regmap_irq_chip axp20x_regmap_irq_chip = {
|
||||
.name = "axp20x_irq_chip",
|
||||
.status_base = AXP20X_IRQ1_STATE,
|
||||
.ack_base = AXP20X_IRQ1_STATE,
|
||||
.mask_base = AXP20X_IRQ1_EN,
|
||||
.num_regs = 5,
|
||||
.irqs = axp20x_regmap_irqs,
|
||||
.num_irqs = ARRAY_SIZE(axp20x_regmap_irqs),
|
||||
.mask_invert = true,
|
||||
.init_ack_masked = true,
|
||||
.irqs = axp20x_regmap_irqs,
|
||||
.num_irqs = ARRAY_SIZE(axp20x_regmap_irqs),
|
||||
.num_regs = 5,
|
||||
|
||||
};
|
||||
|
||||
static const struct regmap_irq_chip axp288_regmap_irq_chip = {
|
||||
.name = "axp288_irq_chip",
|
||||
.status_base = AXP20X_IRQ1_STATE,
|
||||
.ack_base = AXP20X_IRQ1_STATE,
|
||||
.mask_base = AXP20X_IRQ1_EN,
|
||||
.mask_invert = true,
|
||||
.init_ack_masked = true,
|
||||
.irqs = axp288_regmap_irqs,
|
||||
.num_irqs = ARRAY_SIZE(axp288_regmap_irqs),
|
||||
.num_regs = 6,
|
||||
|
||||
};
|
||||
|
||||
static struct mfd_cell axp20x_cells[] = {
|
||||
|
@ -150,36 +281,155 @@ static struct mfd_cell axp20x_cells[] = {
|
|||
},
|
||||
};
|
||||
|
||||
static struct resource axp288_adc_resources[] = {
|
||||
{
|
||||
.name = "GPADC",
|
||||
.start = AXP288_IRQ_GPADC,
|
||||
.end = AXP288_IRQ_GPADC,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
};
|
||||
|
||||
static struct resource axp288_charger_resources[] = {
|
||||
{
|
||||
.start = AXP288_IRQ_OV,
|
||||
.end = AXP288_IRQ_OV,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
{
|
||||
.start = AXP288_IRQ_DONE,
|
||||
.end = AXP288_IRQ_DONE,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
{
|
||||
.start = AXP288_IRQ_CHARGING,
|
||||
.end = AXP288_IRQ_CHARGING,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
{
|
||||
.start = AXP288_IRQ_SAFE_QUIT,
|
||||
.end = AXP288_IRQ_SAFE_QUIT,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
{
|
||||
.start = AXP288_IRQ_SAFE_ENTER,
|
||||
.end = AXP288_IRQ_SAFE_ENTER,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
{
|
||||
.start = AXP288_IRQ_QCBTU,
|
||||
.end = AXP288_IRQ_QCBTU,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
{
|
||||
.start = AXP288_IRQ_CBTU,
|
||||
.end = AXP288_IRQ_CBTU,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
{
|
||||
.start = AXP288_IRQ_QCBTO,
|
||||
.end = AXP288_IRQ_QCBTO,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
{
|
||||
.start = AXP288_IRQ_CBTO,
|
||||
.end = AXP288_IRQ_CBTO,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
};
|
||||
|
||||
static struct mfd_cell axp288_cells[] = {
|
||||
{
|
||||
.name = "axp288_adc",
|
||||
.num_resources = ARRAY_SIZE(axp288_adc_resources),
|
||||
.resources = axp288_adc_resources,
|
||||
},
|
||||
{
|
||||
.name = "axp288_charger",
|
||||
.num_resources = ARRAY_SIZE(axp288_charger_resources),
|
||||
.resources = axp288_charger_resources,
|
||||
},
|
||||
{
|
||||
.name = "axp288_battery",
|
||||
.num_resources = ARRAY_SIZE(axp288_battery_resources),
|
||||
.resources = axp288_battery_resources,
|
||||
},
|
||||
};
|
||||
|
||||
static struct axp20x_dev *axp20x_pm_power_off;
|
||||
static void axp20x_power_off(void)
|
||||
{
|
||||
if (axp20x_pm_power_off->variant == AXP288_ID)
|
||||
return;
|
||||
|
||||
regmap_write(axp20x_pm_power_off->regmap, AXP20X_OFF_CTRL,
|
||||
AXP20X_OFF);
|
||||
}
|
||||
|
||||
static int axp20x_match_device(struct axp20x_dev *axp20x, struct device *dev)
|
||||
{
|
||||
const struct acpi_device_id *acpi_id;
|
||||
const struct of_device_id *of_id;
|
||||
|
||||
if (dev->of_node) {
|
||||
of_id = of_match_device(axp20x_of_match, dev);
|
||||
if (!of_id) {
|
||||
dev_err(dev, "Unable to match OF ID\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
axp20x->variant = (long) of_id->data;
|
||||
} else {
|
||||
acpi_id = acpi_match_device(dev->driver->acpi_match_table, dev);
|
||||
if (!acpi_id || !acpi_id->driver_data) {
|
||||
dev_err(dev, "Unable to match ACPI ID and data\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
axp20x->variant = (long) acpi_id->driver_data;
|
||||
}
|
||||
|
||||
switch (axp20x->variant) {
|
||||
case AXP202_ID:
|
||||
case AXP209_ID:
|
||||
axp20x->nr_cells = ARRAY_SIZE(axp20x_cells);
|
||||
axp20x->cells = axp20x_cells;
|
||||
axp20x->regmap_cfg = &axp20x_regmap_config;
|
||||
axp20x->regmap_irq_chip = &axp20x_regmap_irq_chip;
|
||||
break;
|
||||
case AXP288_ID:
|
||||
axp20x->cells = axp288_cells;
|
||||
axp20x->nr_cells = ARRAY_SIZE(axp288_cells);
|
||||
axp20x->regmap_cfg = &axp288_regmap_config;
|
||||
axp20x->regmap_irq_chip = &axp288_regmap_irq_chip;
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "unsupported AXP20X ID %lu\n", axp20x->variant);
|
||||
return -EINVAL;
|
||||
}
|
||||
dev_info(dev, "AXP20x variant %s found\n",
|
||||
axp20x_model_names[axp20x->variant]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int axp20x_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct axp20x_dev *axp20x;
|
||||
const struct of_device_id *of_id;
|
||||
int ret;
|
||||
|
||||
axp20x = devm_kzalloc(&i2c->dev, sizeof(*axp20x), GFP_KERNEL);
|
||||
if (!axp20x)
|
||||
return -ENOMEM;
|
||||
|
||||
of_id = of_match_device(axp20x_of_match, &i2c->dev);
|
||||
if (!of_id) {
|
||||
dev_err(&i2c->dev, "Unable to setup AXP20X data\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
axp20x->variant = (long) of_id->data;
|
||||
ret = axp20x_match_device(axp20x, &i2c->dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
axp20x->i2c_client = i2c;
|
||||
axp20x->dev = &i2c->dev;
|
||||
dev_set_drvdata(axp20x->dev, axp20x);
|
||||
|
||||
axp20x->regmap = devm_regmap_init_i2c(i2c, &axp20x_regmap_config);
|
||||
axp20x->regmap = devm_regmap_init_i2c(i2c, axp20x->regmap_cfg);
|
||||
if (IS_ERR(axp20x->regmap)) {
|
||||
ret = PTR_ERR(axp20x->regmap);
|
||||
dev_err(&i2c->dev, "regmap init failed: %d\n", ret);
|
||||
|
@ -188,15 +438,15 @@ static int axp20x_i2c_probe(struct i2c_client *i2c,
|
|||
|
||||
ret = regmap_add_irq_chip(axp20x->regmap, i2c->irq,
|
||||
IRQF_ONESHOT | IRQF_SHARED, -1,
|
||||
&axp20x_regmap_irq_chip,
|
||||
axp20x->regmap_irq_chip,
|
||||
&axp20x->regmap_irqc);
|
||||
if (ret) {
|
||||
dev_err(&i2c->dev, "failed to add irq chip: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = mfd_add_devices(axp20x->dev, -1, axp20x_cells,
|
||||
ARRAY_SIZE(axp20x_cells), NULL, 0, NULL);
|
||||
ret = mfd_add_devices(axp20x->dev, -1, axp20x->cells,
|
||||
axp20x->nr_cells, NULL, 0, NULL);
|
||||
|
||||
if (ret) {
|
||||
dev_err(&i2c->dev, "failed to add MFD devices: %d\n", ret);
|
||||
|
@ -234,6 +484,7 @@ static struct i2c_driver axp20x_i2c_driver = {
|
|||
.name = "axp20x",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(axp20x_of_match),
|
||||
.acpi_match_table = ACPI_PTR(axp20x_acpi_match),
|
||||
},
|
||||
.probe = axp20x_i2c_probe,
|
||||
.remove = axp20x_i2c_remove,
|
||||
|
|
|
@ -0,0 +1,769 @@
|
|||
/*
|
||||
* Driver for the Diolan DLN-2 USB adapter
|
||||
*
|
||||
* Copyright (c) 2014 Intel Corporation
|
||||
*
|
||||
* Derived from:
|
||||
* i2c-diolan-u2c.c
|
||||
* Copyright (c) 2010-2011 Ericsson AB
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/dln2.h>
|
||||
#include <linux/rculist.h>
|
||||
|
||||
struct dln2_header {
|
||||
__le16 size;
|
||||
__le16 id;
|
||||
__le16 echo;
|
||||
__le16 handle;
|
||||
};
|
||||
|
||||
struct dln2_response {
|
||||
struct dln2_header hdr;
|
||||
__le16 result;
|
||||
};
|
||||
|
||||
#define DLN2_GENERIC_MODULE_ID 0x00
|
||||
#define DLN2_GENERIC_CMD(cmd) DLN2_CMD(cmd, DLN2_GENERIC_MODULE_ID)
|
||||
#define CMD_GET_DEVICE_VER DLN2_GENERIC_CMD(0x30)
|
||||
#define CMD_GET_DEVICE_SN DLN2_GENERIC_CMD(0x31)
|
||||
|
||||
#define DLN2_HW_ID 0x200
|
||||
#define DLN2_USB_TIMEOUT 200 /* in ms */
|
||||
#define DLN2_MAX_RX_SLOTS 16
|
||||
#define DLN2_MAX_URBS 16
|
||||
#define DLN2_RX_BUF_SIZE 512
|
||||
|
||||
enum dln2_handle {
|
||||
DLN2_HANDLE_EVENT = 0, /* don't change, hardware defined */
|
||||
DLN2_HANDLE_CTRL,
|
||||
DLN2_HANDLE_GPIO,
|
||||
DLN2_HANDLE_I2C,
|
||||
DLN2_HANDLES
|
||||
};
|
||||
|
||||
/*
|
||||
* Receive context used between the receive demultiplexer and the transfer
|
||||
* routine. While sending a request the transfer routine will look for a free
|
||||
* receive context and use it to wait for a response and to receive the URB and
|
||||
* thus the response data.
|
||||
*/
|
||||
struct dln2_rx_context {
|
||||
/* completion used to wait for a response */
|
||||
struct completion done;
|
||||
|
||||
/* if non-NULL the URB contains the response */
|
||||
struct urb *urb;
|
||||
|
||||
/* if true then this context is used to wait for a response */
|
||||
bool in_use;
|
||||
};
|
||||
|
||||
/*
|
||||
* Receive contexts for a particular DLN2 module (i2c, gpio, etc.). We use the
|
||||
* handle header field to identify the module in dln2_dev.mod_rx_slots and then
|
||||
* the echo header field to index the slots field and find the receive context
|
||||
* for a particular request.
|
||||
*/
|
||||
struct dln2_mod_rx_slots {
|
||||
/* RX slots bitmap */
|
||||
DECLARE_BITMAP(bmap, DLN2_MAX_RX_SLOTS);
|
||||
|
||||
/* used to wait for a free RX slot */
|
||||
wait_queue_head_t wq;
|
||||
|
||||
/* used to wait for an RX operation to complete */
|
||||
struct dln2_rx_context slots[DLN2_MAX_RX_SLOTS];
|
||||
|
||||
/* avoid races between alloc/free_rx_slot and dln2_rx_transfer */
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
struct dln2_dev {
|
||||
struct usb_device *usb_dev;
|
||||
struct usb_interface *interface;
|
||||
u8 ep_in;
|
||||
u8 ep_out;
|
||||
|
||||
struct urb *rx_urb[DLN2_MAX_URBS];
|
||||
void *rx_buf[DLN2_MAX_URBS];
|
||||
|
||||
struct dln2_mod_rx_slots mod_rx_slots[DLN2_HANDLES];
|
||||
|
||||
struct list_head event_cb_list;
|
||||
spinlock_t event_cb_lock;
|
||||
|
||||
bool disconnect;
|
||||
int active_transfers;
|
||||
wait_queue_head_t disconnect_wq;
|
||||
spinlock_t disconnect_lock;
|
||||
};
|
||||
|
||||
struct dln2_event_cb_entry {
|
||||
struct list_head list;
|
||||
u16 id;
|
||||
struct platform_device *pdev;
|
||||
dln2_event_cb_t callback;
|
||||
};
|
||||
|
||||
int dln2_register_event_cb(struct platform_device *pdev, u16 id,
|
||||
dln2_event_cb_t event_cb)
|
||||
{
|
||||
struct dln2_dev *dln2 = dev_get_drvdata(pdev->dev.parent);
|
||||
struct dln2_event_cb_entry *i, *entry;
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
|
||||
if (!entry)
|
||||
return -ENOMEM;
|
||||
|
||||
entry->id = id;
|
||||
entry->callback = event_cb;
|
||||
entry->pdev = pdev;
|
||||
|
||||
spin_lock_irqsave(&dln2->event_cb_lock, flags);
|
||||
|
||||
list_for_each_entry(i, &dln2->event_cb_list, list) {
|
||||
if (i->id == id) {
|
||||
ret = -EBUSY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ret)
|
||||
list_add_rcu(&entry->list, &dln2->event_cb_list);
|
||||
|
||||
spin_unlock_irqrestore(&dln2->event_cb_lock, flags);
|
||||
|
||||
if (ret)
|
||||
kfree(entry);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(dln2_register_event_cb);
|
||||
|
||||
void dln2_unregister_event_cb(struct platform_device *pdev, u16 id)
|
||||
{
|
||||
struct dln2_dev *dln2 = dev_get_drvdata(pdev->dev.parent);
|
||||
struct dln2_event_cb_entry *i;
|
||||
unsigned long flags;
|
||||
bool found = false;
|
||||
|
||||
spin_lock_irqsave(&dln2->event_cb_lock, flags);
|
||||
|
||||
list_for_each_entry(i, &dln2->event_cb_list, list) {
|
||||
if (i->id == id) {
|
||||
list_del_rcu(&i->list);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&dln2->event_cb_lock, flags);
|
||||
|
||||
if (found) {
|
||||
synchronize_rcu();
|
||||
kfree(i);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(dln2_unregister_event_cb);
|
||||
|
||||
/*
|
||||
* Returns true if a valid transfer slot is found. In this case the URB must not
|
||||
* be resubmitted immediately in dln2_rx as we need the data when dln2_transfer
|
||||
* is woke up. It will be resubmitted there.
|
||||
*/
|
||||
static bool dln2_transfer_complete(struct dln2_dev *dln2, struct urb *urb,
|
||||
u16 handle, u16 rx_slot)
|
||||
{
|
||||
struct device *dev = &dln2->interface->dev;
|
||||
struct dln2_mod_rx_slots *rxs = &dln2->mod_rx_slots[handle];
|
||||
struct dln2_rx_context *rxc;
|
||||
bool valid_slot = false;
|
||||
|
||||
if (rx_slot >= DLN2_MAX_RX_SLOTS)
|
||||
goto out;
|
||||
|
||||
rxc = &rxs->slots[rx_slot];
|
||||
|
||||
/*
|
||||
* No need to disable interrupts as this lock is not taken in interrupt
|
||||
* context elsewhere in this driver. This function (or its callers) are
|
||||
* also not exported to other modules.
|
||||
*/
|
||||
spin_lock(&rxs->lock);
|
||||
if (rxc->in_use && !rxc->urb) {
|
||||
rxc->urb = urb;
|
||||
complete(&rxc->done);
|
||||
valid_slot = true;
|
||||
}
|
||||
spin_unlock(&rxs->lock);
|
||||
|
||||
out:
|
||||
if (!valid_slot)
|
||||
dev_warn(dev, "bad/late response %d/%d\n", handle, rx_slot);
|
||||
|
||||
return valid_slot;
|
||||
}
|
||||
|
||||
static void dln2_run_event_callbacks(struct dln2_dev *dln2, u16 id, u16 echo,
|
||||
void *data, int len)
|
||||
{
|
||||
struct dln2_event_cb_entry *i;
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
list_for_each_entry_rcu(i, &dln2->event_cb_list, list) {
|
||||
if (i->id == id) {
|
||||
i->callback(i->pdev, echo, data, len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
static void dln2_rx(struct urb *urb)
|
||||
{
|
||||
struct dln2_dev *dln2 = urb->context;
|
||||
struct dln2_header *hdr = urb->transfer_buffer;
|
||||
struct device *dev = &dln2->interface->dev;
|
||||
u16 id, echo, handle, size;
|
||||
u8 *data;
|
||||
int len;
|
||||
int err;
|
||||
|
||||
switch (urb->status) {
|
||||
case 0:
|
||||
/* success */
|
||||
break;
|
||||
case -ECONNRESET:
|
||||
case -ENOENT:
|
||||
case -ESHUTDOWN:
|
||||
case -EPIPE:
|
||||
/* this urb is terminated, clean up */
|
||||
dev_dbg(dev, "urb shutting down with status %d\n", urb->status);
|
||||
return;
|
||||
default:
|
||||
dev_dbg(dev, "nonzero urb status received %d\n", urb->status);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (urb->actual_length < sizeof(struct dln2_header)) {
|
||||
dev_err(dev, "short response: %d\n", urb->actual_length);
|
||||
goto out;
|
||||
}
|
||||
|
||||
handle = le16_to_cpu(hdr->handle);
|
||||
id = le16_to_cpu(hdr->id);
|
||||
echo = le16_to_cpu(hdr->echo);
|
||||
size = le16_to_cpu(hdr->size);
|
||||
|
||||
if (size != urb->actual_length) {
|
||||
dev_err(dev, "size mismatch: handle %x cmd %x echo %x size %d actual %d\n",
|
||||
handle, id, echo, size, urb->actual_length);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (handle >= DLN2_HANDLES) {
|
||||
dev_warn(dev, "invalid handle %d\n", handle);
|
||||
goto out;
|
||||
}
|
||||
|
||||
data = urb->transfer_buffer + sizeof(struct dln2_header);
|
||||
len = urb->actual_length - sizeof(struct dln2_header);
|
||||
|
||||
if (handle == DLN2_HANDLE_EVENT) {
|
||||
dln2_run_event_callbacks(dln2, id, echo, data, len);
|
||||
} else {
|
||||
/* URB will be re-submitted in _dln2_transfer (free_rx_slot) */
|
||||
if (dln2_transfer_complete(dln2, urb, handle, echo))
|
||||
return;
|
||||
}
|
||||
|
||||
out:
|
||||
err = usb_submit_urb(urb, GFP_ATOMIC);
|
||||
if (err < 0)
|
||||
dev_err(dev, "failed to resubmit RX URB: %d\n", err);
|
||||
}
|
||||
|
||||
static void *dln2_prep_buf(u16 handle, u16 cmd, u16 echo, const void *obuf,
|
||||
int *obuf_len, gfp_t gfp)
|
||||
{
|
||||
int len;
|
||||
void *buf;
|
||||
struct dln2_header *hdr;
|
||||
|
||||
len = *obuf_len + sizeof(*hdr);
|
||||
buf = kmalloc(len, gfp);
|
||||
if (!buf)
|
||||
return NULL;
|
||||
|
||||
hdr = (struct dln2_header *)buf;
|
||||
hdr->id = cpu_to_le16(cmd);
|
||||
hdr->size = cpu_to_le16(len);
|
||||
hdr->echo = cpu_to_le16(echo);
|
||||
hdr->handle = cpu_to_le16(handle);
|
||||
|
||||
memcpy(buf + sizeof(*hdr), obuf, *obuf_len);
|
||||
|
||||
*obuf_len = len;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int dln2_send_wait(struct dln2_dev *dln2, u16 handle, u16 cmd, u16 echo,
|
||||
const void *obuf, int obuf_len)
|
||||
{
|
||||
int ret = 0;
|
||||
int len = obuf_len;
|
||||
void *buf;
|
||||
int actual;
|
||||
|
||||
buf = dln2_prep_buf(handle, cmd, echo, obuf, &len, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = usb_bulk_msg(dln2->usb_dev,
|
||||
usb_sndbulkpipe(dln2->usb_dev, dln2->ep_out),
|
||||
buf, len, &actual, DLN2_USB_TIMEOUT);
|
||||
|
||||
kfree(buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool find_free_slot(struct dln2_dev *dln2, u16 handle, int *slot)
|
||||
{
|
||||
struct dln2_mod_rx_slots *rxs;
|
||||
unsigned long flags;
|
||||
|
||||
if (dln2->disconnect) {
|
||||
*slot = -ENODEV;
|
||||
return true;
|
||||
}
|
||||
|
||||
rxs = &dln2->mod_rx_slots[handle];
|
||||
|
||||
spin_lock_irqsave(&rxs->lock, flags);
|
||||
|
||||
*slot = find_first_zero_bit(rxs->bmap, DLN2_MAX_RX_SLOTS);
|
||||
|
||||
if (*slot < DLN2_MAX_RX_SLOTS) {
|
||||
struct dln2_rx_context *rxc = &rxs->slots[*slot];
|
||||
|
||||
set_bit(*slot, rxs->bmap);
|
||||
rxc->in_use = true;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&rxs->lock, flags);
|
||||
|
||||
return *slot < DLN2_MAX_RX_SLOTS;
|
||||
}
|
||||
|
||||
static int alloc_rx_slot(struct dln2_dev *dln2, u16 handle)
|
||||
{
|
||||
int ret;
|
||||
int slot;
|
||||
|
||||
/*
|
||||
* No need to timeout here, the wait is bounded by the timeout in
|
||||
* _dln2_transfer.
|
||||
*/
|
||||
ret = wait_event_interruptible(dln2->mod_rx_slots[handle].wq,
|
||||
find_free_slot(dln2, handle, &slot));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
static void free_rx_slot(struct dln2_dev *dln2, u16 handle, int slot)
|
||||
{
|
||||
struct dln2_mod_rx_slots *rxs;
|
||||
struct urb *urb = NULL;
|
||||
unsigned long flags;
|
||||
struct dln2_rx_context *rxc;
|
||||
|
||||
rxs = &dln2->mod_rx_slots[handle];
|
||||
|
||||
spin_lock_irqsave(&rxs->lock, flags);
|
||||
|
||||
clear_bit(slot, rxs->bmap);
|
||||
|
||||
rxc = &rxs->slots[slot];
|
||||
rxc->in_use = false;
|
||||
urb = rxc->urb;
|
||||
rxc->urb = NULL;
|
||||
reinit_completion(&rxc->done);
|
||||
|
||||
spin_unlock_irqrestore(&rxs->lock, flags);
|
||||
|
||||
if (urb) {
|
||||
int err;
|
||||
struct device *dev = &dln2->interface->dev;
|
||||
|
||||
err = usb_submit_urb(urb, GFP_KERNEL);
|
||||
if (err < 0)
|
||||
dev_err(dev, "failed to resubmit RX URB: %d\n", err);
|
||||
}
|
||||
|
||||
wake_up_interruptible(&rxs->wq);
|
||||
}
|
||||
|
||||
static int _dln2_transfer(struct dln2_dev *dln2, u16 handle, u16 cmd,
|
||||
const void *obuf, unsigned obuf_len,
|
||||
void *ibuf, unsigned *ibuf_len)
|
||||
{
|
||||
int ret = 0;
|
||||
int rx_slot;
|
||||
struct dln2_response *rsp;
|
||||
struct dln2_rx_context *rxc;
|
||||
struct device *dev = &dln2->interface->dev;
|
||||
const unsigned long timeout = DLN2_USB_TIMEOUT * HZ / 1000;
|
||||
struct dln2_mod_rx_slots *rxs = &dln2->mod_rx_slots[handle];
|
||||
int size;
|
||||
|
||||
spin_lock(&dln2->disconnect_lock);
|
||||
if (!dln2->disconnect)
|
||||
dln2->active_transfers++;
|
||||
else
|
||||
ret = -ENODEV;
|
||||
spin_unlock(&dln2->disconnect_lock);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
rx_slot = alloc_rx_slot(dln2, handle);
|
||||
if (rx_slot < 0) {
|
||||
ret = rx_slot;
|
||||
goto out_decr;
|
||||
}
|
||||
|
||||
ret = dln2_send_wait(dln2, handle, cmd, rx_slot, obuf, obuf_len);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "USB write failed: %d\n", ret);
|
||||
goto out_free_rx_slot;
|
||||
}
|
||||
|
||||
rxc = &rxs->slots[rx_slot];
|
||||
|
||||
ret = wait_for_completion_interruptible_timeout(&rxc->done, timeout);
|
||||
if (ret <= 0) {
|
||||
if (!ret)
|
||||
ret = -ETIMEDOUT;
|
||||
goto out_free_rx_slot;
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
if (dln2->disconnect) {
|
||||
ret = -ENODEV;
|
||||
goto out_free_rx_slot;
|
||||
}
|
||||
|
||||
/* if we got here we know that the response header has been checked */
|
||||
rsp = rxc->urb->transfer_buffer;
|
||||
size = le16_to_cpu(rsp->hdr.size);
|
||||
|
||||
if (size < sizeof(*rsp)) {
|
||||
ret = -EPROTO;
|
||||
goto out_free_rx_slot;
|
||||
}
|
||||
|
||||
if (le16_to_cpu(rsp->result) > 0x80) {
|
||||
dev_dbg(dev, "%d received response with error %d\n",
|
||||
handle, le16_to_cpu(rsp->result));
|
||||
ret = -EREMOTEIO;
|
||||
goto out_free_rx_slot;
|
||||
}
|
||||
|
||||
if (!ibuf)
|
||||
goto out_free_rx_slot;
|
||||
|
||||
if (*ibuf_len > size - sizeof(*rsp))
|
||||
*ibuf_len = size - sizeof(*rsp);
|
||||
|
||||
memcpy(ibuf, rsp + 1, *ibuf_len);
|
||||
|
||||
out_free_rx_slot:
|
||||
free_rx_slot(dln2, handle, rx_slot);
|
||||
out_decr:
|
||||
spin_lock(&dln2->disconnect_lock);
|
||||
dln2->active_transfers--;
|
||||
spin_unlock(&dln2->disconnect_lock);
|
||||
if (dln2->disconnect)
|
||||
wake_up(&dln2->disconnect_wq);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int dln2_transfer(struct platform_device *pdev, u16 cmd,
|
||||
const void *obuf, unsigned obuf_len,
|
||||
void *ibuf, unsigned *ibuf_len)
|
||||
{
|
||||
struct dln2_platform_data *dln2_pdata;
|
||||
struct dln2_dev *dln2;
|
||||
u16 handle;
|
||||
|
||||
dln2 = dev_get_drvdata(pdev->dev.parent);
|
||||
dln2_pdata = dev_get_platdata(&pdev->dev);
|
||||
handle = dln2_pdata->handle;
|
||||
|
||||
return _dln2_transfer(dln2, handle, cmd, obuf, obuf_len, ibuf,
|
||||
ibuf_len);
|
||||
}
|
||||
EXPORT_SYMBOL(dln2_transfer);
|
||||
|
||||
static int dln2_check_hw(struct dln2_dev *dln2)
|
||||
{
|
||||
int ret;
|
||||
__le32 hw_type;
|
||||
int len = sizeof(hw_type);
|
||||
|
||||
ret = _dln2_transfer(dln2, DLN2_HANDLE_CTRL, CMD_GET_DEVICE_VER,
|
||||
NULL, 0, &hw_type, &len);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (len < sizeof(hw_type))
|
||||
return -EREMOTEIO;
|
||||
|
||||
if (le32_to_cpu(hw_type) != DLN2_HW_ID) {
|
||||
dev_err(&dln2->interface->dev, "Device ID 0x%x not supported\n",
|
||||
le32_to_cpu(hw_type));
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dln2_print_serialno(struct dln2_dev *dln2)
|
||||
{
|
||||
int ret;
|
||||
__le32 serial_no;
|
||||
int len = sizeof(serial_no);
|
||||
struct device *dev = &dln2->interface->dev;
|
||||
|
||||
ret = _dln2_transfer(dln2, DLN2_HANDLE_CTRL, CMD_GET_DEVICE_SN, NULL, 0,
|
||||
&serial_no, &len);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (len < sizeof(serial_no))
|
||||
return -EREMOTEIO;
|
||||
|
||||
dev_info(dev, "Diolan DLN2 serial %u\n", le32_to_cpu(serial_no));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dln2_hw_init(struct dln2_dev *dln2)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = dln2_check_hw(dln2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return dln2_print_serialno(dln2);
|
||||
}
|
||||
|
||||
static void dln2_free_rx_urbs(struct dln2_dev *dln2)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < DLN2_MAX_URBS; i++) {
|
||||
usb_kill_urb(dln2->rx_urb[i]);
|
||||
usb_free_urb(dln2->rx_urb[i]);
|
||||
kfree(dln2->rx_buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void dln2_free(struct dln2_dev *dln2)
|
||||
{
|
||||
dln2_free_rx_urbs(dln2);
|
||||
usb_put_dev(dln2->usb_dev);
|
||||
kfree(dln2);
|
||||
}
|
||||
|
||||
static int dln2_setup_rx_urbs(struct dln2_dev *dln2,
|
||||
struct usb_host_interface *hostif)
|
||||
{
|
||||
int i;
|
||||
int ret;
|
||||
const int rx_max_size = DLN2_RX_BUF_SIZE;
|
||||
struct device *dev = &dln2->interface->dev;
|
||||
|
||||
for (i = 0; i < DLN2_MAX_URBS; i++) {
|
||||
dln2->rx_buf[i] = kmalloc(rx_max_size, GFP_KERNEL);
|
||||
if (!dln2->rx_buf[i])
|
||||
return -ENOMEM;
|
||||
|
||||
dln2->rx_urb[i] = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (!dln2->rx_urb[i])
|
||||
return -ENOMEM;
|
||||
|
||||
usb_fill_bulk_urb(dln2->rx_urb[i], dln2->usb_dev,
|
||||
usb_rcvbulkpipe(dln2->usb_dev, dln2->ep_in),
|
||||
dln2->rx_buf[i], rx_max_size, dln2_rx, dln2);
|
||||
|
||||
ret = usb_submit_urb(dln2->rx_urb[i], GFP_KERNEL);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to submit RX URB: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct dln2_platform_data dln2_pdata_gpio = {
|
||||
.handle = DLN2_HANDLE_GPIO,
|
||||
};
|
||||
|
||||
/* Only one I2C port seems to be supported on current hardware */
|
||||
static struct dln2_platform_data dln2_pdata_i2c = {
|
||||
.handle = DLN2_HANDLE_I2C,
|
||||
.port = 0,
|
||||
};
|
||||
|
||||
static const struct mfd_cell dln2_devs[] = {
|
||||
{
|
||||
.name = "dln2-gpio",
|
||||
.platform_data = &dln2_pdata_gpio,
|
||||
.pdata_size = sizeof(struct dln2_platform_data),
|
||||
},
|
||||
{
|
||||
.name = "dln2-i2c",
|
||||
.platform_data = &dln2_pdata_i2c,
|
||||
.pdata_size = sizeof(struct dln2_platform_data),
|
||||
},
|
||||
};
|
||||
|
||||
static void dln2_disconnect(struct usb_interface *interface)
|
||||
{
|
||||
struct dln2_dev *dln2 = usb_get_intfdata(interface);
|
||||
int i, j;
|
||||
|
||||
/* don't allow starting new transfers */
|
||||
spin_lock(&dln2->disconnect_lock);
|
||||
dln2->disconnect = true;
|
||||
spin_unlock(&dln2->disconnect_lock);
|
||||
|
||||
/* cancel in progress transfers */
|
||||
for (i = 0; i < DLN2_HANDLES; i++) {
|
||||
struct dln2_mod_rx_slots *rxs = &dln2->mod_rx_slots[i];
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&rxs->lock, flags);
|
||||
|
||||
/* cancel all response waiters */
|
||||
for (j = 0; j < DLN2_MAX_RX_SLOTS; j++) {
|
||||
struct dln2_rx_context *rxc = &rxs->slots[j];
|
||||
|
||||
if (rxc->in_use)
|
||||
complete(&rxc->done);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&rxs->lock, flags);
|
||||
}
|
||||
|
||||
/* wait for transfers to end */
|
||||
wait_event(dln2->disconnect_wq, !dln2->active_transfers);
|
||||
|
||||
mfd_remove_devices(&interface->dev);
|
||||
|
||||
dln2_free(dln2);
|
||||
}
|
||||
|
||||
static int dln2_probe(struct usb_interface *interface,
|
||||
const struct usb_device_id *usb_id)
|
||||
{
|
||||
struct usb_host_interface *hostif = interface->cur_altsetting;
|
||||
struct device *dev = &interface->dev;
|
||||
struct dln2_dev *dln2;
|
||||
int ret;
|
||||
int i, j;
|
||||
|
||||
if (hostif->desc.bInterfaceNumber != 0 ||
|
||||
hostif->desc.bNumEndpoints < 2)
|
||||
return -ENODEV;
|
||||
|
||||
dln2 = kzalloc(sizeof(*dln2), GFP_KERNEL);
|
||||
if (!dln2)
|
||||
return -ENOMEM;
|
||||
|
||||
dln2->ep_out = hostif->endpoint[0].desc.bEndpointAddress;
|
||||
dln2->ep_in = hostif->endpoint[1].desc.bEndpointAddress;
|
||||
dln2->usb_dev = usb_get_dev(interface_to_usbdev(interface));
|
||||
dln2->interface = interface;
|
||||
usb_set_intfdata(interface, dln2);
|
||||
init_waitqueue_head(&dln2->disconnect_wq);
|
||||
|
||||
for (i = 0; i < DLN2_HANDLES; i++) {
|
||||
init_waitqueue_head(&dln2->mod_rx_slots[i].wq);
|
||||
spin_lock_init(&dln2->mod_rx_slots[i].lock);
|
||||
for (j = 0; j < DLN2_MAX_RX_SLOTS; j++)
|
||||
init_completion(&dln2->mod_rx_slots[i].slots[j].done);
|
||||
}
|
||||
|
||||
spin_lock_init(&dln2->event_cb_lock);
|
||||
spin_lock_init(&dln2->disconnect_lock);
|
||||
INIT_LIST_HEAD(&dln2->event_cb_list);
|
||||
|
||||
ret = dln2_setup_rx_urbs(dln2, hostif);
|
||||
if (ret)
|
||||
goto out_cleanup;
|
||||
|
||||
ret = dln2_hw_init(dln2);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to initialize hardware\n");
|
||||
goto out_cleanup;
|
||||
}
|
||||
|
||||
ret = mfd_add_hotplug_devices(dev, dln2_devs, ARRAY_SIZE(dln2_devs));
|
||||
if (ret != 0) {
|
||||
dev_err(dev, "failed to add mfd devices to core\n");
|
||||
goto out_cleanup;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_cleanup:
|
||||
dln2_free(dln2);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct usb_device_id dln2_table[] = {
|
||||
{ USB_DEVICE(0xa257, 0x2013) },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(usb, dln2_table);
|
||||
|
||||
static struct usb_driver dln2_driver = {
|
||||
.name = "dln2",
|
||||
.probe = dln2_probe,
|
||||
.disconnect = dln2_disconnect,
|
||||
.id_table = dln2_table,
|
||||
};
|
||||
|
||||
module_usb_driver(dln2_driver);
|
||||
|
||||
MODULE_AUTHOR("Octavian Purdila <octavian.purdila@intel.com>");
|
||||
MODULE_DESCRIPTION("Core driver for the Diolan DLN2 interface adapter");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -27,6 +27,7 @@
|
|||
#include <linux/mfd/samsung/irq.h>
|
||||
#include <linux/mfd/samsung/s2mpa01.h>
|
||||
#include <linux/mfd/samsung/s2mps11.h>
|
||||
#include <linux/mfd/samsung/s2mps13.h>
|
||||
#include <linux/mfd/samsung/s2mps14.h>
|
||||
#include <linux/mfd/samsung/s2mpu02.h>
|
||||
#include <linux/mfd/samsung/s5m8763.h>
|
||||
|
@ -74,6 +75,15 @@ static const struct mfd_cell s2mps11_devs[] = {
|
|||
}
|
||||
};
|
||||
|
||||
static const struct mfd_cell s2mps13_devs[] = {
|
||||
{ .name = "s2mps13-pmic", },
|
||||
{ .name = "s2mps13-rtc", },
|
||||
{
|
||||
.name = "s2mps13-clk",
|
||||
.of_compatible = "samsung,s2mps13-clk",
|
||||
},
|
||||
};
|
||||
|
||||
static const struct mfd_cell s2mps14_devs[] = {
|
||||
{
|
||||
.name = "s2mps14-pmic",
|
||||
|
@ -107,6 +117,9 @@ static const struct of_device_id sec_dt_match[] = {
|
|||
}, {
|
||||
.compatible = "samsung,s2mps11-pmic",
|
||||
.data = (void *)S2MPS11X,
|
||||
}, {
|
||||
.compatible = "samsung,s2mps13-pmic",
|
||||
.data = (void *)S2MPS13X,
|
||||
}, {
|
||||
.compatible = "samsung,s2mps14-pmic",
|
||||
.data = (void *)S2MPS14X,
|
||||
|
@ -194,6 +207,15 @@ static const struct regmap_config s2mps11_regmap_config = {
|
|||
.cache_type = REGCACHE_FLAT,
|
||||
};
|
||||
|
||||
static const struct regmap_config s2mps13_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
|
||||
.max_register = S2MPS13_REG_LDODSCH5,
|
||||
.volatile_reg = s2mps11_volatile,
|
||||
.cache_type = REGCACHE_FLAT,
|
||||
};
|
||||
|
||||
static const struct regmap_config s2mps14_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
|
@ -325,6 +347,9 @@ static int sec_pmic_probe(struct i2c_client *i2c,
|
|||
case S2MPS11X:
|
||||
regmap = &s2mps11_regmap_config;
|
||||
break;
|
||||
case S2MPS13X:
|
||||
regmap = &s2mps13_regmap_config;
|
||||
break;
|
||||
case S2MPS14X:
|
||||
regmap = &s2mps14_regmap_config;
|
||||
break;
|
||||
|
@ -378,6 +403,10 @@ static int sec_pmic_probe(struct i2c_client *i2c,
|
|||
sec_devs = s2mps11_devs;
|
||||
num_sec_devs = ARRAY_SIZE(s2mps11_devs);
|
||||
break;
|
||||
case S2MPS13X:
|
||||
sec_devs = s2mps13_devs;
|
||||
num_sec_devs = ARRAY_SIZE(s2mps13_devs);
|
||||
break;
|
||||
case S2MPS14X:
|
||||
sec_devs = s2mps14_devs;
|
||||
num_sec_devs = ARRAY_SIZE(s2mps14_devs);
|
||||
|
|
|
@ -389,14 +389,22 @@ static const struct regmap_irq_chip s2mps11_irq_chip = {
|
|||
.ack_base = S2MPS11_REG_INT1,
|
||||
};
|
||||
|
||||
#define S2MPS1X_IRQ_CHIP_COMMON_DATA \
|
||||
.irqs = s2mps14_irqs, \
|
||||
.num_irqs = ARRAY_SIZE(s2mps14_irqs), \
|
||||
.num_regs = 3, \
|
||||
.status_base = S2MPS14_REG_INT1, \
|
||||
.mask_base = S2MPS14_REG_INT1M, \
|
||||
.ack_base = S2MPS14_REG_INT1 \
|
||||
|
||||
static const struct regmap_irq_chip s2mps13_irq_chip = {
|
||||
.name = "s2mps13",
|
||||
S2MPS1X_IRQ_CHIP_COMMON_DATA,
|
||||
};
|
||||
|
||||
static const struct regmap_irq_chip s2mps14_irq_chip = {
|
||||
.name = "s2mps14",
|
||||
.irqs = s2mps14_irqs,
|
||||
.num_irqs = ARRAY_SIZE(s2mps14_irqs),
|
||||
.num_regs = 3,
|
||||
.status_base = S2MPS14_REG_INT1,
|
||||
.mask_base = S2MPS14_REG_INT1M,
|
||||
.ack_base = S2MPS14_REG_INT1,
|
||||
S2MPS1X_IRQ_CHIP_COMMON_DATA,
|
||||
};
|
||||
|
||||
static const struct regmap_irq_chip s2mpu02_irq_chip = {
|
||||
|
@ -452,6 +460,9 @@ int sec_irq_init(struct sec_pmic_dev *sec_pmic)
|
|||
case S2MPS11X:
|
||||
sec_irq_chip = &s2mps11_irq_chip;
|
||||
break;
|
||||
case S2MPS13X:
|
||||
sec_irq_chip = &s2mps13_irq_chip;
|
||||
break;
|
||||
case S2MPS14X:
|
||||
sec_irq_chip = &s2mps14_irq_chip;
|
||||
break;
|
||||
|
|
|
@ -529,13 +529,13 @@ config REGULATOR_S2MPA01
|
|||
via I2C bus. S2MPA01 has 10 Bucks and 26 LDO outputs.
|
||||
|
||||
config REGULATOR_S2MPS11
|
||||
tristate "Samsung S2MPS11/S2MPS14/S2MPU02 voltage regulator"
|
||||
tristate "Samsung S2MPS11/S2MPS13/S2MPS14/S2MPU02 voltage regulator"
|
||||
depends on MFD_SEC_CORE
|
||||
help
|
||||
This driver supports a Samsung S2MPS11/S2MPS14/S2MPU02 voltage output
|
||||
regulator via I2C bus. The chip is comprised of high efficient Buck
|
||||
converters including Dual-Phase Buck converter, Buck-Boost converter,
|
||||
various LDOs.
|
||||
This driver supports a Samsung S2MPS11/S2MPS13/S2MPS14/S2MPU02 voltage
|
||||
output regulator via I2C bus. The chip is comprised of high efficient
|
||||
Buck converters including Dual-Phase Buck converter, Buck-Boost
|
||||
converter, various LDOs.
|
||||
|
||||
config REGULATOR_S5M8767
|
||||
tristate "Samsung S5M8767A voltage regulator"
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include <linux/of_gpio.h>
|
||||
#include <linux/mfd/samsung/core.h>
|
||||
#include <linux/mfd/samsung/s2mps11.h>
|
||||
#include <linux/mfd/samsung/s2mps13.h>
|
||||
#include <linux/mfd/samsung/s2mps14.h>
|
||||
#include <linux/mfd/samsung/s2mpu02.h>
|
||||
|
||||
|
@ -45,10 +46,10 @@ struct s2mps11_info {
|
|||
enum sec_device_type dev_type;
|
||||
|
||||
/*
|
||||
* One bit for each S2MPS14/S2MPU02 regulator whether the suspend mode
|
||||
* was enabled.
|
||||
* One bit for each S2MPS13/S2MPS14/S2MPU02 regulator whether
|
||||
* the suspend mode was enabled.
|
||||
*/
|
||||
unsigned long long s2mps14_suspend_state:35;
|
||||
unsigned long long s2mps14_suspend_state:50;
|
||||
|
||||
/* Array of size rdev_num with GPIO-s for external sleep control */
|
||||
int *ext_control_gpio;
|
||||
|
@ -369,12 +370,101 @@ static const struct regulator_desc s2mps11_regulators[] = {
|
|||
regulator_desc_s2mps11_buck6_10(10, MIN_750_MV, STEP_12_5_MV),
|
||||
};
|
||||
|
||||
static struct regulator_ops s2mps14_reg_ops;
|
||||
|
||||
#define regulator_desc_s2mps13_ldo(num, min, step, min_sel) { \
|
||||
.name = "LDO"#num, \
|
||||
.id = S2MPS13_LDO##num, \
|
||||
.ops = &s2mps14_reg_ops, \
|
||||
.type = REGULATOR_VOLTAGE, \
|
||||
.owner = THIS_MODULE, \
|
||||
.min_uV = min, \
|
||||
.uV_step = step, \
|
||||
.linear_min_sel = min_sel, \
|
||||
.n_voltages = S2MPS14_LDO_N_VOLTAGES, \
|
||||
.vsel_reg = S2MPS13_REG_L1CTRL + num - 1, \
|
||||
.vsel_mask = S2MPS14_LDO_VSEL_MASK, \
|
||||
.enable_reg = S2MPS13_REG_L1CTRL + num - 1, \
|
||||
.enable_mask = S2MPS14_ENABLE_MASK \
|
||||
}
|
||||
|
||||
#define regulator_desc_s2mps13_buck(num, min, step, min_sel) { \
|
||||
.name = "BUCK"#num, \
|
||||
.id = S2MPS13_BUCK##num, \
|
||||
.ops = &s2mps14_reg_ops, \
|
||||
.type = REGULATOR_VOLTAGE, \
|
||||
.owner = THIS_MODULE, \
|
||||
.min_uV = min, \
|
||||
.uV_step = step, \
|
||||
.linear_min_sel = min_sel, \
|
||||
.n_voltages = S2MPS14_BUCK_N_VOLTAGES, \
|
||||
.ramp_delay = S2MPS13_BUCK_RAMP_DELAY, \
|
||||
.vsel_reg = S2MPS13_REG_B1OUT + (num - 1) * 2, \
|
||||
.vsel_mask = S2MPS14_BUCK_VSEL_MASK, \
|
||||
.enable_reg = S2MPS13_REG_B1CTRL + (num - 1) * 2, \
|
||||
.enable_mask = S2MPS14_ENABLE_MASK \
|
||||
}
|
||||
|
||||
static const struct regulator_desc s2mps13_regulators[] = {
|
||||
regulator_desc_s2mps13_ldo(1, MIN_800_MV, STEP_12_5_MV, 0x00),
|
||||
regulator_desc_s2mps13_ldo(2, MIN_1400_MV, STEP_50_MV, 0x0C),
|
||||
regulator_desc_s2mps13_ldo(3, MIN_1000_MV, STEP_25_MV, 0x08),
|
||||
regulator_desc_s2mps13_ldo(4, MIN_800_MV, STEP_12_5_MV, 0x00),
|
||||
regulator_desc_s2mps13_ldo(5, MIN_800_MV, STEP_12_5_MV, 0x00),
|
||||
regulator_desc_s2mps13_ldo(6, MIN_800_MV, STEP_12_5_MV, 0x00),
|
||||
regulator_desc_s2mps13_ldo(7, MIN_1000_MV, STEP_25_MV, 0x08),
|
||||
regulator_desc_s2mps13_ldo(8, MIN_1000_MV, STEP_25_MV, 0x08),
|
||||
regulator_desc_s2mps13_ldo(9, MIN_1000_MV, STEP_25_MV, 0x08),
|
||||
regulator_desc_s2mps13_ldo(10, MIN_1400_MV, STEP_50_MV, 0x0C),
|
||||
regulator_desc_s2mps13_ldo(11, MIN_800_MV, STEP_25_MV, 0x10),
|
||||
regulator_desc_s2mps13_ldo(12, MIN_800_MV, STEP_25_MV, 0x10),
|
||||
regulator_desc_s2mps13_ldo(13, MIN_800_MV, STEP_25_MV, 0x10),
|
||||
regulator_desc_s2mps13_ldo(14, MIN_800_MV, STEP_12_5_MV, 0x00),
|
||||
regulator_desc_s2mps13_ldo(15, MIN_800_MV, STEP_12_5_MV, 0x00),
|
||||
regulator_desc_s2mps13_ldo(16, MIN_1400_MV, STEP_50_MV, 0x0C),
|
||||
regulator_desc_s2mps13_ldo(17, MIN_1400_MV, STEP_50_MV, 0x0C),
|
||||
regulator_desc_s2mps13_ldo(18, MIN_1000_MV, STEP_25_MV, 0x08),
|
||||
regulator_desc_s2mps13_ldo(19, MIN_1000_MV, STEP_25_MV, 0x08),
|
||||
regulator_desc_s2mps13_ldo(20, MIN_1400_MV, STEP_50_MV, 0x0C),
|
||||
regulator_desc_s2mps13_ldo(21, MIN_1000_MV, STEP_25_MV, 0x08),
|
||||
regulator_desc_s2mps13_ldo(22, MIN_1000_MV, STEP_25_MV, 0x08),
|
||||
regulator_desc_s2mps13_ldo(23, MIN_800_MV, STEP_12_5_MV, 0x00),
|
||||
regulator_desc_s2mps13_ldo(24, MIN_800_MV, STEP_12_5_MV, 0x00),
|
||||
regulator_desc_s2mps13_ldo(25, MIN_1400_MV, STEP_50_MV, 0x0C),
|
||||
regulator_desc_s2mps13_ldo(26, MIN_1400_MV, STEP_50_MV, 0x0C),
|
||||
regulator_desc_s2mps13_ldo(27, MIN_1400_MV, STEP_50_MV, 0x0C),
|
||||
regulator_desc_s2mps13_ldo(28, MIN_1000_MV, STEP_25_MV, 0x08),
|
||||
regulator_desc_s2mps13_ldo(29, MIN_1400_MV, STEP_50_MV, 0x0C),
|
||||
regulator_desc_s2mps13_ldo(30, MIN_1400_MV, STEP_50_MV, 0x0C),
|
||||
regulator_desc_s2mps13_ldo(31, MIN_1000_MV, STEP_25_MV, 0x08),
|
||||
regulator_desc_s2mps13_ldo(32, MIN_1000_MV, STEP_25_MV, 0x08),
|
||||
regulator_desc_s2mps13_ldo(33, MIN_1400_MV, STEP_50_MV, 0x0C),
|
||||
regulator_desc_s2mps13_ldo(34, MIN_1000_MV, STEP_25_MV, 0x08),
|
||||
regulator_desc_s2mps13_ldo(35, MIN_1400_MV, STEP_50_MV, 0x0C),
|
||||
regulator_desc_s2mps13_ldo(36, MIN_800_MV, STEP_12_5_MV, 0x00),
|
||||
regulator_desc_s2mps13_ldo(37, MIN_1000_MV, STEP_25_MV, 0x08),
|
||||
regulator_desc_s2mps13_ldo(38, MIN_1400_MV, STEP_50_MV, 0x0C),
|
||||
regulator_desc_s2mps13_ldo(39, MIN_1000_MV, STEP_25_MV, 0x08),
|
||||
regulator_desc_s2mps13_ldo(40, MIN_1400_MV, STEP_50_MV, 0x0C),
|
||||
regulator_desc_s2mps13_buck(1, MIN_500_MV, STEP_6_25_MV, 0x10),
|
||||
regulator_desc_s2mps13_buck(2, MIN_500_MV, STEP_6_25_MV, 0x10),
|
||||
regulator_desc_s2mps13_buck(3, MIN_500_MV, STEP_6_25_MV, 0x10),
|
||||
regulator_desc_s2mps13_buck(4, MIN_500_MV, STEP_6_25_MV, 0x10),
|
||||
regulator_desc_s2mps13_buck(5, MIN_500_MV, STEP_6_25_MV, 0x10),
|
||||
regulator_desc_s2mps13_buck(6, MIN_500_MV, STEP_6_25_MV, 0x10),
|
||||
regulator_desc_s2mps13_buck(7, MIN_500_MV, STEP_6_25_MV, 0x10),
|
||||
regulator_desc_s2mps13_buck(8, MIN_1000_MV, STEP_12_5_MV, 0x20),
|
||||
regulator_desc_s2mps13_buck(9, MIN_1000_MV, STEP_12_5_MV, 0x20),
|
||||
regulator_desc_s2mps13_buck(10, MIN_500_MV, STEP_6_25_MV, 0x10),
|
||||
};
|
||||
|
||||
static int s2mps14_regulator_enable(struct regulator_dev *rdev)
|
||||
{
|
||||
struct s2mps11_info *s2mps11 = rdev_get_drvdata(rdev);
|
||||
unsigned int val;
|
||||
|
||||
switch (s2mps11->dev_type) {
|
||||
case S2MPS13X:
|
||||
case S2MPS14X:
|
||||
if (s2mps11->s2mps14_suspend_state & (1 << rdev_get_id(rdev)))
|
||||
val = S2MPS14_ENABLE_SUSPEND;
|
||||
|
@ -406,6 +496,7 @@ static int s2mps14_regulator_set_suspend_disable(struct regulator_dev *rdev)
|
|||
|
||||
/* Below LDO should be always on or does not support suspend mode. */
|
||||
switch (s2mps11->dev_type) {
|
||||
case S2MPS13X:
|
||||
case S2MPS14X:
|
||||
switch (rdev_id) {
|
||||
case S2MPS14_LDO3:
|
||||
|
@ -831,6 +922,10 @@ static int s2mps11_pmic_probe(struct platform_device *pdev)
|
|||
s2mps11->rdev_num = ARRAY_SIZE(s2mps11_regulators);
|
||||
regulators = s2mps11_regulators;
|
||||
break;
|
||||
case S2MPS13X:
|
||||
s2mps11->rdev_num = ARRAY_SIZE(s2mps13_regulators);
|
||||
regulators = s2mps13_regulators;
|
||||
break;
|
||||
case S2MPS14X:
|
||||
s2mps11->rdev_num = ARRAY_SIZE(s2mps14_regulators);
|
||||
regulators = s2mps14_regulators;
|
||||
|
@ -927,6 +1022,7 @@ out:
|
|||
|
||||
static const struct platform_device_id s2mps11_pmic_id[] = {
|
||||
{ "s2mps11-pmic", S2MPS11X},
|
||||
{ "s2mps13-pmic", S2MPS13X},
|
||||
{ "s2mps14-pmic", S2MPS14X},
|
||||
{ "s2mpu02-pmic", S2MPU02},
|
||||
{ },
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
enum {
|
||||
AXP202_ID = 0,
|
||||
AXP209_ID,
|
||||
AXP288_ID,
|
||||
NR_AXP20X_VARIANTS,
|
||||
};
|
||||
|
||||
#define AXP20X_DATACACHE(m) (0x04 + (m))
|
||||
|
@ -49,11 +51,13 @@ enum {
|
|||
#define AXP20X_IRQ3_EN 0x42
|
||||
#define AXP20X_IRQ4_EN 0x43
|
||||
#define AXP20X_IRQ5_EN 0x44
|
||||
#define AXP20X_IRQ6_EN 0x45
|
||||
#define AXP20X_IRQ1_STATE 0x48
|
||||
#define AXP20X_IRQ2_STATE 0x49
|
||||
#define AXP20X_IRQ3_STATE 0x4a
|
||||
#define AXP20X_IRQ4_STATE 0x4b
|
||||
#define AXP20X_IRQ5_STATE 0x4c
|
||||
#define AXP20X_IRQ6_STATE 0x4d
|
||||
|
||||
/* ADC */
|
||||
#define AXP20X_ACIN_V_ADC_H 0x56
|
||||
|
@ -116,6 +120,15 @@ enum {
|
|||
#define AXP20X_CC_CTRL 0xb8
|
||||
#define AXP20X_FG_RES 0xb9
|
||||
|
||||
/* AXP288 specific registers */
|
||||
#define AXP288_PMIC_ADC_H 0x56
|
||||
#define AXP288_PMIC_ADC_L 0x57
|
||||
#define AXP288_ADC_TS_PIN_CTRL 0x84
|
||||
|
||||
#define AXP288_PMIC_ADC_EN 0x84
|
||||
#define AXP288_FG_TUNE5 0xed
|
||||
|
||||
|
||||
/* Regulators IDs */
|
||||
enum {
|
||||
AXP20X_LDO1 = 0,
|
||||
|
@ -169,12 +182,58 @@ enum {
|
|||
AXP20X_IRQ_GPIO0_INPUT,
|
||||
};
|
||||
|
||||
enum axp288_irqs {
|
||||
AXP288_IRQ_VBUS_FALL = 2,
|
||||
AXP288_IRQ_VBUS_RISE,
|
||||
AXP288_IRQ_OV,
|
||||
AXP288_IRQ_FALLING_ALT,
|
||||
AXP288_IRQ_RISING_ALT,
|
||||
AXP288_IRQ_OV_ALT,
|
||||
AXP288_IRQ_DONE = 10,
|
||||
AXP288_IRQ_CHARGING,
|
||||
AXP288_IRQ_SAFE_QUIT,
|
||||
AXP288_IRQ_SAFE_ENTER,
|
||||
AXP288_IRQ_ABSENT,
|
||||
AXP288_IRQ_APPEND,
|
||||
AXP288_IRQ_QWBTU,
|
||||
AXP288_IRQ_WBTU,
|
||||
AXP288_IRQ_QWBTO,
|
||||
AXP288_IRQ_WBTO,
|
||||
AXP288_IRQ_QCBTU,
|
||||
AXP288_IRQ_CBTU,
|
||||
AXP288_IRQ_QCBTO,
|
||||
AXP288_IRQ_CBTO,
|
||||
AXP288_IRQ_WL2,
|
||||
AXP288_IRQ_WL1,
|
||||
AXP288_IRQ_GPADC,
|
||||
AXP288_IRQ_OT = 31,
|
||||
AXP288_IRQ_GPIO0,
|
||||
AXP288_IRQ_GPIO1,
|
||||
AXP288_IRQ_POKO,
|
||||
AXP288_IRQ_POKL,
|
||||
AXP288_IRQ_POKS,
|
||||
AXP288_IRQ_POKN,
|
||||
AXP288_IRQ_POKP,
|
||||
AXP288_IRQ_TIMER,
|
||||
AXP288_IRQ_MV_CHNG,
|
||||
AXP288_IRQ_BC_USB_CHNG,
|
||||
};
|
||||
|
||||
#define AXP288_TS_ADC_H 0x58
|
||||
#define AXP288_TS_ADC_L 0x59
|
||||
#define AXP288_GP_ADC_H 0x5a
|
||||
#define AXP288_GP_ADC_L 0x5b
|
||||
|
||||
struct axp20x_dev {
|
||||
struct device *dev;
|
||||
struct i2c_client *i2c_client;
|
||||
struct regmap *regmap;
|
||||
struct regmap_irq_chip_data *regmap_irqc;
|
||||
long variant;
|
||||
int nr_cells;
|
||||
struct mfd_cell *cells;
|
||||
const struct regmap_config *regmap_cfg;
|
||||
const struct regmap_irq_chip *regmap_irq_chip;
|
||||
};
|
||||
|
||||
#endif /* __LINUX_MFD_AXP20X_H */
|
||||
|
|
|
@ -111,6 +111,13 @@ extern int mfd_add_devices(struct device *parent, int id,
|
|||
struct resource *mem_base,
|
||||
int irq_base, struct irq_domain *irq_domain);
|
||||
|
||||
static inline int mfd_add_hotplug_devices(struct device *parent,
|
||||
const struct mfd_cell *cells, int n_devs)
|
||||
{
|
||||
return mfd_add_devices(parent, PLATFORM_DEVID_AUTO, cells, n_devs,
|
||||
NULL, 0, NULL);
|
||||
}
|
||||
|
||||
extern void mfd_remove_devices(struct device *parent);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
#ifndef __LINUX_USB_DLN2_H
|
||||
#define __LINUX_USB_DLN2_H
|
||||
|
||||
#define DLN2_CMD(cmd, id) ((cmd) | ((id) << 8))
|
||||
|
||||
struct dln2_platform_data {
|
||||
u16 handle; /* sub-driver handle (internally used only) */
|
||||
u8 port; /* I2C/SPI port */
|
||||
};
|
||||
|
||||
/**
|
||||
* dln2_event_cb_t - event callback function signature
|
||||
*
|
||||
* @pdev - the sub-device that registered this callback
|
||||
* @echo - the echo header field received in the message
|
||||
* @data - the data payload
|
||||
* @len - the data payload length
|
||||
*
|
||||
* The callback function is called in interrupt context and the data payload is
|
||||
* only valid during the call. If the user needs later access of the data, it
|
||||
* must copy it.
|
||||
*/
|
||||
|
||||
typedef void (*dln2_event_cb_t)(struct platform_device *pdev, u16 echo,
|
||||
const void *data, int len);
|
||||
|
||||
/**
|
||||
* dl2n_register_event_cb - register a callback function for an event
|
||||
*
|
||||
* @pdev - the sub-device that registers the callback
|
||||
* @event - the event for which to register a callback
|
||||
* @event_cb - the callback function
|
||||
*
|
||||
* @return 0 in case of success, negative value in case of error
|
||||
*/
|
||||
int dln2_register_event_cb(struct platform_device *pdev, u16 event,
|
||||
dln2_event_cb_t event_cb);
|
||||
|
||||
/**
|
||||
* dln2_unregister_event_cb - unregister the callback function for an event
|
||||
*
|
||||
* @pdev - the sub-device that registered the callback
|
||||
* @event - the event for which to register a callback
|
||||
*/
|
||||
void dln2_unregister_event_cb(struct platform_device *pdev, u16 event);
|
||||
|
||||
/**
|
||||
* dln2_transfer - issue a DLN2 command and wait for a response and the
|
||||
* associated data
|
||||
*
|
||||
* @pdev - the sub-device which is issuing this transfer
|
||||
* @cmd - the command to be sent to the device
|
||||
* @obuf - the buffer to be sent to the device; it can be NULL if the user
|
||||
* doesn't need to transmit data with this command
|
||||
* @obuf_len - the size of the buffer to be sent to the device
|
||||
* @ibuf - any data associated with the response will be copied here; it can be
|
||||
* NULL if the user doesn't need the response data
|
||||
* @ibuf_len - must be initialized to the input buffer size; it will be modified
|
||||
* to indicate the actual data transferred;
|
||||
*
|
||||
* @return 0 for success, negative value for errors
|
||||
*/
|
||||
int dln2_transfer(struct platform_device *pdev, u16 cmd,
|
||||
const void *obuf, unsigned obuf_len,
|
||||
void *ibuf, unsigned *ibuf_len);
|
||||
|
||||
/**
|
||||
* dln2_transfer_rx - variant of @dln2_transfer() where TX buffer is not needed
|
||||
*
|
||||
* @pdev - the sub-device which is issuing this transfer
|
||||
* @cmd - the command to be sent to the device
|
||||
* @ibuf - any data associated with the response will be copied here; it can be
|
||||
* NULL if the user doesn't need the response data
|
||||
* @ibuf_len - must be initialized to the input buffer size; it will be modified
|
||||
* to indicate the actual data transferred;
|
||||
*
|
||||
* @return 0 for success, negative value for errors
|
||||
*/
|
||||
|
||||
static inline int dln2_transfer_rx(struct platform_device *pdev, u16 cmd,
|
||||
void *ibuf, unsigned *ibuf_len)
|
||||
{
|
||||
return dln2_transfer(pdev, cmd, NULL, 0, ibuf, ibuf_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* dln2_transfer_tx - variant of @dln2_transfer() where RX buffer is not needed
|
||||
*
|
||||
* @pdev - the sub-device which is issuing this transfer
|
||||
* @cmd - the command to be sent to the device
|
||||
* @obuf - the buffer to be sent to the device; it can be NULL if the
|
||||
* user doesn't need to transmit data with this command
|
||||
* @obuf_len - the size of the buffer to be sent to the device
|
||||
*
|
||||
* @return 0 for success, negative value for errors
|
||||
*/
|
||||
static inline int dln2_transfer_tx(struct platform_device *pdev, u16 cmd,
|
||||
const void *obuf, unsigned obuf_len)
|
||||
{
|
||||
return dln2_transfer(pdev, cmd, obuf, obuf_len, NULL, NULL);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -28,6 +28,7 @@
|
|||
#define MIN_800_MV 800000
|
||||
#define MIN_750_MV 750000
|
||||
#define MIN_600_MV 600000
|
||||
#define MIN_500_MV 500000
|
||||
|
||||
/* Macros to represent steps for LDO/BUCK */
|
||||
#define STEP_50_MV 50000
|
||||
|
@ -41,6 +42,7 @@ enum sec_device_type {
|
|||
S5M8767X,
|
||||
S2MPA01,
|
||||
S2MPS11X,
|
||||
S2MPS13X,
|
||||
S2MPS14X,
|
||||
S2MPU02,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* s2mps13.h
|
||||
*
|
||||
* Copyright (c) 2014 Samsung Electronics Co., Ltd
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_MFD_S2MPS13_H
|
||||
#define __LINUX_MFD_S2MPS13_H
|
||||
|
||||
/* S2MPS13 registers */
|
||||
enum s2mps13_reg {
|
||||
S2MPS13_REG_ID,
|
||||
S2MPS13_REG_INT1,
|
||||
S2MPS13_REG_INT2,
|
||||
S2MPS13_REG_INT3,
|
||||
S2MPS13_REG_INT1M,
|
||||
S2MPS13_REG_INT2M,
|
||||
S2MPS13_REG_INT3M,
|
||||
S2MPS13_REG_ST1,
|
||||
S2MPS13_REG_ST2,
|
||||
S2MPS13_REG_PWRONSRC,
|
||||
S2MPS13_REG_OFFSRC,
|
||||
S2MPS13_REG_BU_CHG,
|
||||
S2MPS13_REG_RTCCTRL,
|
||||
S2MPS13_REG_CTRL1,
|
||||
S2MPS13_REG_CTRL2,
|
||||
S2MPS13_REG_RSVD1,
|
||||
S2MPS13_REG_RSVD2,
|
||||
S2MPS13_REG_RSVD3,
|
||||
S2MPS13_REG_RSVD4,
|
||||
S2MPS13_REG_RSVD5,
|
||||
S2MPS13_REG_RSVD6,
|
||||
S2MPS13_REG_CTRL3,
|
||||
S2MPS13_REG_RSVD7,
|
||||
S2MPS13_REG_RSVD8,
|
||||
S2MPS13_REG_WRSTBI,
|
||||
S2MPS13_REG_B1CTRL,
|
||||
S2MPS13_REG_B1OUT,
|
||||
S2MPS13_REG_B2CTRL,
|
||||
S2MPS13_REG_B2OUT,
|
||||
S2MPS13_REG_B3CTRL,
|
||||
S2MPS13_REG_B3OUT,
|
||||
S2MPS13_REG_B4CTRL,
|
||||
S2MPS13_REG_B4OUT,
|
||||
S2MPS13_REG_B5CTRL,
|
||||
S2MPS13_REG_B5OUT,
|
||||
S2MPS13_REG_B6CTRL,
|
||||
S2MPS13_REG_B6OUT,
|
||||
S2MPS13_REG_B7CTRL,
|
||||
S2MPS13_REG_B7OUT,
|
||||
S2MPS13_REG_B8CTRL,
|
||||
S2MPS13_REG_B8OUT,
|
||||
S2MPS13_REG_B9CTRL,
|
||||
S2MPS13_REG_B9OUT,
|
||||
S2MPS13_REG_B10CTRL,
|
||||
S2MPS13_REG_B10OUT,
|
||||
S2MPS13_REG_BB1CTRL,
|
||||
S2MPS13_REG_BB1OUT,
|
||||
S2MPS13_REG_BUCK_RAMP1,
|
||||
S2MPS13_REG_BUCK_RAMP2,
|
||||
S2MPS13_REG_LDO_DVS1,
|
||||
S2MPS13_REG_LDO_DVS2,
|
||||
S2MPS13_REG_LDO_DVS3,
|
||||
S2MPS13_REG_B6OUT2,
|
||||
S2MPS13_REG_L1CTRL,
|
||||
S2MPS13_REG_L2CTRL,
|
||||
S2MPS13_REG_L3CTRL,
|
||||
S2MPS13_REG_L4CTRL,
|
||||
S2MPS13_REG_L5CTRL,
|
||||
S2MPS13_REG_L6CTRL,
|
||||
S2MPS13_REG_L7CTRL,
|
||||
S2MPS13_REG_L8CTRL,
|
||||
S2MPS13_REG_L9CTRL,
|
||||
S2MPS13_REG_L10CTRL,
|
||||
S2MPS13_REG_L11CTRL,
|
||||
S2MPS13_REG_L12CTRL,
|
||||
S2MPS13_REG_L13CTRL,
|
||||
S2MPS13_REG_L14CTRL,
|
||||
S2MPS13_REG_L15CTRL,
|
||||
S2MPS13_REG_L16CTRL,
|
||||
S2MPS13_REG_L17CTRL,
|
||||
S2MPS13_REG_L18CTRL,
|
||||
S2MPS13_REG_L19CTRL,
|
||||
S2MPS13_REG_L20CTRL,
|
||||
S2MPS13_REG_L21CTRL,
|
||||
S2MPS13_REG_L22CTRL,
|
||||
S2MPS13_REG_L23CTRL,
|
||||
S2MPS13_REG_L24CTRL,
|
||||
S2MPS13_REG_L25CTRL,
|
||||
S2MPS13_REG_L26CTRL,
|
||||
S2MPS13_REG_L27CTRL,
|
||||
S2MPS13_REG_L28CTRL,
|
||||
S2MPS13_REG_L30CTRL,
|
||||
S2MPS13_REG_L31CTRL,
|
||||
S2MPS13_REG_L32CTRL,
|
||||
S2MPS13_REG_L33CTRL,
|
||||
S2MPS13_REG_L34CTRL,
|
||||
S2MPS13_REG_L35CTRL,
|
||||
S2MPS13_REG_L36CTRL,
|
||||
S2MPS13_REG_L37CTRL,
|
||||
S2MPS13_REG_L38CTRL,
|
||||
S2MPS13_REG_L39CTRL,
|
||||
S2MPS13_REG_L40CTRL,
|
||||
S2MPS13_REG_LDODSCH1,
|
||||
S2MPS13_REG_LDODSCH2,
|
||||
S2MPS13_REG_LDODSCH3,
|
||||
S2MPS13_REG_LDODSCH4,
|
||||
S2MPS13_REG_LDODSCH5,
|
||||
};
|
||||
|
||||
/* regulator ids */
|
||||
enum s2mps13_regulators {
|
||||
S2MPS13_LDO1,
|
||||
S2MPS13_LDO2,
|
||||
S2MPS13_LDO3,
|
||||
S2MPS13_LDO4,
|
||||
S2MPS13_LDO5,
|
||||
S2MPS13_LDO6,
|
||||
S2MPS13_LDO7,
|
||||
S2MPS13_LDO8,
|
||||
S2MPS13_LDO9,
|
||||
S2MPS13_LDO10,
|
||||
S2MPS13_LDO11,
|
||||
S2MPS13_LDO12,
|
||||
S2MPS13_LDO13,
|
||||
S2MPS13_LDO14,
|
||||
S2MPS13_LDO15,
|
||||
S2MPS13_LDO16,
|
||||
S2MPS13_LDO17,
|
||||
S2MPS13_LDO18,
|
||||
S2MPS13_LDO19,
|
||||
S2MPS13_LDO20,
|
||||
S2MPS13_LDO21,
|
||||
S2MPS13_LDO22,
|
||||
S2MPS13_LDO23,
|
||||
S2MPS13_LDO24,
|
||||
S2MPS13_LDO25,
|
||||
S2MPS13_LDO26,
|
||||
S2MPS13_LDO27,
|
||||
S2MPS13_LDO28,
|
||||
S2MPS13_LDO29,
|
||||
S2MPS13_LDO30,
|
||||
S2MPS13_LDO31,
|
||||
S2MPS13_LDO32,
|
||||
S2MPS13_LDO33,
|
||||
S2MPS13_LDO34,
|
||||
S2MPS13_LDO35,
|
||||
S2MPS13_LDO36,
|
||||
S2MPS13_LDO37,
|
||||
S2MPS13_LDO38,
|
||||
S2MPS13_LDO39,
|
||||
S2MPS13_LDO40,
|
||||
S2MPS13_BUCK1,
|
||||
S2MPS13_BUCK2,
|
||||
S2MPS13_BUCK3,
|
||||
S2MPS13_BUCK4,
|
||||
S2MPS13_BUCK5,
|
||||
S2MPS13_BUCK6,
|
||||
S2MPS13_BUCK7,
|
||||
S2MPS13_BUCK8,
|
||||
S2MPS13_BUCK9,
|
||||
S2MPS13_BUCK10,
|
||||
|
||||
S2MPS13_REGULATOR_MAX,
|
||||
};
|
||||
|
||||
/*
|
||||
* Default ramp delay in uv/us. Datasheet says that ramp delay can be
|
||||
* controlled however it does not specify which register is used for that.
|
||||
* Let's assume that default value will be set.
|
||||
*/
|
||||
#define S2MPS13_BUCK_RAMP_DELAY 12500
|
||||
|
||||
#endif /* __LINUX_MFD_S2MPS13_H */
|
|
@ -866,4 +866,15 @@ static inline int of_changeset_update_property(struct of_changeset *ocs,
|
|||
/* CONFIG_OF_RESOLVE api */
|
||||
extern int of_resolve_phandles(struct device_node *tree);
|
||||
|
||||
/**
|
||||
* of_system_has_poweroff_source - Tells if poweroff-source is found for device_node
|
||||
* @np: Pointer to the given device_node
|
||||
*
|
||||
* return true if present false otherwise
|
||||
*/
|
||||
static inline bool of_system_has_poweroff_source(const struct device_node *np)
|
||||
{
|
||||
return of_property_read_bool(np, "poweroff-source");
|
||||
}
|
||||
|
||||
#endif /* _LINUX_OF_H */
|
||||
|
|
Loading…
Reference in New Issue