Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > linux.kernel > #1739838 > unrolled thread
| Started by | Quentin Schulz <quentin.schulz@free-electrons.com> |
|---|---|
| First post | 2017-09-26 14:30 +0200 |
| Last post | 2017-09-27 11:30 +0200 |
| Articles | 12 — 2 participants |
Back to article view | Back to linux.kernel
[PATCH v2 00/10] add pinmuxing support for pins in AXP209 and AXP813 PMICs Quentin Schulz <quentin.schulz@free-electrons.com> - 2017-09-26 14:30 +0200
[PATCH v2 03/10] pinctrl: axp209: use drv_data of pinctrl_pin_desc to store pin reg Quentin Schulz <quentin.schulz@free-electrons.com> - 2017-09-26 14:30 +0200
Re: [PATCH v2 03/10] pinctrl: axp209: use drv_data of pinctrl_pin_desc to store pin reg Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-09-26 15:10 +0200
Re: [PATCH v2 03/10] pinctrl: axp209: use drv_data of pinctrl_pin_desc to store pin reg Quentin Schulz <quentin.schulz@free-electrons.com> - 2017-09-26 15:20 +0200
Re: [PATCH v2 03/10] pinctrl: axp209: use drv_data of pinctrl_pin_desc to store pin reg Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-09-26 15:50 +0200
[PATCH v2 04/10] pinctrl: axp209: rename everything from gpio to pctl Quentin Schulz <quentin.schulz@free-electrons.com> - 2017-09-26 14:30 +0200
[PATCH v2 02/10] pinctrl: axp209: add pinctrl features Quentin Schulz <quentin.schulz@free-electrons.com> - 2017-09-26 14:30 +0200
Re: [PATCH v2 02/10] pinctrl: axp209: add pinctrl features Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-09-26 15:10 +0200
Re: [PATCH v2 02/10] pinctrl: axp209: add pinctrl features Quentin Schulz <quentin.schulz@free-electrons.com> - 2017-09-26 15:20 +0200
Re: [PATCH v2 02/10] pinctrl: axp209: add pinctrl features Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-09-26 15:30 +0200
Re: [PATCH v2 02/10] pinctrl: axp209: add pinctrl features Quentin Schulz <quentin.schulz@free-electrons.com> - 2017-09-26 15:40 +0200
Re: [PATCH v2 02/10] pinctrl: axp209: add pinctrl features Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-09-27 11:30 +0200
| From | Quentin Schulz <quentin.schulz@free-electrons.com> |
|---|---|
| Date | 2017-09-26 14:30 +0200 |
| Subject | [PATCH v2 00/10] add pinmuxing support for pins in AXP209 and AXP813 PMICs |
| Message-ID | <utX0e-4FS-7@gated-at.bofh.it> |
The AXP209 and AXP813 PMICs have several pins (respectively 3 and 2) that can be used either as GPIOs or for other purposes (ADC or LDO here). We already have a GPIO driver for the GPIO use of those pins on the AXP209. Let's "upgrade" this driver to support all the functions these pins can have. Then we add support to this driver for the AXP813 which is slighlty different (basically a different offset in a register and one less pin). This is a v2 to a first version that was sent in November 2016. v2: - add support for AXP813 pins, - split into more patches so it is easier to follow the modifications, - reorder of some patches, - register all pins within the same range instead of a range per pin, Thanks, Quentin Maxime Ripard (1): ARM: dts: add dtsi for AXP813 PMIC Quentin Schulz (9): pinctrl: move gpio-axp209 to pinctrl pinctrl: axp209: add pinctrl features pinctrl: axp209: use drv_data of pinctrl_pin_desc to store pin reg pinctrl: axp209: rename everything from gpio to pctl pinctrl: axp209: add programmable gpio_status_offset pinctrl: axp209: add support for AXP813 GPIOs mfd: axp20x: add pinctrl cell for AXP813 ARM: dts: sun8i: a711: include axp813 dtsi ARM: dts: sun8i: bananapi-m3: include axp813 dtsi Documentation/devicetree/bindings/gpio/gpio-axp209.txt | 30 +- Documentation/devicetree/bindings/pinctrl/pinctrl-axp209.txt | 67 +- arch/arm/boot/dts/axp813.dtsi | 58 +- arch/arm/boot/dts/sun8i-a83t-bananapi-m3.dts | 4 +- arch/arm/boot/dts/sun8i-a83t-tbs-a711.dts | 2 +- drivers/gpio/Kconfig | 6 +- drivers/gpio/Makefile | 1 +- drivers/gpio/gpio-axp209.c | 188 +-- drivers/mfd/axp20x.c | 3 +- drivers/pinctrl/Kconfig | 6 +- drivers/pinctrl/Makefile | 1 +- drivers/pinctrl/pinctrl-axp209.c | 610 +++++++- 12 files changed, 750 insertions(+), 226 deletions(-) delete mode 100644 Documentation/devicetree/bindings/gpio/gpio-axp209.txt create mode 100644 Documentation/devicetree/bindings/pinctrl/pinctrl-axp209.txt create mode 100644 arch/arm/boot/dts/axp813.dtsi delete mode 100644 drivers/gpio/gpio-axp209.c create mode 100644 drivers/pinctrl/pinctrl-axp209.c base-commit: 73527316e3fdde8a210b8ab66c1bf48538cf6b09 -- git-series 0.9.1
[toc] | [next] | [standalone]
| From | Quentin Schulz <quentin.schulz@free-electrons.com> |
|---|---|
| Date | 2017-09-26 14:30 +0200 |
| Subject | [PATCH v2 03/10] pinctrl: axp209: use drv_data of pinctrl_pin_desc to store pin reg |
| Message-ID | <utX9U-4JB-31@gated-at.bofh.it> |
| In reply to | #1739838 |
Instead of using a function to retrieve each pin's correct control
register, use drv_data within pinctrl_pin_desc to store the ctrl reg.
Remove axp20x_gpio_get_reg and replace every occurrence by a get from
drv_data.
Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
---
drivers/pinctrl/pinctrl-axp209.c | 42 +++++++--------------------------
1 file changed, 9 insertions(+), 33 deletions(-)
diff --git a/drivers/pinctrl/pinctrl-axp209.c b/drivers/pinctrl/pinctrl-axp209.c
index b35e8dd..4bbcba2 100644
--- a/drivers/pinctrl/pinctrl-axp209.c
+++ b/drivers/pinctrl/pinctrl-axp209.c
@@ -32,10 +32,11 @@
#define AXP20X_GPIO_FUNCTION_OUT_HIGH 1
#define AXP20X_GPIO_FUNCTION_INPUT 2
-#define AXP20X_PINCTRL_PIN(_pin_num, _pin) \
+#define AXP20X_PINCTRL_PIN(_pin_num, _pin, _regs) \
{ \
.number = _pin_num, \
.name = _pin, \
+ .drv_data = _regs, \
}
#define AXP20X_PIN(_pin, ...) \
@@ -91,17 +92,17 @@ struct axp20x_gpio {
};
static const struct axp20x_desc_pin axp209_pins[] = {
- AXP20X_PIN(AXP20X_PINCTRL_PIN(0, "GPIO0"),
+ AXP20X_PIN(AXP20X_PINCTRL_PIN(0, "GPIO0", (void *)AXP20X_GPIO0_CTRL),
AXP20X_FUNCTION(0x0, "gpio_out"),
AXP20X_FUNCTION(0x2, "gpio_in"),
AXP20X_FUNCTION(0x3, "ldo"),
AXP20X_FUNCTION(0x4, "adc")),
- AXP20X_PIN(AXP20X_PINCTRL_PIN(1, "GPIO1"),
+ AXP20X_PIN(AXP20X_PINCTRL_PIN(1, "GPIO1", (void *)AXP20X_GPIO1_CTRL),
AXP20X_FUNCTION(0x0, "gpio_out"),
AXP20X_FUNCTION(0x2, "gpio_in"),
AXP20X_FUNCTION(0x3, "ldo"),
AXP20X_FUNCTION(0x4, "adc")),
- AXP20X_PIN(AXP20X_PINCTRL_PIN(2, "GPIO2"),
+ AXP20X_PIN(AXP20X_PINCTRL_PIN(2, "GPIO2", (void *)AXP20X_GPIO2_CTRL),
AXP20X_FUNCTION(0x0, "gpio_out"),
AXP20X_FUNCTION(0x2, "gpio_in")),
};
@@ -111,20 +112,6 @@ static const struct axp20x_pinctrl_desc axp20x_pinctrl_data = {
.npins = ARRAY_SIZE(axp209_pins),
};
-static int axp20x_gpio_get_reg(unsigned offset)
-{
- switch (offset) {
- case 0:
- return AXP20X_GPIO0_CTRL;
- case 1:
- return AXP20X_GPIO1_CTRL;
- case 2:
- return AXP20X_GPIO2_CTRL;
- }
-
- return -EINVAL;
-}
-
static int axp20x_gpio_input(struct gpio_chip *chip, unsigned offset)
{
return pinctrl_gpio_direction_input(chip->base + offset);
@@ -146,12 +133,9 @@ static int axp20x_gpio_get(struct gpio_chip *chip, unsigned offset)
static int axp20x_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
{
struct axp20x_gpio *gpio = gpiochip_get_data(chip);
+ int reg = (int)gpio->desc->pins[offset].pin.drv_data;
unsigned int val;
- int reg, ret;
-
- reg = axp20x_gpio_get_reg(offset);
- if (reg < 0)
- return reg;
+ int ret;
ret = regmap_read(gpio->regmap, reg, &val);
if (ret)
@@ -184,11 +168,7 @@ static void axp20x_gpio_set(struct gpio_chip *chip, unsigned offset,
int value)
{
struct axp20x_gpio *gpio = gpiochip_get_data(chip);
- int reg;
-
- reg = axp20x_gpio_get_reg(offset);
- if (reg < 0)
- return;
+ int reg = (int)gpio->desc->pins[offset].pin.drv_data;
regmap_update_bits(gpio->regmap, reg,
AXP20X_GPIO_FUNCTIONS,
@@ -200,11 +180,7 @@ static int axp20x_pmx_set(struct pinctrl_dev *pctldev, unsigned int offset,
u8 config)
{
struct axp20x_gpio *gpio = pinctrl_dev_get_drvdata(pctldev);
- int reg;
-
- reg = axp20x_gpio_get_reg(offset);
- if (reg < 0)
- return reg;
+ int reg = (int)gpio->desc->pins[offset].pin.drv_data;
return regmap_update_bits(gpio->regmap, reg, AXP20X_GPIO_FUNCTIONS,
config);
--
git-series 0.9.1
[toc] | [prev] | [next] | [standalone]
| From | Maxime Ripard <maxime.ripard@free-electrons.com> |
|---|---|
| Date | 2017-09-26 15:10 +0200 |
| Subject | Re: [PATCH v2 03/10] pinctrl: axp209: use drv_data of pinctrl_pin_desc to store pin reg |
| Message-ID | <utXMB-5dg-7@gated-at.bofh.it> |
| In reply to | #1739840 |
[Multipart message — attachments visible in raw view] — view raw
On Tue, Sep 26, 2017 at 12:17:13PM +0000, Quentin Schulz wrote: > Instead of using a function to retrieve each pin's correct control > register, use drv_data within pinctrl_pin_desc to store the ctrl reg. > > Remove axp20x_gpio_get_reg and replace every occurrence by a get from > drv_data. Why do you need to do that? This should be explained. Maxime -- Maxime Ripard, Free Electrons Embedded Linux and Kernel engineering http://free-electrons.com
[toc] | [prev] | [next] | [standalone]
| From | Quentin Schulz <quentin.schulz@free-electrons.com> |
|---|---|
| Date | 2017-09-26 15:20 +0200 |
| Subject | Re: [PATCH v2 03/10] pinctrl: axp209: use drv_data of pinctrl_pin_desc to store pin reg |
| Message-ID | <utXWi-5gz-11@gated-at.bofh.it> |
| In reply to | #1739872 |
[Multipart message — attachments visible in raw view] — view raw
Hi Maxime, On 26/09/2017 15:01, Maxime Ripard wrote: > On Tue, Sep 26, 2017 at 12:17:13PM +0000, Quentin Schulz wrote: >> Instead of using a function to retrieve each pin's correct control >> register, use drv_data within pinctrl_pin_desc to store the ctrl reg. >> >> Remove axp20x_gpio_get_reg and replace every occurrence by a get from >> drv_data. > > Why do you need to do that? This should be explained. > Agreed that it misses an explanation. Today, to get a register addr of one of the GPIOs in the PMIC, we basically get the GPIO number and returns the register via this info. There are 3 GPIOs in AXP209, 2 in AXP813. I didn't want to have a switch case for the GPIO number and then an if/else inside one of the case to check if the device is AXP209 or AXP813 in which case we return -EINVAL instead of the GPIO2 reg. With support for new PMIC, we would have a bunch of if conditions and complexify the process for something really simple. IMHO, this also allows easier integration of future PMICs which might have different regs for the GPIOs. I don't *need* it but I find this solution nicer. Thanks, Quentin -- Quentin Schulz, Free Electrons Embedded Linux and Kernel engineering http://free-electrons.com
[toc] | [prev] | [next] | [standalone]
| From | Maxime Ripard <maxime.ripard@free-electrons.com> |
|---|---|
| Date | 2017-09-26 15:50 +0200 |
| Subject | Re: [PATCH v2 03/10] pinctrl: axp209: use drv_data of pinctrl_pin_desc to store pin reg |
| Message-ID | <utYpj-5qG-1@gated-at.bofh.it> |
| In reply to | #1739877 |
[Multipart message — attachments visible in raw view] — view raw
On Tue, Sep 26, 2017 at 01:17:05PM +0000, Quentin Schulz wrote: > Hi Maxime, > > On 26/09/2017 15:01, Maxime Ripard wrote: > > On Tue, Sep 26, 2017 at 12:17:13PM +0000, Quentin Schulz wrote: > >> Instead of using a function to retrieve each pin's correct control > >> register, use drv_data within pinctrl_pin_desc to store the ctrl reg. > >> > >> Remove axp20x_gpio_get_reg and replace every occurrence by a get from > >> drv_data. > > > > Why do you need to do that? This should be explained. > > > > Agreed that it misses an explanation. > > Today, to get a register addr of one of the GPIOs in the PMIC, we > basically get the GPIO number and returns the register via this info. > > There are 3 GPIOs in AXP209, 2 in AXP813. I didn't want to have a switch > case for the GPIO number and then an if/else inside one of the case to > check if the device is AXP209 or AXP813 in which case we return -EINVAL > instead of the GPIO2 reg. With support for new PMIC, we would have a > bunch of if conditions and complexify the process for something really > simple. I'm not sure how that relates to your code actually. The only thing that patch is doing is to move the register offset from a function to the structure associated to the pin. However, even in the AXP813 case, you're using exactly the same values, so that's not really needed. Now, you also mentionned the pin number. While this patch doesn't really address it, it's also no really needed. The number of pins is already known and registered in the GPIO framework. If the framework doesn't already do it (which would be surprising), you can just check that the pin number passed is not going to be higher than the one you registered. > IMHO, this also allows easier integration of future PMICs which might > have different regs for the GPIOs. Let's worry about future PMICs in the future. Maxime -- Maxime Ripard, Free Electrons Embedded Linux and Kernel engineering http://free-electrons.com
[toc] | [prev] | [next] | [standalone]
| From | Quentin Schulz <quentin.schulz@free-electrons.com> |
|---|---|
| Date | 2017-09-26 14:30 +0200 |
| Subject | [PATCH v2 04/10] pinctrl: axp209: rename everything from gpio to pctl |
| Message-ID | <utX9U-4JB-23@gated-at.bofh.it> |
| In reply to | #1739838 |
This driver used to do only GPIO features of the GPIOs in X-Powers
AXP20X. Now that we have migrated everything to the pinctrl subsystem
and added pinctrl features, rename everything related to pinctrl from
gpio to pctl to ease the understanding of differences between GPIO
and pinctrl features.
Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
---
drivers/pinctrl/pinctrl-axp209.c | 198 ++++++++++++++++----------------
1 file changed, 99 insertions(+), 99 deletions(-)
diff --git a/drivers/pinctrl/pinctrl-axp209.c b/drivers/pinctrl/pinctrl-axp209.c
index 4bbcba2..4be1aca 100644
--- a/drivers/pinctrl/pinctrl-axp209.c
+++ b/drivers/pinctrl/pinctrl-axp209.c
@@ -79,7 +79,7 @@ struct axp20x_pinctrl_function {
unsigned int ngroups;
};
-struct axp20x_gpio {
+struct axp20x_pctl {
struct gpio_chip chip;
struct regmap *regmap;
struct pinctrl_dev *pctl_dev;
@@ -119,11 +119,11 @@ static int axp20x_gpio_input(struct gpio_chip *chip, unsigned offset)
static int axp20x_gpio_get(struct gpio_chip *chip, unsigned offset)
{
- struct axp20x_gpio *gpio = gpiochip_get_data(chip);
+ struct axp20x_pctl *pctl = gpiochip_get_data(chip);
unsigned int val;
int ret;
- ret = regmap_read(gpio->regmap, AXP20X_GPIO20_SS, &val);
+ ret = regmap_read(pctl->regmap, AXP20X_GPIO20_SS, &val);
if (ret)
return ret;
@@ -132,12 +132,12 @@ static int axp20x_gpio_get(struct gpio_chip *chip, unsigned offset)
static int axp20x_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
{
- struct axp20x_gpio *gpio = gpiochip_get_data(chip);
- int reg = (int)gpio->desc->pins[offset].pin.drv_data;
+ struct axp20x_pctl *pctl = gpiochip_get_data(chip);
+ int reg = (int)pctl->desc->pins[offset].pin.drv_data;
unsigned int val;
int ret;
- ret = regmap_read(gpio->regmap, reg, &val);
+ ret = regmap_read(pctl->regmap, reg, &val);
if (ret)
return ret;
@@ -167,10 +167,10 @@ static int axp20x_gpio_output(struct gpio_chip *chip, unsigned offset,
static void axp20x_gpio_set(struct gpio_chip *chip, unsigned offset,
int value)
{
- struct axp20x_gpio *gpio = gpiochip_get_data(chip);
- int reg = (int)gpio->desc->pins[offset].pin.drv_data;
+ struct axp20x_pctl *pctl = gpiochip_get_data(chip);
+ int reg = (int)pctl->desc->pins[offset].pin.drv_data;
- regmap_update_bits(gpio->regmap, reg,
+ regmap_update_bits(pctl->regmap, reg,
AXP20X_GPIO_FUNCTIONS,
value ? AXP20X_GPIO_FUNCTION_OUT_HIGH :
AXP20X_GPIO_FUNCTION_OUT_LOW);
@@ -179,26 +179,26 @@ static void axp20x_gpio_set(struct gpio_chip *chip, unsigned offset,
static int axp20x_pmx_set(struct pinctrl_dev *pctldev, unsigned int offset,
u8 config)
{
- struct axp20x_gpio *gpio = pinctrl_dev_get_drvdata(pctldev);
- int reg = (int)gpio->desc->pins[offset].pin.drv_data;
+ struct axp20x_pctl *pctl = pinctrl_dev_get_drvdata(pctldev);
+ int reg = (int)pctl->desc->pins[offset].pin.drv_data;
- return regmap_update_bits(gpio->regmap, reg, AXP20X_GPIO_FUNCTIONS,
+ return regmap_update_bits(pctl->regmap, reg, AXP20X_GPIO_FUNCTIONS,
config);
}
static int axp20x_pmx_func_cnt(struct pinctrl_dev *pctldev)
{
- struct axp20x_gpio *gpio = pinctrl_dev_get_drvdata(pctldev);
+ struct axp20x_pctl *pctl = pinctrl_dev_get_drvdata(pctldev);
- return gpio->nfunctions;
+ return pctl->nfunctions;
}
static const char *axp20x_pmx_func_name(struct pinctrl_dev *pctldev,
unsigned int selector)
{
- struct axp20x_gpio *gpio = pinctrl_dev_get_drvdata(pctldev);
+ struct axp20x_pctl *pctl = pinctrl_dev_get_drvdata(pctldev);
- return gpio->functions[selector].name;
+ return pctl->functions[selector].name;
}
static int axp20x_pmx_func_groups(struct pinctrl_dev *pctldev,
@@ -206,24 +206,24 @@ static int axp20x_pmx_func_groups(struct pinctrl_dev *pctldev,
const char * const **groups,
unsigned int *num_groups)
{
- struct axp20x_gpio *gpio = pinctrl_dev_get_drvdata(pctldev);
+ struct axp20x_pctl *pctl = pinctrl_dev_get_drvdata(pctldev);
- *groups = gpio->functions[selector].groups;
- *num_groups = gpio->functions[selector].ngroups;
+ *groups = pctl->functions[selector].groups;
+ *num_groups = pctl->functions[selector].ngroups;
return 0;
}
static struct axp20x_desc_function *
-axp20x_pinctrl_desc_find_func_by_name(struct axp20x_gpio *gpio,
+axp20x_pinctrl_desc_find_func_by_name(struct axp20x_pctl *pctl,
const char *group, const char *func)
{
const struct axp20x_desc_pin *pin;
struct axp20x_desc_function *desc_func;
int i;
- for (i = 0; i < gpio->desc->npins; i++) {
- pin = &gpio->desc->pins[i];
+ for (i = 0; i < pctl->desc->npins; i++) {
+ pin = &pctl->desc->pins[i];
if (!strcmp(pin->pin.name, group)) {
desc_func = pin->functions;
@@ -250,11 +250,11 @@ axp20x_pinctrl_desc_find_func_by_name(struct axp20x_gpio *gpio,
static int axp20x_pmx_set_mux(struct pinctrl_dev *pctldev,
unsigned int function, unsigned int group)
{
- struct axp20x_gpio *gpio = pinctrl_dev_get_drvdata(pctldev);
- struct axp20x_pinctrl_group *g = gpio->groups + group;
- struct axp20x_pinctrl_function *func = gpio->functions + function;
+ struct axp20x_pctl *pctl = pinctrl_dev_get_drvdata(pctldev);
+ struct axp20x_pinctrl_group *g = pctl->groups + group;
+ struct axp20x_pinctrl_function *func = pctl->functions + function;
struct axp20x_desc_function *desc_func =
- axp20x_pinctrl_desc_find_func_by_name(gpio, g->name,
+ axp20x_pinctrl_desc_find_func_by_name(pctl, g->name,
func->name);
if (!desc_func)
return -EINVAL;
@@ -263,15 +263,15 @@ static int axp20x_pmx_set_mux(struct pinctrl_dev *pctldev,
}
static struct axp20x_desc_function *
-axp20x_pctl_desc_find_func_by_pin(struct axp20x_gpio *gpio, unsigned int offset,
+axp20x_pctl_desc_find_func_by_pin(struct axp20x_pctl *pctl, unsigned int offset,
const char *func)
{
const struct axp20x_desc_pin *pin;
struct axp20x_desc_function *desc_func;
int i;
- for (i = 0; i < gpio->desc->npins; i++) {
- pin = &gpio->desc->pins[i];
+ for (i = 0; i < pctl->desc->npins; i++) {
+ pin = &pctl->desc->pins[i];
if (pin->pin.number == offset) {
desc_func = pin->functions;
@@ -292,7 +292,7 @@ static int axp20x_pmx_gpio_set_direction(struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned int offset, bool input)
{
- struct axp20x_gpio *gpio = pinctrl_dev_get_drvdata(pctldev);
+ struct axp20x_pctl *pctl = pinctrl_dev_get_drvdata(pctldev);
struct axp20x_desc_function *desc_func;
const char *func;
@@ -301,7 +301,7 @@ static int axp20x_pmx_gpio_set_direction(struct pinctrl_dev *pctldev,
else
func = "gpio_out";
- desc_func = axp20x_pctl_desc_find_func_by_pin(gpio, offset, func);
+ desc_func = axp20x_pctl_desc_find_func_by_pin(pctl, offset, func);
if (!desc_func)
return -EINVAL;
@@ -319,16 +319,16 @@ static const struct pinmux_ops axp20x_pmx_ops = {
static int axp20x_groups_cnt(struct pinctrl_dev *pctldev)
{
- struct axp20x_gpio *gpio = pinctrl_dev_get_drvdata(pctldev);
+ struct axp20x_pctl *pctl = pinctrl_dev_get_drvdata(pctldev);
- return gpio->ngroups;
+ return pctl->ngroups;
}
static int axp20x_group_pins(struct pinctrl_dev *pctldev, unsigned int selector,
const unsigned int **pins, unsigned int *num_pins)
{
- struct axp20x_gpio *gpio = pinctrl_dev_get_drvdata(pctldev);
- struct axp20x_pinctrl_group *g = gpio->groups + selector;
+ struct axp20x_pctl *pctl = pinctrl_dev_get_drvdata(pctldev);
+ struct axp20x_pinctrl_group *g = pctl->groups + selector;
*pins = (unsigned int *)&g->pin;
*num_pins = 1;
@@ -339,9 +339,9 @@ static int axp20x_group_pins(struct pinctrl_dev *pctldev, unsigned int selector,
static const char *axp20x_group_name(struct pinctrl_dev *pctldev,
unsigned int selector)
{
- struct axp20x_gpio *gpio = pinctrl_dev_get_drvdata(pctldev);
+ struct axp20x_pctl *pctl = pinctrl_dev_get_drvdata(pctldev);
- return gpio->groups[selector].name;
+ return pctl->groups[selector].name;
}
static const struct pinctrl_ops axp20x_pctrl_ops = {
@@ -353,9 +353,9 @@ static const struct pinctrl_ops axp20x_pctrl_ops = {
};
static struct axp20x_pinctrl_function *
-axp20x_pinctrl_function_by_name(struct axp20x_gpio *gpio, const char *name)
+axp20x_pinctrl_function_by_name(struct axp20x_pctl *pctl, const char *name)
{
- struct axp20x_pinctrl_function *func = gpio->functions;
+ struct axp20x_pinctrl_function *func = pctl->functions;
while (func->name) {
if (!strcmp(func->name, name))
@@ -366,10 +366,10 @@ axp20x_pinctrl_function_by_name(struct axp20x_gpio *gpio, const char *name)
return NULL;
}
-static int axp20x_pinctrl_add_function(struct axp20x_gpio *gpio,
+static int axp20x_pinctrl_add_function(struct axp20x_pctl *pctl,
const char *name)
{
- struct axp20x_pinctrl_function *func = gpio->functions;
+ struct axp20x_pinctrl_function *func = pctl->functions;
while (func->name) {
if (!strcmp(func->name, name)) {
@@ -383,7 +383,7 @@ static int axp20x_pinctrl_add_function(struct axp20x_gpio *gpio,
func->name = name;
func->ngroups = 1;
- gpio->nfunctions++;
+ pctl->nfunctions++;
return 0;
}
@@ -391,13 +391,13 @@ static int axp20x_pinctrl_add_function(struct axp20x_gpio *gpio,
static int axp20x_attach_group_function(struct platform_device *pdev,
const struct axp20x_desc_pin *pin)
{
- struct axp20x_gpio *gpio = platform_get_drvdata(pdev);
+ struct axp20x_pctl *pctl = platform_get_drvdata(pdev);
struct axp20x_desc_function *desc_func = pin->functions;
struct axp20x_pinctrl_function *func;
const char **func_grp;
while (desc_func->name) {
- func = axp20x_pinctrl_function_by_name(gpio, desc_func->name);
+ func = axp20x_pinctrl_function_by_name(pctl, desc_func->name);
if (!func)
return -EINVAL;
@@ -422,48 +422,48 @@ static int axp20x_attach_group_function(struct platform_device *pdev,
static int axp20x_build_state(struct platform_device *pdev)
{
- struct axp20x_gpio *gpio = platform_get_drvdata(pdev);
- unsigned int npins = gpio->desc->npins;
+ struct axp20x_pctl *pctl = platform_get_drvdata(pdev);
+ unsigned int npins = pctl->desc->npins;
const struct axp20x_desc_pin *pin;
struct axp20x_desc_function *func;
int i, ret;
- gpio->ngroups = npins;
- gpio->groups = devm_kzalloc(&pdev->dev,
- gpio->ngroups * sizeof(*gpio->groups),
+ pctl->ngroups = npins;
+ pctl->groups = devm_kzalloc(&pdev->dev,
+ pctl->ngroups * sizeof(*pctl->groups),
GFP_KERNEL);
- if (!gpio->groups)
+ if (!pctl->groups)
return -ENOMEM;
for (i = 0; i < npins; i++) {
- gpio->groups[i].name = gpio->desc->pins[i].pin.name;
- gpio->groups[i].pin = gpio->desc->pins[i].pin.number;
+ pctl->groups[i].name = pctl->desc->pins[i].pin.name;
+ pctl->groups[i].pin = pctl->desc->pins[i].pin.number;
}
/* We assume 4 functions per pin should be enough as a default max */
- gpio->functions = devm_kzalloc(&pdev->dev,
- npins * 4 * sizeof(*gpio->functions),
+ pctl->functions = devm_kzalloc(&pdev->dev,
+ npins * 4 * sizeof(*pctl->functions),
GFP_KERNEL);
- if (!gpio->functions)
+ if (!pctl->functions)
return -ENOMEM;
/* Create a list of uniquely named functions */
for (i = 0; i < npins; i++) {
- pin = &gpio->desc->pins[i];
+ pin = &pctl->desc->pins[i];
func = pin->functions;
while (func->name) {
- axp20x_pinctrl_add_function(gpio, func->name);
+ axp20x_pinctrl_add_function(pctl, func->name);
func++;
}
}
- gpio->functions = krealloc(gpio->functions,
- gpio->nfunctions * sizeof(*gpio->functions),
+ pctl->functions = krealloc(pctl->functions,
+ pctl->nfunctions * sizeof(*pctl->functions),
GFP_KERNEL);
for (i = 0; i < npins; i++) {
- pin = &gpio->desc->pins[i];
+ pin = &pctl->desc->pins[i];
ret = axp20x_attach_group_function(pdev, pin);
if (ret)
return ret;
@@ -472,10 +472,10 @@ static int axp20x_build_state(struct platform_device *pdev)
return 0;
}
-static int axp20x_gpio_probe(struct platform_device *pdev)
+static int axp20x_pctl_probe(struct platform_device *pdev)
{
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
- struct axp20x_gpio *gpio;
+ struct axp20x_pctl *pctl;
struct pinctrl_desc *pctrl_desc;
struct pinctrl_pin_desc *pins;
int ret, i;
@@ -488,42 +488,42 @@ static int axp20x_gpio_probe(struct platform_device *pdev)
return -EINVAL;
}
- gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
- if (!gpio)
+ pctl = devm_kzalloc(&pdev->dev, sizeof(*pctl), GFP_KERNEL);
+ if (!pctl)
return -ENOMEM;
- gpio->chip.base = -1;
- gpio->chip.can_sleep = true;
- gpio->chip.request = gpiochip_generic_request;
- gpio->chip.free = gpiochip_generic_free;
- gpio->chip.parent = &pdev->dev;
- gpio->chip.label = dev_name(&pdev->dev);
- gpio->chip.owner = THIS_MODULE;
- gpio->chip.get = axp20x_gpio_get;
- gpio->chip.get_direction = axp20x_gpio_get_direction;
- gpio->chip.set = axp20x_gpio_set;
- gpio->chip.direction_input = axp20x_gpio_input;
- gpio->chip.direction_output = axp20x_gpio_output;
- gpio->chip.ngpio = 3;
+ pctl->chip.base = -1;
+ pctl->chip.can_sleep = true;
+ pctl->chip.request = gpiochip_generic_request;
+ pctl->chip.free = gpiochip_generic_free;
+ pctl->chip.parent = &pdev->dev;
+ pctl->chip.label = dev_name(&pdev->dev);
+ pctl->chip.owner = THIS_MODULE;
+ pctl->chip.get = axp20x_gpio_get;
+ pctl->chip.get_direction = axp20x_gpio_get_direction;
+ pctl->chip.set = axp20x_gpio_set;
+ pctl->chip.direction_input = axp20x_gpio_input;
+ pctl->chip.direction_output = axp20x_gpio_output;
+ pctl->chip.ngpio = 3;
- gpio->regmap = axp20x->regmap;
+ pctl->regmap = axp20x->regmap;
- gpio->desc = &axp20x_pinctrl_data;
- gpio->dev = &pdev->dev;
+ pctl->desc = &axp20x_pinctrl_data;
+ pctl->dev = &pdev->dev;
- platform_set_drvdata(pdev, gpio);
+ platform_set_drvdata(pdev, pctl);
ret = axp20x_build_state(pdev);
if (ret)
return ret;
- pins = devm_kzalloc(&pdev->dev, gpio->desc->npins * sizeof(*pins),
+ pins = devm_kzalloc(&pdev->dev, pctl->desc->npins * sizeof(*pins),
GFP_KERNEL);
if (!pins)
return -ENOMEM;
- for (i = 0; i < gpio->desc->npins; i++)
- pins[i] = gpio->desc->pins[i].pin;
+ for (i = 0; i < pctl->desc->npins; i++)
+ pins[i] = pctl->desc->pins[i].pin;
pctrl_desc = devm_kzalloc(&pdev->dev, sizeof(*pctrl_desc), GFP_KERNEL);
if (!pctrl_desc)
@@ -532,26 +532,26 @@ static int axp20x_gpio_probe(struct platform_device *pdev)
pctrl_desc->name = dev_name(&pdev->dev);
pctrl_desc->owner = THIS_MODULE;
pctrl_desc->pins = pins;
- pctrl_desc->npins = gpio->desc->npins;
+ pctrl_desc->npins = pctl->desc->npins;
pctrl_desc->pctlops = &axp20x_pctrl_ops;
pctrl_desc->pmxops = &axp20x_pmx_ops;
- gpio->pctl_dev = devm_pinctrl_register(&pdev->dev, pctrl_desc, gpio);
- if (IS_ERR(gpio->pctl_dev)) {
+ pctl->pctl_dev = devm_pinctrl_register(&pdev->dev, pctrl_desc, pctl);
+ if (IS_ERR(pctl->pctl_dev)) {
dev_err(&pdev->dev, "couldn't register pinctrl driver\n");
- return PTR_ERR(gpio->pctl_dev);
+ return PTR_ERR(pctl->pctl_dev);
}
- ret = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio);
+ ret = devm_gpiochip_add_data(&pdev->dev, &pctl->chip, pctl);
if (ret) {
dev_err(&pdev->dev, "Failed to register GPIO chip\n");
return ret;
}
- ret = gpiochip_add_pin_range(&gpio->chip, dev_name(&pdev->dev),
- gpio->desc->pins->pin.number,
- gpio->desc->pins->pin.number,
- gpio->desc->npins);
+ ret = gpiochip_add_pin_range(&pctl->chip, dev_name(&pdev->dev),
+ pctl->desc->pins->pin.number,
+ pctl->desc->pins->pin.number,
+ pctl->desc->npins);
if (ret) {
dev_err(&pdev->dev, "failed to add pin range\n");
return ret;
@@ -562,21 +562,21 @@ static int axp20x_gpio_probe(struct platform_device *pdev)
return 0;
}
-static const struct of_device_id axp20x_gpio_match[] = {
+static const struct of_device_id axp20x_pctl_match[] = {
{ .compatible = "x-powers,axp209-gpio" },
{ }
};
-MODULE_DEVICE_TABLE(of, axp20x_gpio_match);
+MODULE_DEVICE_TABLE(of, axp20x_pctl_match);
-static struct platform_driver axp20x_gpio_driver = {
- .probe = axp20x_gpio_probe,
+static struct platform_driver axp20x_pctl_driver = {
+ .probe = axp20x_pctl_probe,
.driver = {
.name = "axp20x-gpio",
- .of_match_table = axp20x_gpio_match,
+ .of_match_table = axp20x_pctl_match,
},
};
-module_platform_driver(axp20x_gpio_driver);
+module_platform_driver(axp20x_pctl_driver);
MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
--
git-series 0.9.1
[toc] | [prev] | [next] | [standalone]
| From | Quentin Schulz <quentin.schulz@free-electrons.com> |
|---|---|
| Date | 2017-09-26 14:30 +0200 |
| Subject | [PATCH v2 02/10] pinctrl: axp209: add pinctrl features |
| Message-ID | <utX9V-4JB-43@gated-at.bofh.it> |
| In reply to | #1739838 |
The X-Powers AXP209 has 3 GPIOs. GPIO0/1 can each act either as a GPIO,
an ADC or a LDO regulator. GPIO2 can only act as a GPIO.
This adds the pinctrl features to the driver so GPIO0/1 can be used as
ADC or LDO regulator.
Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
---
Documentation/devicetree/bindings/pinctrl/pinctrl-axp209.txt | 28 +-
drivers/pinctrl/pinctrl-axp209.c | 462 ++++++-
2 files changed, 468 insertions(+), 22 deletions(-)
diff --git a/Documentation/devicetree/bindings/pinctrl/pinctrl-axp209.txt b/Documentation/devicetree/bindings/pinctrl/pinctrl-axp209.txt
index a661130..a5bfe87 100644
--- a/Documentation/devicetree/bindings/pinctrl/pinctrl-axp209.txt
+++ b/Documentation/devicetree/bindings/pinctrl/pinctrl-axp209.txt
@@ -1,4 +1,4 @@
-AXP209 GPIO controller
+AXP209 GPIO & pinctrl controller
This driver follows the usual GPIO bindings found in
Documentation/devicetree/bindings/gpio/gpio.txt
@@ -28,3 +28,29 @@ axp209: pmic@34 {
#gpio-cells = <2>;
};
};
+
+The GPIOs can be muxed to other functions and therefore, must be a subnode of
+axp_gpio.
+
+Example:
+
+&axp_gpio {
+ gpio0_adc: gpio0_adc {
+ pin = "GPIO0";
+ function = "adc";
+ };
+};
+
+&example_node {
+ pinctrl-names = "default";
+ pinctrl-0 = <&gpio0_adc>;
+};
+
+GPIOs and their functions
+-------------------------
+
+GPIO | Functions
+------------------------
+GPIO0 | gpio_in, gpio_out, ldo, adc
+GPIO1 | gpio_in, gpio_out, ldo, adc
+GPIO2 | gpio_in, gpio_out
diff --git a/drivers/pinctrl/pinctrl-axp209.c b/drivers/pinctrl/pinctrl-axp209.c
index 4a346b7..b35e8dd 100644
--- a/drivers/pinctrl/pinctrl-axp209.c
+++ b/drivers/pinctrl/pinctrl-axp209.c
@@ -1,7 +1,8 @@
/*
- * AXP20x GPIO driver
+ * AXP20x pinctrl and GPIO driver
*
* Copyright (C) 2016 Maxime Ripard <maxime.ripard@free-electrons.com>
+ * Copyright (C) 201 Quentin Schulz <quentin.schulz@free-electrons.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
@@ -22,14 +23,92 @@
#include <linux/regmap.h>
#include <linux/slab.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/pinconf-generic.h>
+
#define AXP20X_GPIO_FUNCTIONS 0x7
#define AXP20X_GPIO_FUNCTION_OUT_LOW 0
#define AXP20X_GPIO_FUNCTION_OUT_HIGH 1
#define AXP20X_GPIO_FUNCTION_INPUT 2
+#define AXP20X_PINCTRL_PIN(_pin_num, _pin) \
+ { \
+ .number = _pin_num, \
+ .name = _pin, \
+ }
+
+#define AXP20X_PIN(_pin, ...) \
+ { \
+ .pin = _pin, \
+ .functions = (struct axp20x_desc_function[]) { \
+ __VA_ARGS__, { } }, \
+ }
+
+#define AXP20X_FUNCTION(_val, _name) \
+ { \
+ .name = _name, \
+ .muxval = _val, \
+ }
+
+struct axp20x_desc_function {
+ const char *name;
+ u8 muxval;
+};
+
+struct axp20x_desc_pin {
+ struct pinctrl_pin_desc pin;
+ struct axp20x_desc_function *functions;
+};
+
+struct axp20x_pinctrl_desc {
+ const struct axp20x_desc_pin *pins;
+ int npins;
+};
+
+struct axp20x_pinctrl_group {
+ const char *name;
+ unsigned long config;
+ unsigned int pin;
+};
+
+struct axp20x_pinctrl_function {
+ const char *name;
+ const char **groups;
+ unsigned int ngroups;
+};
+
struct axp20x_gpio {
struct gpio_chip chip;
struct regmap *regmap;
+ struct pinctrl_dev *pctl_dev;
+ struct device *dev;
+ const struct axp20x_pinctrl_desc *desc;
+ struct axp20x_pinctrl_group *groups;
+ unsigned int ngroups;
+ struct axp20x_pinctrl_function *functions;
+ unsigned int nfunctions;
+};
+
+static const struct axp20x_desc_pin axp209_pins[] = {
+ AXP20X_PIN(AXP20X_PINCTRL_PIN(0, "GPIO0"),
+ AXP20X_FUNCTION(0x0, "gpio_out"),
+ AXP20X_FUNCTION(0x2, "gpio_in"),
+ AXP20X_FUNCTION(0x3, "ldo"),
+ AXP20X_FUNCTION(0x4, "adc")),
+ AXP20X_PIN(AXP20X_PINCTRL_PIN(1, "GPIO1"),
+ AXP20X_FUNCTION(0x0, "gpio_out"),
+ AXP20X_FUNCTION(0x2, "gpio_in"),
+ AXP20X_FUNCTION(0x3, "ldo"),
+ AXP20X_FUNCTION(0x4, "adc")),
+ AXP20X_PIN(AXP20X_PINCTRL_PIN(2, "GPIO2"),
+ AXP20X_FUNCTION(0x0, "gpio_out"),
+ AXP20X_FUNCTION(0x2, "gpio_in")),
+};
+
+static const struct axp20x_pinctrl_desc axp20x_pinctrl_data = {
+ .pins = axp209_pins,
+ .npins = ARRAY_SIZE(axp209_pins),
};
static int axp20x_gpio_get_reg(unsigned offset)
@@ -48,16 +127,7 @@ static int axp20x_gpio_get_reg(unsigned offset)
static int axp20x_gpio_input(struct gpio_chip *chip, unsigned offset)
{
- struct axp20x_gpio *gpio = gpiochip_get_data(chip);
- int reg;
-
- reg = axp20x_gpio_get_reg(offset);
- if (reg < 0)
- return reg;
-
- return regmap_update_bits(gpio->regmap, reg,
- AXP20X_GPIO_FUNCTIONS,
- AXP20X_GPIO_FUNCTION_INPUT);
+ return pinctrl_gpio_direction_input(chip->base + offset);
}
static int axp20x_gpio_get(struct gpio_chip *chip, unsigned offset)
@@ -105,30 +175,334 @@ static int axp20x_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
static int axp20x_gpio_output(struct gpio_chip *chip, unsigned offset,
int value)
{
+ chip->set(chip, offset, value);
+
+ return 0;
+}
+
+static void axp20x_gpio_set(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
struct axp20x_gpio *gpio = gpiochip_get_data(chip);
int reg;
reg = axp20x_gpio_get_reg(offset);
if (reg < 0)
+ return;
+
+ regmap_update_bits(gpio->regmap, reg,
+ AXP20X_GPIO_FUNCTIONS,
+ value ? AXP20X_GPIO_FUNCTION_OUT_HIGH :
+ AXP20X_GPIO_FUNCTION_OUT_LOW);
+}
+
+static int axp20x_pmx_set(struct pinctrl_dev *pctldev, unsigned int offset,
+ u8 config)
+{
+ struct axp20x_gpio *gpio = pinctrl_dev_get_drvdata(pctldev);
+ int reg;
+
+ reg = axp20x_gpio_get_reg(offset);
+ if (reg < 0)
return reg;
- return regmap_update_bits(gpio->regmap, reg,
- AXP20X_GPIO_FUNCTIONS,
- value ? AXP20X_GPIO_FUNCTION_OUT_HIGH
- : AXP20X_GPIO_FUNCTION_OUT_LOW);
+ return regmap_update_bits(gpio->regmap, reg, AXP20X_GPIO_FUNCTIONS,
+ config);
}
-static void axp20x_gpio_set(struct gpio_chip *chip, unsigned offset,
- int value)
+static int axp20x_pmx_func_cnt(struct pinctrl_dev *pctldev)
+{
+ struct axp20x_gpio *gpio = pinctrl_dev_get_drvdata(pctldev);
+
+ return gpio->nfunctions;
+}
+
+static const char *axp20x_pmx_func_name(struct pinctrl_dev *pctldev,
+ unsigned int selector)
{
- axp20x_gpio_output(chip, offset, value);
+ struct axp20x_gpio *gpio = pinctrl_dev_get_drvdata(pctldev);
+
+ return gpio->functions[selector].name;
+}
+
+static int axp20x_pmx_func_groups(struct pinctrl_dev *pctldev,
+ unsigned int selector,
+ const char * const **groups,
+ unsigned int *num_groups)
+{
+ struct axp20x_gpio *gpio = pinctrl_dev_get_drvdata(pctldev);
+
+ *groups = gpio->functions[selector].groups;
+ *num_groups = gpio->functions[selector].ngroups;
+
+ return 0;
+}
+
+static struct axp20x_desc_function *
+axp20x_pinctrl_desc_find_func_by_name(struct axp20x_gpio *gpio,
+ const char *group, const char *func)
+{
+ const struct axp20x_desc_pin *pin;
+ struct axp20x_desc_function *desc_func;
+ int i;
+
+ for (i = 0; i < gpio->desc->npins; i++) {
+ pin = &gpio->desc->pins[i];
+
+ if (!strcmp(pin->pin.name, group)) {
+ desc_func = pin->functions;
+
+ while (desc_func->name) {
+ if (!strcmp(desc_func->name, func))
+ return desc_func;
+ desc_func++;
+ }
+
+ /*
+ * Pins are uniquely named. Groups are named after one
+ * pin name. If one pin matches group name but its
+ * function cannot be found, no other pin will match
+ * group name.
+ */
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+static int axp20x_pmx_set_mux(struct pinctrl_dev *pctldev,
+ unsigned int function, unsigned int group)
+{
+ struct axp20x_gpio *gpio = pinctrl_dev_get_drvdata(pctldev);
+ struct axp20x_pinctrl_group *g = gpio->groups + group;
+ struct axp20x_pinctrl_function *func = gpio->functions + function;
+ struct axp20x_desc_function *desc_func =
+ axp20x_pinctrl_desc_find_func_by_name(gpio, g->name,
+ func->name);
+ if (!desc_func)
+ return -EINVAL;
+
+ return axp20x_pmx_set(pctldev, g->pin, desc_func->muxval);
+}
+
+static struct axp20x_desc_function *
+axp20x_pctl_desc_find_func_by_pin(struct axp20x_gpio *gpio, unsigned int offset,
+ const char *func)
+{
+ const struct axp20x_desc_pin *pin;
+ struct axp20x_desc_function *desc_func;
+ int i;
+
+ for (i = 0; i < gpio->desc->npins; i++) {
+ pin = &gpio->desc->pins[i];
+
+ if (pin->pin.number == offset) {
+ desc_func = pin->functions;
+
+ while (desc_func->name) {
+ if (!strcmp(desc_func->name, func))
+ return desc_func;
+
+ desc_func++;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static int axp20x_pmx_gpio_set_direction(struct pinctrl_dev *pctldev,
+ struct pinctrl_gpio_range *range,
+ unsigned int offset, bool input)
+{
+ struct axp20x_gpio *gpio = pinctrl_dev_get_drvdata(pctldev);
+ struct axp20x_desc_function *desc_func;
+ const char *func;
+
+ if (input)
+ func = "gpio_in";
+ else
+ func = "gpio_out";
+
+ desc_func = axp20x_pctl_desc_find_func_by_pin(gpio, offset, func);
+ if (!desc_func)
+ return -EINVAL;
+
+ return axp20x_pmx_set(pctldev, offset, desc_func->muxval);
+}
+
+static const struct pinmux_ops axp20x_pmx_ops = {
+ .get_functions_count = axp20x_pmx_func_cnt,
+ .get_function_name = axp20x_pmx_func_name,
+ .get_function_groups = axp20x_pmx_func_groups,
+ .set_mux = axp20x_pmx_set_mux,
+ .gpio_set_direction = axp20x_pmx_gpio_set_direction,
+ .strict = true,
+};
+
+static int axp20x_groups_cnt(struct pinctrl_dev *pctldev)
+{
+ struct axp20x_gpio *gpio = pinctrl_dev_get_drvdata(pctldev);
+
+ return gpio->ngroups;
+}
+
+static int axp20x_group_pins(struct pinctrl_dev *pctldev, unsigned int selector,
+ const unsigned int **pins, unsigned int *num_pins)
+{
+ struct axp20x_gpio *gpio = pinctrl_dev_get_drvdata(pctldev);
+ struct axp20x_pinctrl_group *g = gpio->groups + selector;
+
+ *pins = (unsigned int *)&g->pin;
+ *num_pins = 1;
+
+ return 0;
+}
+
+static const char *axp20x_group_name(struct pinctrl_dev *pctldev,
+ unsigned int selector)
+{
+ struct axp20x_gpio *gpio = pinctrl_dev_get_drvdata(pctldev);
+
+ return gpio->groups[selector].name;
+}
+
+static const struct pinctrl_ops axp20x_pctrl_ops = {
+ .dt_node_to_map = pinconf_generic_dt_node_to_map_group,
+ .dt_free_map = pinconf_generic_dt_free_map,
+ .get_groups_count = axp20x_groups_cnt,
+ .get_group_name = axp20x_group_name,
+ .get_group_pins = axp20x_group_pins,
+};
+
+static struct axp20x_pinctrl_function *
+axp20x_pinctrl_function_by_name(struct axp20x_gpio *gpio, const char *name)
+{
+ struct axp20x_pinctrl_function *func = gpio->functions;
+
+ while (func->name) {
+ if (!strcmp(func->name, name))
+ return func;
+ func++;
+ }
+
+ return NULL;
+}
+
+static int axp20x_pinctrl_add_function(struct axp20x_gpio *gpio,
+ const char *name)
+{
+ struct axp20x_pinctrl_function *func = gpio->functions;
+
+ while (func->name) {
+ if (!strcmp(func->name, name)) {
+ func->ngroups++;
+ return -EEXIST;
+ }
+
+ func++;
+ }
+
+ func->name = name;
+ func->ngroups = 1;
+
+ gpio->nfunctions++;
+
+ return 0;
+}
+
+static int axp20x_attach_group_function(struct platform_device *pdev,
+ const struct axp20x_desc_pin *pin)
+{
+ struct axp20x_gpio *gpio = platform_get_drvdata(pdev);
+ struct axp20x_desc_function *desc_func = pin->functions;
+ struct axp20x_pinctrl_function *func;
+ const char **func_grp;
+
+ while (desc_func->name) {
+ func = axp20x_pinctrl_function_by_name(gpio, desc_func->name);
+ if (!func)
+ return -EINVAL;
+
+ if (!func->groups) {
+ func->groups = devm_kzalloc(&pdev->dev,
+ func->ngroups * sizeof(const char *),
+ GFP_KERNEL);
+ if (!func->groups)
+ return -ENOMEM;
+ }
+
+ func_grp = func->groups;
+ while (*func_grp)
+ func_grp++;
+
+ *func_grp = pin->pin.name;
+ desc_func++;
+ }
+
+ return 0;
+}
+
+static int axp20x_build_state(struct platform_device *pdev)
+{
+ struct axp20x_gpio *gpio = platform_get_drvdata(pdev);
+ unsigned int npins = gpio->desc->npins;
+ const struct axp20x_desc_pin *pin;
+ struct axp20x_desc_function *func;
+ int i, ret;
+
+ gpio->ngroups = npins;
+ gpio->groups = devm_kzalloc(&pdev->dev,
+ gpio->ngroups * sizeof(*gpio->groups),
+ GFP_KERNEL);
+ if (!gpio->groups)
+ return -ENOMEM;
+
+ for (i = 0; i < npins; i++) {
+ gpio->groups[i].name = gpio->desc->pins[i].pin.name;
+ gpio->groups[i].pin = gpio->desc->pins[i].pin.number;
+ }
+
+ /* We assume 4 functions per pin should be enough as a default max */
+ gpio->functions = devm_kzalloc(&pdev->dev,
+ npins * 4 * sizeof(*gpio->functions),
+ GFP_KERNEL);
+ if (!gpio->functions)
+ return -ENOMEM;
+
+ /* Create a list of uniquely named functions */
+ for (i = 0; i < npins; i++) {
+ pin = &gpio->desc->pins[i];
+ func = pin->functions;
+
+ while (func->name) {
+ axp20x_pinctrl_add_function(gpio, func->name);
+ func++;
+ }
+ }
+
+ gpio->functions = krealloc(gpio->functions,
+ gpio->nfunctions * sizeof(*gpio->functions),
+ GFP_KERNEL);
+
+ for (i = 0; i < npins; i++) {
+ pin = &gpio->desc->pins[i];
+ ret = axp20x_attach_group_function(pdev, pin);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
}
static int axp20x_gpio_probe(struct platform_device *pdev)
{
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
struct axp20x_gpio *gpio;
- int ret;
+ struct pinctrl_desc *pctrl_desc;
+ struct pinctrl_pin_desc *pins;
+ int ret, i;
if (!of_device_is_available(pdev->dev.of_node))
return -ENODEV;
@@ -144,6 +518,8 @@ static int axp20x_gpio_probe(struct platform_device *pdev)
gpio->chip.base = -1;
gpio->chip.can_sleep = true;
+ gpio->chip.request = gpiochip_generic_request;
+ gpio->chip.free = gpiochip_generic_free;
gpio->chip.parent = &pdev->dev;
gpio->chip.label = dev_name(&pdev->dev);
gpio->chip.owner = THIS_MODULE;
@@ -156,13 +532,56 @@ static int axp20x_gpio_probe(struct platform_device *pdev)
gpio->regmap = axp20x->regmap;
+ gpio->desc = &axp20x_pinctrl_data;
+ gpio->dev = &pdev->dev;
+
+ platform_set_drvdata(pdev, gpio);
+
+ ret = axp20x_build_state(pdev);
+ if (ret)
+ return ret;
+
+ pins = devm_kzalloc(&pdev->dev, gpio->desc->npins * sizeof(*pins),
+ GFP_KERNEL);
+ if (!pins)
+ return -ENOMEM;
+
+ for (i = 0; i < gpio->desc->npins; i++)
+ pins[i] = gpio->desc->pins[i].pin;
+
+ pctrl_desc = devm_kzalloc(&pdev->dev, sizeof(*pctrl_desc), GFP_KERNEL);
+ if (!pctrl_desc)
+ return -ENOMEM;
+
+ pctrl_desc->name = dev_name(&pdev->dev);
+ pctrl_desc->owner = THIS_MODULE;
+ pctrl_desc->pins = pins;
+ pctrl_desc->npins = gpio->desc->npins;
+ pctrl_desc->pctlops = &axp20x_pctrl_ops;
+ pctrl_desc->pmxops = &axp20x_pmx_ops;
+
+ gpio->pctl_dev = devm_pinctrl_register(&pdev->dev, pctrl_desc, gpio);
+ if (IS_ERR(gpio->pctl_dev)) {
+ dev_err(&pdev->dev, "couldn't register pinctrl driver\n");
+ return PTR_ERR(gpio->pctl_dev);
+ }
+
ret = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio);
if (ret) {
dev_err(&pdev->dev, "Failed to register GPIO chip\n");
return ret;
}
- dev_info(&pdev->dev, "AXP209 GPIO driver loaded\n");
+ ret = gpiochip_add_pin_range(&gpio->chip, dev_name(&pdev->dev),
+ gpio->desc->pins->pin.number,
+ gpio->desc->pins->pin.number,
+ gpio->desc->npins);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add pin range\n");
+ return ret;
+ }
+
+ dev_info(&pdev->dev, "AXP209 pinctrl and GPIO driver loaded\n");
return 0;
}
@@ -184,5 +603,6 @@ static struct platform_driver axp20x_gpio_driver = {
module_platform_driver(axp20x_gpio_driver);
MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
-MODULE_DESCRIPTION("AXP20x PMIC GPIO driver");
+MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
+MODULE_DESCRIPTION("AXP20x PMIC pinctrl and GPIO driver");
MODULE_LICENSE("GPL");
--
git-series 0.9.1
[toc] | [prev] | [next] | [standalone]
| From | Maxime Ripard <maxime.ripard@free-electrons.com> |
|---|---|
| Date | 2017-09-26 15:10 +0200 |
| Subject | Re: [PATCH v2 02/10] pinctrl: axp209: add pinctrl features |
| Message-ID | <utXMB-5dg-17@gated-at.bofh.it> |
| In reply to | #1739845 |
[Multipart message — attachments visible in raw view] — view raw
On Tue, Sep 26, 2017 at 12:17:12PM +0000, Quentin Schulz wrote:
> +static const struct axp20x_desc_pin axp209_pins[] = {
> + AXP20X_PIN(AXP20X_PINCTRL_PIN(0, "GPIO0"),
> + AXP20X_FUNCTION(0x0, "gpio_out"),
> + AXP20X_FUNCTION(0x2, "gpio_in"),
> + AXP20X_FUNCTION(0x3, "ldo"),
> + AXP20X_FUNCTION(0x4, "adc")),
> + AXP20X_PIN(AXP20X_PINCTRL_PIN(1, "GPIO1"),
> + AXP20X_FUNCTION(0x0, "gpio_out"),
> + AXP20X_FUNCTION(0x2, "gpio_in"),
> + AXP20X_FUNCTION(0x3, "ldo"),
> + AXP20X_FUNCTION(0x4, "adc")),
> + AXP20X_PIN(AXP20X_PINCTRL_PIN(2, "GPIO2"),
> + AXP20X_FUNCTION(0x0, "gpio_out"),
> + AXP20X_FUNCTION(0x2, "gpio_in")),
> +};
If all the functions are the same, and at the same offset, can't we
just hardcode it, instead of having (and duplicate) all the logic
below?
> + pctrl_desc = devm_kzalloc(&pdev->dev, sizeof(*pctrl_desc), GFP_KERNEL);
> + if (!pctrl_desc)
> + return -ENOMEM;
> +
> + pctrl_desc->name = dev_name(&pdev->dev);
> + pctrl_desc->owner = THIS_MODULE;
> + pctrl_desc->pins = pins;
> + pctrl_desc->npins = gpio->desc->npins;
> + pctrl_desc->pctlops = &axp20x_pctrl_ops;
> + pctrl_desc->pmxops = &axp20x_pmx_ops;
The strict flag needs to be set too in order to avoid concurrent uses
of GPIO and other functions.
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
[toc] | [prev] | [next] | [standalone]
| From | Quentin Schulz <quentin.schulz@free-electrons.com> |
|---|---|
| Date | 2017-09-26 15:20 +0200 |
| Subject | Re: [PATCH v2 02/10] pinctrl: axp209: add pinctrl features |
| Message-ID | <utXWi-5gz-7@gated-at.bofh.it> |
| In reply to | #1739871 |
[Multipart message — attachments visible in raw view] — view raw
Hi Maxime,
On 26/09/2017 15:00, Maxime Ripard wrote:
> On Tue, Sep 26, 2017 at 12:17:12PM +0000, Quentin Schulz wrote:
>> +static const struct axp20x_desc_pin axp209_pins[] = {
>> + AXP20X_PIN(AXP20X_PINCTRL_PIN(0, "GPIO0"),
>> + AXP20X_FUNCTION(0x0, "gpio_out"),
>> + AXP20X_FUNCTION(0x2, "gpio_in"),
>> + AXP20X_FUNCTION(0x3, "ldo"),
>> + AXP20X_FUNCTION(0x4, "adc")),
>> + AXP20X_PIN(AXP20X_PINCTRL_PIN(1, "GPIO1"),
>> + AXP20X_FUNCTION(0x0, "gpio_out"),
>> + AXP20X_FUNCTION(0x2, "gpio_in"),
>> + AXP20X_FUNCTION(0x3, "ldo"),
>> + AXP20X_FUNCTION(0x4, "adc")),
>> + AXP20X_PIN(AXP20X_PINCTRL_PIN(2, "GPIO2"),
>> + AXP20X_FUNCTION(0x0, "gpio_out"),
>> + AXP20X_FUNCTION(0x2, "gpio_in")),
>> +};
>
> If all the functions are the same, and at the same offset, can't we
> just hardcode it, instead of having (and duplicate) all the logic
> below?
>
AXP20X_PIN(AXP20X_PINCTRL_PIN(0, "GPIO0"),
AXP20X_GPIO_OUT,
AXP20X_GPIO_IN,
AXP20X_LDO,
AXP20X_ADC))
That's what you mean?
>> + pctrl_desc = devm_kzalloc(&pdev->dev, sizeof(*pctrl_desc), GFP_KERNEL);
>> + if (!pctrl_desc)
>> + return -ENOMEM;
>> +
>> + pctrl_desc->name = dev_name(&pdev->dev);
>> + pctrl_desc->owner = THIS_MODULE;
>> + pctrl_desc->pins = pins;
>> + pctrl_desc->npins = gpio->desc->npins;
>> + pctrl_desc->pctlops = &axp20x_pctrl_ops;
>> + pctrl_desc->pmxops = &axp20x_pmx_ops;
>
> The strict flag needs to be set too in order to avoid concurrent uses
> of GPIO and other functions.
>
Strict is a property of pinmux_ops struct (pmxops) and it is set.
Thanks,
Quentin
--
Quentin Schulz, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
[toc] | [prev] | [next] | [standalone]
| From | Maxime Ripard <maxime.ripard@free-electrons.com> |
|---|---|
| Date | 2017-09-26 15:30 +0200 |
| Subject | Re: [PATCH v2 02/10] pinctrl: axp209: add pinctrl features |
| Message-ID | <utY5Y-5jU-15@gated-at.bofh.it> |
| In reply to | #1739876 |
[Multipart message — attachments visible in raw view] — view raw
On Tue, Sep 26, 2017 at 01:08:21PM +0000, Quentin Schulz wrote:
> Hi Maxime,
>
> On 26/09/2017 15:00, Maxime Ripard wrote:
> > On Tue, Sep 26, 2017 at 12:17:12PM +0000, Quentin Schulz wrote:
> >> +static const struct axp20x_desc_pin axp209_pins[] = {
> >> + AXP20X_PIN(AXP20X_PINCTRL_PIN(0, "GPIO0"),
> >> + AXP20X_FUNCTION(0x0, "gpio_out"),
> >> + AXP20X_FUNCTION(0x2, "gpio_in"),
> >> + AXP20X_FUNCTION(0x3, "ldo"),
> >> + AXP20X_FUNCTION(0x4, "adc")),
> >> + AXP20X_PIN(AXP20X_PINCTRL_PIN(1, "GPIO1"),
> >> + AXP20X_FUNCTION(0x0, "gpio_out"),
> >> + AXP20X_FUNCTION(0x2, "gpio_in"),
> >> + AXP20X_FUNCTION(0x3, "ldo"),
> >> + AXP20X_FUNCTION(0x4, "adc")),
> >> + AXP20X_PIN(AXP20X_PINCTRL_PIN(2, "GPIO2"),
> >> + AXP20X_FUNCTION(0x0, "gpio_out"),
> >> + AXP20X_FUNCTION(0x2, "gpio_in")),
> >> +};
> >
> > If all the functions are the same, and at the same offset, can't we
> > just hardcode it, instead of having (and duplicate) all the logic
> > below?
> >
>
> AXP20X_PIN(AXP20X_PINCTRL_PIN(0, "GPIO0"),
> AXP20X_GPIO_OUT,
> AXP20X_GPIO_IN,
> AXP20X_LDO,
> AXP20X_ADC))
>
> That's what you mean?
What I mean is:
static int axp20x_get_func(char *func)
{
if (!strcmp(func, "gpio_out"))
return 0;
if (!strcmp(func, "gpio_in"))
return 2;
if (!strcmp(func, "ldo"))
return 3;
if (!strcmp(func, "adc"))
return 4;
return -EINVAL;
}
> >> + pctrl_desc = devm_kzalloc(&pdev->dev, sizeof(*pctrl_desc), GFP_KERNEL);
> >> + if (!pctrl_desc)
> >> + return -ENOMEM;
> >> +
> >> + pctrl_desc->name = dev_name(&pdev->dev);
> >> + pctrl_desc->owner = THIS_MODULE;
> >> + pctrl_desc->pins = pins;
> >> + pctrl_desc->npins = gpio->desc->npins;
> >> + pctrl_desc->pctlops = &axp20x_pctrl_ops;
> >> + pctrl_desc->pmxops = &axp20x_pmx_ops;
> >
> > The strict flag needs to be set too in order to avoid concurrent uses
> > of GPIO and other functions.
> >
>
> Strict is a property of pinmux_ops struct (pmxops) and it is set.
Ah, right, my bad.
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
[toc] | [prev] | [next] | [standalone]
| From | Quentin Schulz <quentin.schulz@free-electrons.com> |
|---|---|
| Date | 2017-09-26 15:40 +0200 |
| Subject | Re: [PATCH v2 02/10] pinctrl: axp209: add pinctrl features |
| Message-ID | <utYfE-5n5-7@gated-at.bofh.it> |
| In reply to | #1739886 |
[Multipart message — attachments visible in raw view] — view raw
On 26/09/2017 15:27, Maxime Ripard wrote:
> On Tue, Sep 26, 2017 at 01:08:21PM +0000, Quentin Schulz wrote:
>> Hi Maxime,
>>
>> On 26/09/2017 15:00, Maxime Ripard wrote:
>>> On Tue, Sep 26, 2017 at 12:17:12PM +0000, Quentin Schulz wrote:
>>>> +static const struct axp20x_desc_pin axp209_pins[] = {
>>>> + AXP20X_PIN(AXP20X_PINCTRL_PIN(0, "GPIO0"),
>>>> + AXP20X_FUNCTION(0x0, "gpio_out"),
>>>> + AXP20X_FUNCTION(0x2, "gpio_in"),
>>>> + AXP20X_FUNCTION(0x3, "ldo"),
>>>> + AXP20X_FUNCTION(0x4, "adc")),
>>>> + AXP20X_PIN(AXP20X_PINCTRL_PIN(1, "GPIO1"),
>>>> + AXP20X_FUNCTION(0x0, "gpio_out"),
>>>> + AXP20X_FUNCTION(0x2, "gpio_in"),
>>>> + AXP20X_FUNCTION(0x3, "ldo"),
>>>> + AXP20X_FUNCTION(0x4, "adc")),
>>>> + AXP20X_PIN(AXP20X_PINCTRL_PIN(2, "GPIO2"),
>>>> + AXP20X_FUNCTION(0x0, "gpio_out"),
>>>> + AXP20X_FUNCTION(0x2, "gpio_in")),
>>>> +};
>>>
>>> If all the functions are the same, and at the same offset, can't we
>>> just hardcode it, instead of having (and duplicate) all the logic
>>> below?
>>>
>>
>> AXP20X_PIN(AXP20X_PINCTRL_PIN(0, "GPIO0"),
>> AXP20X_GPIO_OUT,
>> AXP20X_GPIO_IN,
>> AXP20X_LDO,
>> AXP20X_ADC))
>>
>> That's what you mean?
>
> What I mean is:
>
> static int axp20x_get_func(char *func)
> {
> if (!strcmp(func, "gpio_out"))
> return 0;
>
> if (!strcmp(func, "gpio_in"))
> return 2;
>
> if (!strcmp(func, "ldo"))
> return 3;
>
> if (!strcmp(func, "adc"))
> return 4;
>
> return -EINVAL;
> }
>
GPIO2 on AXP209 does not support ldo nor adc.
GPIO1 on AXP813 does not support adc.
I find it more complex to handle those two cases in a function than by
hardcoding it in structures like above.
Moreover, nothing tells us that it would be the same offset for other PMICs.
Quentin
--
Quentin Schulz, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
[toc] | [prev] | [next] | [standalone]
| From | Maxime Ripard <maxime.ripard@free-electrons.com> |
|---|---|
| Date | 2017-09-27 11:30 +0200 |
| Subject | Re: [PATCH v2 02/10] pinctrl: axp209: add pinctrl features |
| Message-ID | <uugPf-t2-1@gated-at.bofh.it> |
| In reply to | #1739894 |
[Multipart message — attachments visible in raw view] — view raw
On Tue, Sep 26, 2017 at 01:37:37PM +0000, Quentin Schulz wrote:
> On 26/09/2017 15:27, Maxime Ripard wrote:
> > On Tue, Sep 26, 2017 at 01:08:21PM +0000, Quentin Schulz wrote:
> >> Hi Maxime,
> >>
> >> On 26/09/2017 15:00, Maxime Ripard wrote:
> >>> On Tue, Sep 26, 2017 at 12:17:12PM +0000, Quentin Schulz wrote:
> >>>> +static const struct axp20x_desc_pin axp209_pins[] = {
> >>>> + AXP20X_PIN(AXP20X_PINCTRL_PIN(0, "GPIO0"),
> >>>> + AXP20X_FUNCTION(0x0, "gpio_out"),
> >>>> + AXP20X_FUNCTION(0x2, "gpio_in"),
> >>>> + AXP20X_FUNCTION(0x3, "ldo"),
> >>>> + AXP20X_FUNCTION(0x4, "adc")),
> >>>> + AXP20X_PIN(AXP20X_PINCTRL_PIN(1, "GPIO1"),
> >>>> + AXP20X_FUNCTION(0x0, "gpio_out"),
> >>>> + AXP20X_FUNCTION(0x2, "gpio_in"),
> >>>> + AXP20X_FUNCTION(0x3, "ldo"),
> >>>> + AXP20X_FUNCTION(0x4, "adc")),
> >>>> + AXP20X_PIN(AXP20X_PINCTRL_PIN(2, "GPIO2"),
> >>>> + AXP20X_FUNCTION(0x0, "gpio_out"),
> >>>> + AXP20X_FUNCTION(0x2, "gpio_in")),
> >>>> +};
> >>>
> >>> If all the functions are the same, and at the same offset, can't we
> >>> just hardcode it, instead of having (and duplicate) all the logic
> >>> below?
> >>>
> >>
> >> AXP20X_PIN(AXP20X_PINCTRL_PIN(0, "GPIO0"),
> >> AXP20X_GPIO_OUT,
> >> AXP20X_GPIO_IN,
> >> AXP20X_LDO,
> >> AXP20X_ADC))
> >>
> >> That's what you mean?
> >
> > What I mean is:
> >
> > static int axp20x_get_func(char *func)
> > {
> > if (!strcmp(func, "gpio_out"))
> > return 0;
> >
> > if (!strcmp(func, "gpio_in"))
> > return 2;
> >
> > if (!strcmp(func, "ldo"))
> > return 3;
> >
> > if (!strcmp(func, "adc"))
> > return 4;
> >
> > return -EINVAL;
> > }
> >
>
> GPIO2 on AXP209 does not support ldo nor adc.
> GPIO1 on AXP813 does not support adc.
Right, and surely that can be caught as well. This was a global
approach. You could add a bitmap for example to encode whether ldo and
adc are available. It takes two bytes, and two or operations.
> I find it more complex to handle those two cases in a function than by
> hardcoding it in structures like above.
You find more complex to add a 10 lines function than 450 lines of
code that you ripped off from another driver, that generates 4
structures many structures (groups, functions, pins and pins'
functions) and will provide three different lookup methods? Really? :)
It's way overkill for that driver. Most of these lists can be
hardcoded as well.
> Moreover, nothing tells us that it would be the same offset for
> other PMICs.
Again, let's worry about those PMICs when we'll need to support
them. Unless you already have an example in mind of course. Otherwise,
it's just building things on theories that have never been proven (and
might never be).
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
[toc] | [prev] | [standalone]
Back to top | Article view | linux.kernel
csiph-web