mirror of
git://git.yoctoproject.org/linux-yocto.git
synced 2025-10-23 07:23:12 +02:00
Immutable branch between MFD, GPIO and NVMEM due for the v6.16 merge window
-----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEdrbJNaO+IJqU8IdIUa+KL4f8d2EFAmgeEAYACgkQUa+KL4f8 d2EW6g//Q1uxdE113B4WgLZIN3z3BEAqQwsFRjbgXUcqHTRGbek42dh2PW9DLY7A PGK1c63jA+pZPA4+UTTAUl+Hse3DV6ZFE9kpdxsLxAP2w83u3FwTUwOG+dJRRKTR U0EN2FmionjwoZK7rpNGNpYgI1cVos4J8jnVRvc8ete3ZbET5kKZCM9z/LJ9phYG daAbWZXo8Su7ws6awxWUL3NbOFx+jtX9H7U+UtVJUlEjCQ0vwa9mg7L0DC+WMh0B n+eGp6RpNqdprIjoVAAh0/7D9jnMgXtj/vL13ofr+eesVA9O1P9lj9wNIOcZ+GIv LVH/0Cl5dCDjLu0mhrZyuSHi96c1wMdQmBN2/5NGdqYvTo281tSxLWEI7zb26AF9 GhC5TA1OVPAA2zOF1az1bbAs+EmZqkIl9weV7w3Ad37uRLm5J6f6Pjx0gYABFvHW 8Ibny/SDCAe45R81LKG5ITSeJX1athXr5HhW4ShpL10pYQo8VK8tYkW1wtTpcYLc lBjLmD0M/mdNFo/2Y/Dv24KQeZvpM4Z3IMjIE1kZLcITI14AECUnlp60zWuIU+lh Z/U4ytrw3/0ac+gBUJ6bhdH8mxp/MHaw/I1mNKmm016sIfqTV5hhGC2i/kRoZki7 r4XlQViW6LqRXPW4WzVL4zgM0m+nM9jbd43K+pF2WUctthGkQ3I= =2dKC -----END PGP SIGNATURE----- Merge tag 'ib-mfd-gpio-nvmem-v6.16' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd into gpio/for-next Immutable branch between MFD, GPIO and NVMEM due for the v6.16 merge window
This commit is contained in:
commit
6ac28cd232
|
@ -0,0 +1,44 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/gpio/maxim,max77759-gpio.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Maxim Integrated MAX77759 GPIO
|
||||
|
||||
maintainers:
|
||||
- André Draszik <andre.draszik@linaro.org>
|
||||
|
||||
description: |
|
||||
This module is part of the MAX77759 PMIC. For additional information, see
|
||||
Documentation/devicetree/bindings/mfd/maxim,max77759.yaml.
|
||||
|
||||
The MAX77759 is a PMIC integrating, amongst others, a GPIO controller
|
||||
including interrupt support for 2 GPIO lines.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: maxim,max77759-gpio
|
||||
|
||||
"#gpio-cells":
|
||||
const: 2
|
||||
|
||||
gpio-controller: true
|
||||
|
||||
gpio-line-names:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
|
||||
"#interrupt-cells":
|
||||
const: 2
|
||||
|
||||
interrupt-controller: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- "#gpio-cells"
|
||||
- gpio-controller
|
||||
- "#interrupt-cells"
|
||||
- interrupt-controller
|
||||
|
||||
additionalProperties: false
|
99
Documentation/devicetree/bindings/mfd/maxim,max77759.yaml
Normal file
99
Documentation/devicetree/bindings/mfd/maxim,max77759.yaml
Normal file
|
@ -0,0 +1,99 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/mfd/maxim,max77759.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Maxim Integrated MAX77759 PMIC for USB Type-C applications
|
||||
|
||||
maintainers:
|
||||
- André Draszik <andre.draszik@linaro.org>
|
||||
|
||||
description: |
|
||||
This is a part of device tree bindings for the MAX77759 companion Power
|
||||
Management IC for USB Type-C applications.
|
||||
|
||||
The MAX77759 includes Battery Charger, Fuel Gauge, temperature sensors, USB
|
||||
Type-C Port Controller (TCPC), NVMEM, and a GPIO expander.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: maxim,max77759
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
interrupt-controller: true
|
||||
|
||||
"#interrupt-cells":
|
||||
const: 2
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
gpio:
|
||||
$ref: /schemas/gpio/maxim,max77759-gpio.yaml
|
||||
|
||||
nvmem-0:
|
||||
$ref: /schemas/nvmem/maxim,max77759-nvmem.yaml
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- interrupts
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
pmic@66 {
|
||||
compatible = "maxim,max77759";
|
||||
reg = <0x66>;
|
||||
interrupts-extended = <&gpa8 3 IRQ_TYPE_LEVEL_LOW>;
|
||||
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <2>;
|
||||
|
||||
gpio {
|
||||
compatible = "maxim,max77759-gpio";
|
||||
|
||||
gpio-controller;
|
||||
#gpio-cells = <2>;
|
||||
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <2>;
|
||||
};
|
||||
|
||||
nvmem-0 {
|
||||
compatible = "maxim,max77759-nvmem";
|
||||
|
||||
nvmem-layout {
|
||||
compatible = "fixed-layout";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
reboot-mode@0 {
|
||||
reg = <0x0 0x4>;
|
||||
};
|
||||
|
||||
boot-reason@4 {
|
||||
reg = <0x4 0x4>;
|
||||
};
|
||||
|
||||
shutdown-user-flag@8 {
|
||||
reg = <0x8 0x1>;
|
||||
};
|
||||
|
||||
rsoc@10 {
|
||||
reg = <0xa 0x2>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/nvmem/maxim,max77759-nvmem.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Maxim Integrated MAX77759 Non Volatile Memory
|
||||
|
||||
maintainers:
|
||||
- André Draszik <andre.draszik@linaro.org>
|
||||
|
||||
description: |
|
||||
This module is part of the MAX77759 PMIC. For additional information, see
|
||||
Documentation/devicetree/bindings/mfd/maxim,max77759.yaml.
|
||||
|
||||
The MAX77759 is a PMIC integrating, amongst others, Non Volatile Memory
|
||||
(NVMEM) with 30 bytes of storage which can be used by software to store
|
||||
information or communicate with a boot loader.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: maxim,max77759-nvmem
|
||||
|
||||
wp-gpios: false
|
||||
|
||||
required:
|
||||
- compatible
|
||||
|
||||
allOf:
|
||||
- $ref: nvmem.yaml#
|
||||
|
||||
unevaluatedProperties: false
|
10
MAINTAINERS
10
MAINTAINERS
|
@ -14585,6 +14585,16 @@ F: Documentation/devicetree/bindings/mfd/maxim,max77714.yaml
|
|||
F: drivers/mfd/max77714.c
|
||||
F: include/linux/mfd/max77714.h
|
||||
|
||||
MAXIM MAX77759 PMIC MFD DRIVER
|
||||
M: André Draszik <andre.draszik@linaro.org>
|
||||
L: linux-kernel@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/*/maxim,max77759*.yaml
|
||||
F: drivers/gpio/gpio-max77759.c
|
||||
F: drivers/mfd/max77759.c
|
||||
F: drivers/nvmem/max77759-nvmem.c
|
||||
F: include/linux/mfd/max77759.h
|
||||
|
||||
MAXIM MAX77802 PMIC REGULATOR DEVICE DRIVER
|
||||
M: Javier Martinez Canillas <javier@dowhile0.org>
|
||||
L: linux-kernel@vger.kernel.org
|
||||
|
|
|
@ -1483,6 +1483,19 @@ config GPIO_MAX77650
|
|||
GPIO driver for MAX77650/77651 PMIC from Maxim Semiconductor.
|
||||
These chips have a single pin that can be configured as GPIO.
|
||||
|
||||
config GPIO_MAX77759
|
||||
tristate "Maxim Integrated MAX77759 GPIO support"
|
||||
depends on MFD_MAX77759
|
||||
default MFD_MAX77759
|
||||
select GPIOLIB_IRQCHIP
|
||||
help
|
||||
GPIO driver for MAX77759 PMIC from Maxim Integrated.
|
||||
There are two GPIOs available on these chips in total, both of
|
||||
which can also generate interrupts.
|
||||
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called gpio-max77759.
|
||||
|
||||
config GPIO_PALMAS
|
||||
bool "TI PALMAS series PMICs GPIO"
|
||||
depends on MFD_PALMAS
|
||||
|
|
|
@ -106,6 +106,7 @@ obj-$(CONFIG_GPIO_MAX730X) += gpio-max730x.o
|
|||
obj-$(CONFIG_GPIO_MAX732X) += gpio-max732x.o
|
||||
obj-$(CONFIG_GPIO_MAX77620) += gpio-max77620.o
|
||||
obj-$(CONFIG_GPIO_MAX77650) += gpio-max77650.o
|
||||
obj-$(CONFIG_GPIO_MAX77759) += gpio-max77759.o
|
||||
obj-$(CONFIG_GPIO_MB86S7X) += gpio-mb86s7x.o
|
||||
obj-$(CONFIG_GPIO_MC33880) += gpio-mc33880.o
|
||||
obj-$(CONFIG_GPIO_MENZ127) += gpio-menz127.o
|
||||
|
|
530
drivers/gpio/gpio-max77759.c
Normal file
530
drivers/gpio/gpio-max77759.c
Normal file
|
@ -0,0 +1,530 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
//
|
||||
// Copyright 2020 Google Inc
|
||||
// Copyright 2025 Linaro Ltd.
|
||||
//
|
||||
// GPIO driver for Maxim MAX77759
|
||||
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/device/driver.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqreturn.h>
|
||||
#include <linux/lockdep.h>
|
||||
#include <linux/mfd/max77759.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/seq_file.h>
|
||||
|
||||
#define MAX77759_N_GPIOS ARRAY_SIZE(max77759_gpio_line_names)
|
||||
static const char * const max77759_gpio_line_names[] = { "GPIO5", "GPIO6" };
|
||||
|
||||
struct max77759_gpio_chip {
|
||||
struct regmap *map;
|
||||
struct max77759 *max77759;
|
||||
struct gpio_chip gc;
|
||||
struct mutex maxq_lock; /* protect MaxQ r/m/w operations */
|
||||
|
||||
struct mutex irq_lock; /* protect irq bus */
|
||||
int irq_mask;
|
||||
int irq_mask_changed;
|
||||
int irq_trig;
|
||||
int irq_trig_changed;
|
||||
};
|
||||
|
||||
#define MAX77759_GPIOx_TRIGGER(offs, val) (((val) & 1) << (offs))
|
||||
#define MAX77759_GPIOx_TRIGGER_MASK(offs) MAX77759_GPIOx_TRIGGER(offs, ~0)
|
||||
enum max77759_trigger_gpio_type {
|
||||
MAX77759_GPIO_TRIGGER_RISING = 0,
|
||||
MAX77759_GPIO_TRIGGER_FALLING = 1
|
||||
};
|
||||
|
||||
#define MAX77759_GPIOx_DIR(offs, dir) (((dir) & 1) << (2 + (3 * (offs))))
|
||||
#define MAX77759_GPIOx_DIR_MASK(offs) MAX77759_GPIOx_DIR(offs, ~0)
|
||||
enum max77759_control_gpio_dir {
|
||||
MAX77759_GPIO_DIR_IN = 0,
|
||||
MAX77759_GPIO_DIR_OUT = 1
|
||||
};
|
||||
|
||||
#define MAX77759_GPIOx_OUTVAL(offs, val) (((val) & 1) << (3 + (3 * (offs))))
|
||||
#define MAX77759_GPIOx_OUTVAL_MASK(offs) MAX77759_GPIOx_OUTVAL(offs, ~0)
|
||||
|
||||
#define MAX77759_GPIOx_INVAL_MASK(offs) (BIT(4) << (3 * (offs)))
|
||||
|
||||
static int max77759_gpio_maxq_gpio_trigger_read(struct max77759_gpio_chip *chip)
|
||||
{
|
||||
DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length, 1);
|
||||
DEFINE_FLEX(struct max77759_maxq_response, rsp, rsp, length, 2);
|
||||
int ret;
|
||||
|
||||
cmd->cmd[0] = MAX77759_MAXQ_OPCODE_GPIO_TRIGGER_READ;
|
||||
|
||||
ret = max77759_maxq_command(chip->max77759, cmd, rsp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return rsp->rsp[1];
|
||||
}
|
||||
|
||||
static int max77759_gpio_maxq_gpio_trigger_write(struct max77759_gpio_chip *chip,
|
||||
u8 trigger)
|
||||
{
|
||||
DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length, 2);
|
||||
|
||||
cmd->cmd[0] = MAX77759_MAXQ_OPCODE_GPIO_TRIGGER_WRITE;
|
||||
cmd->cmd[1] = trigger;
|
||||
|
||||
return max77759_maxq_command(chip->max77759, cmd, NULL);
|
||||
}
|
||||
|
||||
static int max77759_gpio_maxq_gpio_control_read(struct max77759_gpio_chip *chip)
|
||||
{
|
||||
DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length, 1);
|
||||
DEFINE_FLEX(struct max77759_maxq_response, rsp, rsp, length, 2);
|
||||
int ret;
|
||||
|
||||
cmd->cmd[0] = MAX77759_MAXQ_OPCODE_GPIO_CONTROL_READ;
|
||||
|
||||
ret = max77759_maxq_command(chip->max77759, cmd, rsp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return rsp->rsp[1];
|
||||
}
|
||||
|
||||
static int max77759_gpio_maxq_gpio_control_write(struct max77759_gpio_chip *chip,
|
||||
u8 ctrl)
|
||||
{
|
||||
DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length, 2);
|
||||
|
||||
cmd->cmd[0] = MAX77759_MAXQ_OPCODE_GPIO_CONTROL_WRITE;
|
||||
cmd->cmd[1] = ctrl;
|
||||
|
||||
return max77759_maxq_command(chip->max77759, cmd, NULL);
|
||||
}
|
||||
|
||||
static int
|
||||
max77759_gpio_direction_from_control(int ctrl, unsigned int offset)
|
||||
{
|
||||
enum max77759_control_gpio_dir dir;
|
||||
|
||||
dir = !!(ctrl & MAX77759_GPIOx_DIR_MASK(offset));
|
||||
return ((dir == MAX77759_GPIO_DIR_OUT)
|
||||
? GPIO_LINE_DIRECTION_OUT
|
||||
: GPIO_LINE_DIRECTION_IN);
|
||||
}
|
||||
|
||||
static int max77759_gpio_get_direction(struct gpio_chip *gc,
|
||||
unsigned int offset)
|
||||
{
|
||||
struct max77759_gpio_chip *chip = gpiochip_get_data(gc);
|
||||
int ctrl;
|
||||
|
||||
ctrl = max77759_gpio_maxq_gpio_control_read(chip);
|
||||
if (ctrl < 0)
|
||||
return ctrl;
|
||||
|
||||
return max77759_gpio_direction_from_control(ctrl, offset);
|
||||
}
|
||||
|
||||
static int max77759_gpio_direction_helper(struct gpio_chip *gc,
|
||||
unsigned int offset,
|
||||
enum max77759_control_gpio_dir dir,
|
||||
int value)
|
||||
{
|
||||
struct max77759_gpio_chip *chip = gpiochip_get_data(gc);
|
||||
int ctrl, new_ctrl;
|
||||
|
||||
guard(mutex)(&chip->maxq_lock);
|
||||
|
||||
ctrl = max77759_gpio_maxq_gpio_control_read(chip);
|
||||
if (ctrl < 0)
|
||||
return ctrl;
|
||||
|
||||
new_ctrl = ctrl & ~MAX77759_GPIOx_DIR_MASK(offset);
|
||||
new_ctrl |= MAX77759_GPIOx_DIR(offset, dir);
|
||||
|
||||
if (dir == MAX77759_GPIO_DIR_OUT) {
|
||||
new_ctrl &= ~MAX77759_GPIOx_OUTVAL_MASK(offset);
|
||||
new_ctrl |= MAX77759_GPIOx_OUTVAL(offset, value);
|
||||
}
|
||||
|
||||
if (new_ctrl == ctrl)
|
||||
return 0;
|
||||
|
||||
return max77759_gpio_maxq_gpio_control_write(chip, new_ctrl);
|
||||
}
|
||||
|
||||
static int max77759_gpio_direction_input(struct gpio_chip *gc,
|
||||
unsigned int offset)
|
||||
{
|
||||
return max77759_gpio_direction_helper(gc, offset,
|
||||
MAX77759_GPIO_DIR_IN, -1);
|
||||
}
|
||||
|
||||
static int max77759_gpio_direction_output(struct gpio_chip *gc,
|
||||
unsigned int offset, int value)
|
||||
{
|
||||
return max77759_gpio_direction_helper(gc, offset,
|
||||
MAX77759_GPIO_DIR_OUT, value);
|
||||
}
|
||||
|
||||
static int max77759_gpio_get_value(struct gpio_chip *gc, unsigned int offset)
|
||||
{
|
||||
struct max77759_gpio_chip *chip = gpiochip_get_data(gc);
|
||||
int ctrl, mask;
|
||||
|
||||
ctrl = max77759_gpio_maxq_gpio_control_read(chip);
|
||||
if (ctrl < 0)
|
||||
return ctrl;
|
||||
|
||||
/*
|
||||
* The input status bit doesn't reflect the pin state when the GPIO is
|
||||
* configured as an output. Check the direction, and inspect the input
|
||||
* or output bit accordingly.
|
||||
*/
|
||||
mask = ((max77759_gpio_direction_from_control(ctrl, offset)
|
||||
== GPIO_LINE_DIRECTION_IN)
|
||||
? MAX77759_GPIOx_INVAL_MASK(offset)
|
||||
: MAX77759_GPIOx_OUTVAL_MASK(offset));
|
||||
|
||||
return !!(ctrl & mask);
|
||||
}
|
||||
|
||||
static int max77759_gpio_set_value(struct gpio_chip *gc,
|
||||
unsigned int offset, int value)
|
||||
{
|
||||
struct max77759_gpio_chip *chip = gpiochip_get_data(gc);
|
||||
int ctrl, new_ctrl;
|
||||
|
||||
guard(mutex)(&chip->maxq_lock);
|
||||
|
||||
ctrl = max77759_gpio_maxq_gpio_control_read(chip);
|
||||
if (ctrl < 0)
|
||||
return ctrl;
|
||||
|
||||
new_ctrl = ctrl & ~MAX77759_GPIOx_OUTVAL_MASK(offset);
|
||||
new_ctrl |= MAX77759_GPIOx_OUTVAL(offset, value);
|
||||
|
||||
if (new_ctrl == ctrl)
|
||||
return 0;
|
||||
|
||||
return max77759_gpio_maxq_gpio_control_write(chip, new_ctrl);
|
||||
}
|
||||
|
||||
static void max77759_gpio_irq_mask(struct irq_data *d)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct max77759_gpio_chip *chip = gpiochip_get_data(gc);
|
||||
irq_hw_number_t hwirq = irqd_to_hwirq(d);
|
||||
|
||||
chip->irq_mask &= ~MAX77759_MAXQ_REG_UIC_INT1_GPIOxI_MASK(hwirq);
|
||||
chip->irq_mask |= MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(hwirq, 1);
|
||||
chip->irq_mask_changed |= MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(hwirq, 1);
|
||||
|
||||
gpiochip_disable_irq(gc, hwirq);
|
||||
}
|
||||
|
||||
static void max77759_gpio_irq_unmask(struct irq_data *d)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct max77759_gpio_chip *chip = gpiochip_get_data(gc);
|
||||
irq_hw_number_t hwirq = irqd_to_hwirq(d);
|
||||
|
||||
gpiochip_enable_irq(gc, hwirq);
|
||||
|
||||
chip->irq_mask &= ~MAX77759_MAXQ_REG_UIC_INT1_GPIOxI_MASK(hwirq);
|
||||
chip->irq_mask |= MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(hwirq, 0);
|
||||
chip->irq_mask_changed |= MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(hwirq, 1);
|
||||
}
|
||||
|
||||
static int max77759_gpio_set_irq_type(struct irq_data *d, unsigned int type)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct max77759_gpio_chip *chip = gpiochip_get_data(gc);
|
||||
irq_hw_number_t hwirq = irqd_to_hwirq(d);
|
||||
|
||||
chip->irq_trig &= ~MAX77759_GPIOx_TRIGGER_MASK(hwirq);
|
||||
switch (type) {
|
||||
case IRQ_TYPE_EDGE_RISING:
|
||||
chip->irq_trig |= MAX77759_GPIOx_TRIGGER(hwirq,
|
||||
MAX77759_GPIO_TRIGGER_RISING);
|
||||
break;
|
||||
|
||||
case IRQ_TYPE_EDGE_FALLING:
|
||||
chip->irq_trig |= MAX77759_GPIOx_TRIGGER(hwirq,
|
||||
MAX77759_GPIO_TRIGGER_FALLING);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
chip->irq_trig_changed |= MAX77759_GPIOx_TRIGGER(hwirq, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void max77759_gpio_bus_lock(struct irq_data *d)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct max77759_gpio_chip *chip = gpiochip_get_data(gc);
|
||||
|
||||
mutex_lock(&chip->irq_lock);
|
||||
}
|
||||
|
||||
static int max77759_gpio_bus_sync_unlock_helper(struct gpio_chip *gc,
|
||||
struct max77759_gpio_chip *chip)
|
||||
__must_hold(&chip->maxq_lock)
|
||||
{
|
||||
int ctrl, trigger, new_trigger, new_ctrl;
|
||||
unsigned long irq_trig_changed;
|
||||
int offset;
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&chip->maxq_lock);
|
||||
|
||||
ctrl = max77759_gpio_maxq_gpio_control_read(chip);
|
||||
trigger = max77759_gpio_maxq_gpio_trigger_read(chip);
|
||||
if (ctrl < 0 || trigger < 0) {
|
||||
dev_err(gc->parent, "failed to read current state: %d / %d\n",
|
||||
ctrl, trigger);
|
||||
return (ctrl < 0) ? ctrl : trigger;
|
||||
}
|
||||
|
||||
new_trigger = trigger & ~chip->irq_trig_changed;
|
||||
new_trigger |= (chip->irq_trig & chip->irq_trig_changed);
|
||||
|
||||
/* change GPIO direction if required */
|
||||
new_ctrl = ctrl;
|
||||
irq_trig_changed = chip->irq_trig_changed;
|
||||
for_each_set_bit(offset, &irq_trig_changed, MAX77759_N_GPIOS) {
|
||||
new_ctrl &= ~MAX77759_GPIOx_DIR_MASK(offset);
|
||||
new_ctrl |= MAX77759_GPIOx_DIR(offset, MAX77759_GPIO_DIR_IN);
|
||||
}
|
||||
|
||||
if (new_trigger != trigger) {
|
||||
ret = max77759_gpio_maxq_gpio_trigger_write(chip, new_trigger);
|
||||
if (ret) {
|
||||
dev_err(gc->parent,
|
||||
"failed to write new trigger: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_ctrl != ctrl) {
|
||||
ret = max77759_gpio_maxq_gpio_control_write(chip, new_ctrl);
|
||||
if (ret) {
|
||||
dev_err(gc->parent,
|
||||
"failed to write new control: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
chip->irq_trig_changed = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void max77759_gpio_bus_sync_unlock(struct irq_data *d)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct max77759_gpio_chip *chip = gpiochip_get_data(gc);
|
||||
int ret;
|
||||
|
||||
scoped_guard(mutex, &chip->maxq_lock) {
|
||||
ret = max77759_gpio_bus_sync_unlock_helper(gc, chip);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
ret = regmap_update_bits(chip->map,
|
||||
MAX77759_MAXQ_REG_UIC_INT1_M,
|
||||
chip->irq_mask_changed, chip->irq_mask);
|
||||
if (ret) {
|
||||
dev_err(gc->parent,
|
||||
"failed to update UIC_INT1 irq mask: %d\n", ret);
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
chip->irq_mask_changed = 0;
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&chip->irq_lock);
|
||||
}
|
||||
|
||||
static void max77759_gpio_irq_print_chip(struct irq_data *d, struct seq_file *p)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
|
||||
seq_puts(p, dev_name(gc->parent));
|
||||
}
|
||||
|
||||
static const struct irq_chip max77759_gpio_irq_chip = {
|
||||
.irq_mask = max77759_gpio_irq_mask,
|
||||
.irq_unmask = max77759_gpio_irq_unmask,
|
||||
.irq_set_type = max77759_gpio_set_irq_type,
|
||||
.irq_bus_lock = max77759_gpio_bus_lock,
|
||||
.irq_bus_sync_unlock = max77759_gpio_bus_sync_unlock,
|
||||
.irq_print_chip = max77759_gpio_irq_print_chip,
|
||||
.flags = IRQCHIP_IMMUTABLE,
|
||||
GPIOCHIP_IRQ_RESOURCE_HELPERS,
|
||||
};
|
||||
|
||||
static irqreturn_t max77759_gpio_irqhandler(int irq, void *data)
|
||||
{
|
||||
struct max77759_gpio_chip *chip = data;
|
||||
struct gpio_chip *gc = &chip->gc;
|
||||
bool handled = false;
|
||||
|
||||
/* iterate until no interrupt is pending */
|
||||
while (true) {
|
||||
unsigned int uic_int1;
|
||||
int ret;
|
||||
unsigned long pending;
|
||||
int offset;
|
||||
|
||||
ret = regmap_read(chip->map, MAX77759_MAXQ_REG_UIC_INT1,
|
||||
&uic_int1);
|
||||
if (ret < 0) {
|
||||
dev_err_ratelimited(gc->parent,
|
||||
"failed to read IRQ status: %d\n",
|
||||
ret);
|
||||
/*
|
||||
* If !handled, we have looped not even once, which
|
||||
* means we should return IRQ_NONE in that case (and
|
||||
* of course IRQ_HANDLED otherwise).
|
||||
*/
|
||||
return IRQ_RETVAL(handled);
|
||||
}
|
||||
|
||||
pending = uic_int1;
|
||||
pending &= (MAX77759_MAXQ_REG_UIC_INT1_GPIO6I
|
||||
| MAX77759_MAXQ_REG_UIC_INT1_GPIO5I);
|
||||
if (!pending)
|
||||
break;
|
||||
|
||||
for_each_set_bit(offset, &pending, MAX77759_N_GPIOS) {
|
||||
/*
|
||||
* ACK interrupt by writing 1 to bit 'offset', all
|
||||
* others need to be written as 0. This needs to be
|
||||
* done unconditionally hence regmap_set_bits() is
|
||||
* inappropriate here.
|
||||
*/
|
||||
regmap_write(chip->map, MAX77759_MAXQ_REG_UIC_INT1,
|
||||
BIT(offset));
|
||||
|
||||
handle_nested_irq(irq_find_mapping(gc->irq.domain,
|
||||
offset));
|
||||
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
return IRQ_RETVAL(handled);
|
||||
}
|
||||
|
||||
static int max77759_gpio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct max77759_gpio_chip *chip;
|
||||
int irq;
|
||||
struct gpio_irq_chip *girq;
|
||||
int ret;
|
||||
unsigned long irq_flags;
|
||||
struct irq_data *irqd;
|
||||
|
||||
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
|
||||
if (!chip)
|
||||
return -ENOMEM;
|
||||
|
||||
chip->map = dev_get_regmap(pdev->dev.parent, "maxq");
|
||||
if (!chip->map)
|
||||
return dev_err_probe(&pdev->dev, -ENODEV, "Missing regmap\n");
|
||||
|
||||
irq = platform_get_irq_byname(pdev, "GPI");
|
||||
if (irq < 0)
|
||||
return dev_err_probe(&pdev->dev, irq, "Failed to get IRQ\n");
|
||||
|
||||
chip->max77759 = dev_get_drvdata(pdev->dev.parent);
|
||||
ret = devm_mutex_init(&pdev->dev, &chip->maxq_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = devm_mutex_init(&pdev->dev, &chip->irq_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
chip->gc.base = -1;
|
||||
chip->gc.label = dev_name(&pdev->dev);
|
||||
chip->gc.parent = &pdev->dev;
|
||||
chip->gc.can_sleep = true;
|
||||
|
||||
chip->gc.names = max77759_gpio_line_names;
|
||||
chip->gc.ngpio = MAX77759_N_GPIOS;
|
||||
chip->gc.get_direction = max77759_gpio_get_direction;
|
||||
chip->gc.direction_input = max77759_gpio_direction_input;
|
||||
chip->gc.direction_output = max77759_gpio_direction_output;
|
||||
chip->gc.get = max77759_gpio_get_value;
|
||||
chip->gc.set_rv = max77759_gpio_set_value;
|
||||
|
||||
girq = &chip->gc.irq;
|
||||
gpio_irq_chip_set_chip(girq, &max77759_gpio_irq_chip);
|
||||
/* This will let us handle the parent IRQ in the driver */
|
||||
girq->parent_handler = NULL;
|
||||
girq->num_parents = 0;
|
||||
girq->parents = NULL;
|
||||
girq->default_type = IRQ_TYPE_NONE;
|
||||
girq->handler = handle_simple_irq;
|
||||
girq->threaded = true;
|
||||
|
||||
ret = devm_gpiochip_add_data(&pdev->dev, &chip->gc, chip);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(&pdev->dev, ret,
|
||||
"Failed to add GPIO chip\n");
|
||||
|
||||
irq_flags = IRQF_ONESHOT | IRQF_SHARED;
|
||||
irqd = irq_get_irq_data(irq);
|
||||
if (irqd)
|
||||
irq_flags |= irqd_get_trigger_type(irqd);
|
||||
|
||||
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
|
||||
max77759_gpio_irqhandler, irq_flags,
|
||||
dev_name(&pdev->dev), chip);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(&pdev->dev, ret,
|
||||
"Failed to request IRQ\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id max77759_gpio_of_id[] = {
|
||||
{ .compatible = "maxim,max77759-gpio", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, max77759_gpio_of_id);
|
||||
|
||||
static const struct platform_device_id max77759_gpio_platform_id[] = {
|
||||
{ "max77759-gpio", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, max77759_gpio_platform_id);
|
||||
|
||||
static struct platform_driver max77759_gpio_driver = {
|
||||
.driver = {
|
||||
.name = "max77759-gpio",
|
||||
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
||||
.of_match_table = max77759_gpio_of_id,
|
||||
},
|
||||
.probe = max77759_gpio_probe,
|
||||
.id_table = max77759_gpio_platform_id,
|
||||
};
|
||||
|
||||
module_platform_driver(max77759_gpio_driver);
|
||||
|
||||
MODULE_AUTHOR("André Draszik <andre.draszik@linaro.org>");
|
||||
MODULE_DESCRIPTION("GPIO driver for Maxim MAX77759");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -943,6 +943,26 @@ config MFD_MAX77714
|
|||
drivers must be enabled in order to use each functionality of the
|
||||
device.
|
||||
|
||||
config MFD_MAX77759
|
||||
tristate "Maxim Integrated MAX77759 PMIC"
|
||||
depends on I2C
|
||||
depends on OF
|
||||
select IRQ_DOMAIN
|
||||
select MFD_CORE
|
||||
select REGMAP_I2C
|
||||
select REGMAP_IRQ
|
||||
help
|
||||
Say yes here to add support for Maxim Integrated MAX77759.
|
||||
This is a companion Power Management IC for USB Type-C applications
|
||||
with Battery Charger, Fuel Gauge, temperature sensors, USB Type-C
|
||||
Port Controller (TCPC), NVMEM, and additional GPIO interfaces.
|
||||
This driver provides common support for accessing the device;
|
||||
additional drivers must be enabled in order to use the functionality
|
||||
of the device.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called max77759.
|
||||
|
||||
config MFD_MAX77843
|
||||
bool "Maxim Semiconductor MAX77843 PMIC Support"
|
||||
depends on I2C=y
|
||||
|
|
|
@ -169,6 +169,7 @@ obj-$(CONFIG_MFD_MAX77686) += max77686.o
|
|||
obj-$(CONFIG_MFD_MAX77693) += max77693.o
|
||||
obj-$(CONFIG_MFD_MAX77705) += max77705.o
|
||||
obj-$(CONFIG_MFD_MAX77714) += max77714.o
|
||||
obj-$(CONFIG_MFD_MAX77759) += max77759.o
|
||||
obj-$(CONFIG_MFD_MAX77843) += max77843.o
|
||||
obj-$(CONFIG_MFD_MAX8907) += max8907.o
|
||||
max8925-objs := max8925-core.o max8925-i2c.o
|
||||
|
|
690
drivers/mfd/max77759.c
Normal file
690
drivers/mfd/max77759.c
Normal file
|
@ -0,0 +1,690 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright 2020 Google Inc
|
||||
* Copyright 2025 Linaro Ltd.
|
||||
*
|
||||
* Core driver for Maxim MAX77759 companion PMIC for USB Type-C
|
||||
*/
|
||||
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/max77759.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
/* Chip ID as per MAX77759_PMIC_REG_PMIC_ID */
|
||||
enum {
|
||||
MAX77759_CHIP_ID = 59,
|
||||
};
|
||||
|
||||
enum max77759_i2c_subdev_id {
|
||||
/*
|
||||
* These are arbitrary and simply used to match struct
|
||||
* max77759_i2c_subdev entries to the regmap pointers in struct
|
||||
* max77759 during probe().
|
||||
*/
|
||||
MAX77759_I2C_SUBDEV_ID_MAXQ,
|
||||
MAX77759_I2C_SUBDEV_ID_CHARGER,
|
||||
};
|
||||
|
||||
struct max77759_i2c_subdev {
|
||||
enum max77759_i2c_subdev_id id;
|
||||
const struct regmap_config *cfg;
|
||||
u16 i2c_address;
|
||||
};
|
||||
|
||||
static const struct regmap_range max77759_top_registers[] = {
|
||||
regmap_reg_range(0x00, 0x02), /* PMIC_ID / PMIC_REVISION / OTP_REVISION */
|
||||
regmap_reg_range(0x22, 0x24), /* INTSRC / INTSRCMASK / TOPSYS_INT */
|
||||
regmap_reg_range(0x26, 0x26), /* TOPSYS_INT_MASK */
|
||||
regmap_reg_range(0x40, 0x40), /* I2C_CNFG */
|
||||
regmap_reg_range(0x50, 0x51), /* SWRESET / CONTROL_FG */
|
||||
};
|
||||
|
||||
static const struct regmap_range max77759_top_ro_registers[] = {
|
||||
regmap_reg_range(0x00, 0x02),
|
||||
regmap_reg_range(0x22, 0x22),
|
||||
};
|
||||
|
||||
static const struct regmap_range max77759_top_volatile_registers[] = {
|
||||
regmap_reg_range(0x22, 0x22),
|
||||
regmap_reg_range(0x24, 0x24),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table max77759_top_wr_table = {
|
||||
.yes_ranges = max77759_top_registers,
|
||||
.n_yes_ranges = ARRAY_SIZE(max77759_top_registers),
|
||||
.no_ranges = max77759_top_ro_registers,
|
||||
.n_no_ranges = ARRAY_SIZE(max77759_top_ro_registers),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table max77759_top_rd_table = {
|
||||
.yes_ranges = max77759_top_registers,
|
||||
.n_yes_ranges = ARRAY_SIZE(max77759_top_registers),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table max77759_top_volatile_table = {
|
||||
.yes_ranges = max77759_top_volatile_registers,
|
||||
.n_yes_ranges = ARRAY_SIZE(max77759_top_volatile_registers),
|
||||
};
|
||||
|
||||
static const struct regmap_config max77759_regmap_config_top = {
|
||||
.name = "top",
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = MAX77759_PMIC_REG_CONTROL_FG,
|
||||
.wr_table = &max77759_top_wr_table,
|
||||
.rd_table = &max77759_top_rd_table,
|
||||
.volatile_table = &max77759_top_volatile_table,
|
||||
.num_reg_defaults_raw = MAX77759_PMIC_REG_CONTROL_FG + 1,
|
||||
.cache_type = REGCACHE_FLAT,
|
||||
};
|
||||
|
||||
static const struct regmap_range max77759_maxq_registers[] = {
|
||||
regmap_reg_range(0x60, 0x73), /* Device ID, Rev, INTx, STATUSx, MASKx */
|
||||
regmap_reg_range(0x81, 0xa1), /* AP_DATAOUTx */
|
||||
regmap_reg_range(0xb1, 0xd1), /* AP_DATAINx */
|
||||
regmap_reg_range(0xe0, 0xe0), /* UIC_SWRST */
|
||||
};
|
||||
|
||||
static const struct regmap_range max77759_maxq_ro_registers[] = {
|
||||
regmap_reg_range(0x60, 0x63), /* Device ID, Rev */
|
||||
regmap_reg_range(0x68, 0x6f), /* STATUSx */
|
||||
regmap_reg_range(0xb1, 0xd1),
|
||||
};
|
||||
|
||||
static const struct regmap_range max77759_maxq_volatile_registers[] = {
|
||||
regmap_reg_range(0x64, 0x6f), /* INTx, STATUSx */
|
||||
regmap_reg_range(0xb1, 0xd1),
|
||||
regmap_reg_range(0xe0, 0xe0),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table max77759_maxq_wr_table = {
|
||||
.yes_ranges = max77759_maxq_registers,
|
||||
.n_yes_ranges = ARRAY_SIZE(max77759_maxq_registers),
|
||||
.no_ranges = max77759_maxq_ro_registers,
|
||||
.n_no_ranges = ARRAY_SIZE(max77759_maxq_ro_registers),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table max77759_maxq_rd_table = {
|
||||
.yes_ranges = max77759_maxq_registers,
|
||||
.n_yes_ranges = ARRAY_SIZE(max77759_maxq_registers),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table max77759_maxq_volatile_table = {
|
||||
.yes_ranges = max77759_maxq_volatile_registers,
|
||||
.n_yes_ranges = ARRAY_SIZE(max77759_maxq_volatile_registers),
|
||||
};
|
||||
|
||||
static const struct regmap_config max77759_regmap_config_maxq = {
|
||||
.name = "maxq",
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = MAX77759_MAXQ_REG_UIC_SWRST,
|
||||
.wr_table = &max77759_maxq_wr_table,
|
||||
.rd_table = &max77759_maxq_rd_table,
|
||||
.volatile_table = &max77759_maxq_volatile_table,
|
||||
.num_reg_defaults_raw = MAX77759_MAXQ_REG_UIC_SWRST + 1,
|
||||
.cache_type = REGCACHE_FLAT,
|
||||
};
|
||||
|
||||
static const struct regmap_range max77759_charger_registers[] = {
|
||||
regmap_reg_range(0xb0, 0xcc),
|
||||
};
|
||||
|
||||
static const struct regmap_range max77759_charger_ro_registers[] = {
|
||||
regmap_reg_range(0xb4, 0xb8), /* INT_OK, DETAILS_0x */
|
||||
};
|
||||
|
||||
static const struct regmap_range max77759_charger_volatile_registers[] = {
|
||||
regmap_reg_range(0xb0, 0xb1), /* INTx */
|
||||
regmap_reg_range(0xb4, 0xb8),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table max77759_charger_wr_table = {
|
||||
.yes_ranges = max77759_charger_registers,
|
||||
.n_yes_ranges = ARRAY_SIZE(max77759_charger_registers),
|
||||
.no_ranges = max77759_charger_ro_registers,
|
||||
.n_no_ranges = ARRAY_SIZE(max77759_charger_ro_registers),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table max77759_charger_rd_table = {
|
||||
.yes_ranges = max77759_charger_registers,
|
||||
.n_yes_ranges = ARRAY_SIZE(max77759_charger_registers),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table max77759_charger_volatile_table = {
|
||||
.yes_ranges = max77759_charger_volatile_registers,
|
||||
.n_yes_ranges = ARRAY_SIZE(max77759_charger_volatile_registers),
|
||||
};
|
||||
|
||||
static const struct regmap_config max77759_regmap_config_charger = {
|
||||
.name = "charger",
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = MAX77759_CHGR_REG_CHG_CNFG_19,
|
||||
.wr_table = &max77759_charger_wr_table,
|
||||
.rd_table = &max77759_charger_rd_table,
|
||||
.volatile_table = &max77759_charger_volatile_table,
|
||||
.num_reg_defaults_raw = MAX77759_CHGR_REG_CHG_CNFG_19 + 1,
|
||||
.cache_type = REGCACHE_FLAT,
|
||||
};
|
||||
|
||||
/*
|
||||
* Interrupts - with the following interrupt hierarchy:
|
||||
* pmic IRQs (INTSRC)
|
||||
* - MAXQ_INT: MaxQ IRQs
|
||||
* - UIC_INT1
|
||||
* - APCmdResI
|
||||
* - SysMsgI
|
||||
* - GPIOxI
|
||||
* - TOPSYS_INT: topsys
|
||||
* - TOPSYS_INT
|
||||
* - TSHDN_INT
|
||||
* - SYSOVLO_INT
|
||||
* - SYSUVLO_INT
|
||||
* - FSHIP_NOT_RD
|
||||
* - CHGR_INT: charger
|
||||
* - CHG_INT
|
||||
* - CHG_INT2
|
||||
*/
|
||||
enum {
|
||||
MAX77759_INT_MAXQ,
|
||||
MAX77759_INT_TOPSYS,
|
||||
MAX77759_INT_CHGR,
|
||||
};
|
||||
|
||||
enum {
|
||||
MAX77759_TOPSYS_INT_TSHDN,
|
||||
MAX77759_TOPSYS_INT_SYSOVLO,
|
||||
MAX77759_TOPSYS_INT_SYSUVLO,
|
||||
MAX77759_TOPSYS_INT_FSHIP_NOT_RD,
|
||||
};
|
||||
|
||||
enum {
|
||||
MAX77759_MAXQ_INT_APCMDRESI,
|
||||
MAX77759_MAXQ_INT_SYSMSGI,
|
||||
MAX77759_MAXQ_INT_GPIO,
|
||||
MAX77759_MAXQ_INT_UIC1,
|
||||
MAX77759_MAXQ_INT_UIC2,
|
||||
MAX77759_MAXQ_INT_UIC3,
|
||||
MAX77759_MAXQ_INT_UIC4,
|
||||
};
|
||||
|
||||
enum {
|
||||
MAX77759_CHARGER_INT_1,
|
||||
MAX77759_CHARGER_INT_2,
|
||||
};
|
||||
|
||||
static const struct regmap_irq max77759_pmic_irqs[] = {
|
||||
REGMAP_IRQ_REG(MAX77759_INT_MAXQ, 0, MAX77759_PMIC_REG_INTSRC_MAXQ),
|
||||
REGMAP_IRQ_REG(MAX77759_INT_TOPSYS, 0, MAX77759_PMIC_REG_INTSRC_TOPSYS),
|
||||
REGMAP_IRQ_REG(MAX77759_INT_CHGR, 0, MAX77759_PMIC_REG_INTSRC_CHGR),
|
||||
};
|
||||
|
||||
static const struct regmap_irq max77759_maxq_irqs[] = {
|
||||
REGMAP_IRQ_REG(MAX77759_MAXQ_INT_APCMDRESI, 0, MAX77759_MAXQ_REG_UIC_INT1_APCMDRESI),
|
||||
REGMAP_IRQ_REG(MAX77759_MAXQ_INT_SYSMSGI, 0, MAX77759_MAXQ_REG_UIC_INT1_SYSMSGI),
|
||||
REGMAP_IRQ_REG(MAX77759_MAXQ_INT_GPIO, 0, GENMASK(1, 0)),
|
||||
REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC1, 0, GENMASK(5, 2)),
|
||||
REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC2, 1, GENMASK(7, 0)),
|
||||
REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC3, 2, GENMASK(7, 0)),
|
||||
REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC4, 3, GENMASK(7, 0)),
|
||||
};
|
||||
|
||||
static const struct regmap_irq max77759_topsys_irqs[] = {
|
||||
REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_TSHDN, 0, MAX77759_PMIC_REG_TOPSYS_INT_TSHDN),
|
||||
REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_SYSOVLO, 0, MAX77759_PMIC_REG_TOPSYS_INT_SYSOVLO),
|
||||
REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_SYSUVLO, 0, MAX77759_PMIC_REG_TOPSYS_INT_SYSUVLO),
|
||||
REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_FSHIP_NOT_RD, 0, MAX77759_PMIC_REG_TOPSYS_INT_FSHIP),
|
||||
};
|
||||
|
||||
static const struct regmap_irq max77759_chgr_irqs[] = {
|
||||
REGMAP_IRQ_REG(MAX77759_CHARGER_INT_1, 0, GENMASK(7, 0)),
|
||||
REGMAP_IRQ_REG(MAX77759_CHARGER_INT_2, 1, GENMASK(7, 0)),
|
||||
};
|
||||
|
||||
static const struct regmap_irq_chip max77759_pmic_irq_chip = {
|
||||
.name = "max77759-pmic",
|
||||
/* INTSRC is read-only and doesn't require clearing */
|
||||
.status_base = MAX77759_PMIC_REG_INTSRC,
|
||||
.mask_base = MAX77759_PMIC_REG_INTSRCMASK,
|
||||
.num_regs = 1,
|
||||
.irqs = max77759_pmic_irqs,
|
||||
.num_irqs = ARRAY_SIZE(max77759_pmic_irqs),
|
||||
};
|
||||
|
||||
/*
|
||||
* We can let regmap-irq auto-ack the topsys interrupt bits as required, but
|
||||
* for all others the individual drivers need to know which interrupt bit
|
||||
* exactly is set inside their interrupt handlers, and therefore we can not set
|
||||
* .ack_base for those.
|
||||
*/
|
||||
static const struct regmap_irq_chip max77759_maxq_irq_chip = {
|
||||
.name = "max77759-maxq",
|
||||
.domain_suffix = "MAXQ",
|
||||
.status_base = MAX77759_MAXQ_REG_UIC_INT1,
|
||||
.mask_base = MAX77759_MAXQ_REG_UIC_INT1_M,
|
||||
.num_regs = 4,
|
||||
.irqs = max77759_maxq_irqs,
|
||||
.num_irqs = ARRAY_SIZE(max77759_maxq_irqs),
|
||||
};
|
||||
|
||||
static const struct regmap_irq_chip max77759_topsys_irq_chip = {
|
||||
.name = "max77759-topsys",
|
||||
.domain_suffix = "TOPSYS",
|
||||
.status_base = MAX77759_PMIC_REG_TOPSYS_INT,
|
||||
.mask_base = MAX77759_PMIC_REG_TOPSYS_INT_MASK,
|
||||
.ack_base = MAX77759_PMIC_REG_TOPSYS_INT,
|
||||
.num_regs = 1,
|
||||
.irqs = max77759_topsys_irqs,
|
||||
.num_irqs = ARRAY_SIZE(max77759_topsys_irqs),
|
||||
};
|
||||
|
||||
static const struct regmap_irq_chip max77759_chrg_irq_chip = {
|
||||
.name = "max77759-chgr",
|
||||
.domain_suffix = "CHGR",
|
||||
.status_base = MAX77759_CHGR_REG_CHG_INT,
|
||||
.mask_base = MAX77759_CHGR_REG_CHG_INT_MASK,
|
||||
.num_regs = 2,
|
||||
.irqs = max77759_chgr_irqs,
|
||||
.num_irqs = ARRAY_SIZE(max77759_chgr_irqs),
|
||||
};
|
||||
|
||||
static const struct max77759_i2c_subdev max77759_i2c_subdevs[] = {
|
||||
{
|
||||
.id = MAX77759_I2C_SUBDEV_ID_MAXQ,
|
||||
.cfg = &max77759_regmap_config_maxq,
|
||||
/* I2C address is same as for sub-block 'top' */
|
||||
},
|
||||
{
|
||||
.id = MAX77759_I2C_SUBDEV_ID_CHARGER,
|
||||
.cfg = &max77759_regmap_config_charger,
|
||||
.i2c_address = 0x69,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct resource max77759_gpio_resources[] = {
|
||||
DEFINE_RES_IRQ_NAMED(MAX77759_MAXQ_INT_GPIO, "GPI"),
|
||||
};
|
||||
|
||||
static const struct resource max77759_charger_resources[] = {
|
||||
DEFINE_RES_IRQ_NAMED(MAX77759_CHARGER_INT_1, "INT1"),
|
||||
DEFINE_RES_IRQ_NAMED(MAX77759_CHARGER_INT_2, "INT2"),
|
||||
};
|
||||
|
||||
static const struct mfd_cell max77759_cells[] = {
|
||||
MFD_CELL_OF("max77759-nvmem", NULL, NULL, 0, 0,
|
||||
"maxim,max77759-nvmem"),
|
||||
};
|
||||
|
||||
static const struct mfd_cell max77759_maxq_cells[] = {
|
||||
MFD_CELL_OF("max77759-gpio", max77759_gpio_resources, NULL, 0, 0,
|
||||
"maxim,max77759-gpio"),
|
||||
};
|
||||
|
||||
static const struct mfd_cell max77759_charger_cells[] = {
|
||||
MFD_CELL_RES("max77759-charger", max77759_charger_resources),
|
||||
};
|
||||
|
||||
int max77759_maxq_command(struct max77759 *max77759,
|
||||
const struct max77759_maxq_command *cmd,
|
||||
struct max77759_maxq_response *rsp)
|
||||
{
|
||||
DEFINE_FLEX(struct max77759_maxq_response, _rsp, rsp, length, 1);
|
||||
struct device *dev = regmap_get_device(max77759->regmap_maxq);
|
||||
static const unsigned int timeout_ms = 200;
|
||||
int ret;
|
||||
|
||||
if (cmd->length > MAX77759_MAXQ_OPCODE_MAXLENGTH)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* As a convenience for API users when issuing simple commands, rsp is
|
||||
* allowed to be NULL. In that case we need a temporary here to write
|
||||
* the response to, as we need to verify that the command was indeed
|
||||
* completed correctly.
|
||||
*/
|
||||
if (!rsp)
|
||||
rsp = _rsp;
|
||||
|
||||
if (!rsp->length || rsp->length > MAX77759_MAXQ_OPCODE_MAXLENGTH)
|
||||
return -EINVAL;
|
||||
|
||||
guard(mutex)(&max77759->maxq_lock);
|
||||
|
||||
reinit_completion(&max77759->cmd_done);
|
||||
|
||||
/*
|
||||
* MaxQ latches the message when the DATAOUT32 register is written. If
|
||||
* cmd->length is shorter we still need to write 0 to it.
|
||||
*/
|
||||
ret = regmap_bulk_write(max77759->regmap_maxq,
|
||||
MAX77759_MAXQ_REG_AP_DATAOUT0, cmd->cmd,
|
||||
cmd->length);
|
||||
if (!ret && cmd->length < MAX77759_MAXQ_OPCODE_MAXLENGTH)
|
||||
ret = regmap_write(max77759->regmap_maxq,
|
||||
MAX77759_MAXQ_REG_AP_DATAOUT32, 0);
|
||||
if (ret) {
|
||||
dev_err(dev, "writing command failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Wait for response from MaxQ */
|
||||
if (!wait_for_completion_timeout(&max77759->cmd_done,
|
||||
msecs_to_jiffies(timeout_ms))) {
|
||||
dev_err(dev, "timed out waiting for response\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
ret = regmap_bulk_read(max77759->regmap_maxq,
|
||||
MAX77759_MAXQ_REG_AP_DATAIN0,
|
||||
rsp->rsp, rsp->length);
|
||||
if (ret) {
|
||||
dev_err(dev, "reading response failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* As per the protocol, the first byte of the reply will match the
|
||||
* request.
|
||||
*/
|
||||
if (cmd->cmd[0] != rsp->rsp[0]) {
|
||||
dev_err(dev, "unexpected opcode response for %#.2x: %*ph\n",
|
||||
cmd->cmd[0], (int)rsp->length, rsp->rsp);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(max77759_maxq_command);
|
||||
|
||||
static irqreturn_t apcmdres_irq_handler(int irq, void *irq_data)
|
||||
{
|
||||
struct max77759 *max77759 = irq_data;
|
||||
|
||||
regmap_write(max77759->regmap_maxq, MAX77759_MAXQ_REG_UIC_INT1,
|
||||
MAX77759_MAXQ_REG_UIC_INT1_APCMDRESI);
|
||||
|
||||
complete(&max77759->cmd_done);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int max77759_create_i2c_subdev(struct i2c_client *client,
|
||||
struct max77759 *max77759,
|
||||
const struct max77759_i2c_subdev *sd)
|
||||
{
|
||||
struct i2c_client *sub;
|
||||
struct regmap *regmap;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* If 'sd' has an I2C address, 'sub' will be assigned a new 'dummy'
|
||||
* device, otherwise use it as-is.
|
||||
*/
|
||||
sub = client;
|
||||
if (sd->i2c_address) {
|
||||
sub = devm_i2c_new_dummy_device(&client->dev,
|
||||
client->adapter,
|
||||
sd->i2c_address);
|
||||
|
||||
if (IS_ERR(sub))
|
||||
return dev_err_probe(&client->dev, PTR_ERR(sub),
|
||||
"failed to claim I2C device %s\n",
|
||||
sd->cfg->name);
|
||||
}
|
||||
|
||||
regmap = devm_regmap_init_i2c(sub, sd->cfg);
|
||||
if (IS_ERR(regmap))
|
||||
return dev_err_probe(&sub->dev, PTR_ERR(regmap),
|
||||
"regmap init for '%s' failed\n",
|
||||
sd->cfg->name);
|
||||
|
||||
ret = regmap_attach_dev(&client->dev, regmap, sd->cfg);
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"regmap attach of '%s' failed\n",
|
||||
sd->cfg->name);
|
||||
|
||||
if (sd->id == MAX77759_I2C_SUBDEV_ID_MAXQ)
|
||||
max77759->regmap_maxq = regmap;
|
||||
else if (sd->id == MAX77759_I2C_SUBDEV_ID_CHARGER)
|
||||
max77759->regmap_charger = regmap;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max77759_add_chained_irq_chip(struct device *dev,
|
||||
struct regmap *regmap,
|
||||
int pirq,
|
||||
struct regmap_irq_chip_data *parent,
|
||||
const struct regmap_irq_chip *chip,
|
||||
struct regmap_irq_chip_data **data)
|
||||
{
|
||||
int irq, ret;
|
||||
|
||||
irq = regmap_irq_get_virq(parent, pirq);
|
||||
if (irq < 0)
|
||||
return dev_err_probe(dev, irq,
|
||||
"failed to get parent vIRQ(%d) for chip %s\n",
|
||||
pirq, chip->name);
|
||||
|
||||
ret = devm_regmap_add_irq_chip(dev, regmap, irq,
|
||||
IRQF_ONESHOT | IRQF_SHARED, 0, chip,
|
||||
data);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to add %s IRQ chip\n",
|
||||
chip->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max77759_add_chained_maxq(struct i2c_client *client,
|
||||
struct max77759 *max77759,
|
||||
struct regmap_irq_chip_data *parent)
|
||||
{
|
||||
struct regmap_irq_chip_data *irq_chip_data;
|
||||
int apcmdres_irq;
|
||||
int ret;
|
||||
|
||||
ret = max77759_add_chained_irq_chip(&client->dev,
|
||||
max77759->regmap_maxq,
|
||||
MAX77759_INT_MAXQ,
|
||||
parent,
|
||||
&max77759_maxq_irq_chip,
|
||||
&irq_chip_data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
init_completion(&max77759->cmd_done);
|
||||
apcmdres_irq = regmap_irq_get_virq(irq_chip_data,
|
||||
MAX77759_MAXQ_INT_APCMDRESI);
|
||||
|
||||
ret = devm_request_threaded_irq(&client->dev, apcmdres_irq,
|
||||
NULL, apcmdres_irq_handler,
|
||||
IRQF_ONESHOT | IRQF_SHARED,
|
||||
dev_name(&client->dev), max77759);
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"MAX77759_MAXQ_INT_APCMDRESI failed\n");
|
||||
|
||||
ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO,
|
||||
max77759_maxq_cells,
|
||||
ARRAY_SIZE(max77759_maxq_cells),
|
||||
NULL, 0,
|
||||
regmap_irq_get_domain(irq_chip_data));
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"failed to add child devices (MaxQ)\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max77759_add_chained_topsys(struct i2c_client *client,
|
||||
struct max77759 *max77759,
|
||||
struct regmap_irq_chip_data *parent)
|
||||
{
|
||||
struct regmap_irq_chip_data *irq_chip_data;
|
||||
int ret;
|
||||
|
||||
ret = max77759_add_chained_irq_chip(&client->dev,
|
||||
max77759->regmap_top,
|
||||
MAX77759_INT_TOPSYS,
|
||||
parent,
|
||||
&max77759_topsys_irq_chip,
|
||||
&irq_chip_data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max77759_add_chained_charger(struct i2c_client *client,
|
||||
struct max77759 *max77759,
|
||||
struct regmap_irq_chip_data *parent)
|
||||
{
|
||||
struct regmap_irq_chip_data *irq_chip_data;
|
||||
int ret;
|
||||
|
||||
ret = max77759_add_chained_irq_chip(&client->dev,
|
||||
max77759->regmap_charger,
|
||||
MAX77759_INT_CHGR,
|
||||
parent,
|
||||
&max77759_chrg_irq_chip,
|
||||
&irq_chip_data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO,
|
||||
max77759_charger_cells,
|
||||
ARRAY_SIZE(max77759_charger_cells),
|
||||
NULL, 0,
|
||||
regmap_irq_get_domain(irq_chip_data));
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"failed to add child devices (charger)\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max77759_probe(struct i2c_client *client)
|
||||
{
|
||||
struct regmap_irq_chip_data *irq_chip_data_pmic;
|
||||
struct irq_data *irq_data;
|
||||
struct max77759 *max77759;
|
||||
unsigned long irq_flags;
|
||||
unsigned int pmic_id;
|
||||
int ret;
|
||||
|
||||
max77759 = devm_kzalloc(&client->dev, sizeof(*max77759), GFP_KERNEL);
|
||||
if (!max77759)
|
||||
return -ENOMEM;
|
||||
|
||||
i2c_set_clientdata(client, max77759);
|
||||
|
||||
max77759->regmap_top = devm_regmap_init_i2c(client,
|
||||
&max77759_regmap_config_top);
|
||||
if (IS_ERR(max77759->regmap_top))
|
||||
return dev_err_probe(&client->dev, PTR_ERR(max77759->regmap_top),
|
||||
"regmap init for '%s' failed\n",
|
||||
max77759_regmap_config_top.name);
|
||||
|
||||
ret = regmap_read(max77759->regmap_top,
|
||||
MAX77759_PMIC_REG_PMIC_ID, &pmic_id);
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"unable to read device ID\n");
|
||||
|
||||
if (pmic_id != MAX77759_CHIP_ID)
|
||||
return dev_err_probe(&client->dev, -ENODEV,
|
||||
"unsupported device ID %#.2x (%d)\n",
|
||||
pmic_id, pmic_id);
|
||||
|
||||
ret = devm_mutex_init(&client->dev, &max77759->maxq_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(max77759_i2c_subdevs); i++) {
|
||||
ret = max77759_create_i2c_subdev(client, max77759,
|
||||
&max77759_i2c_subdevs[i]);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
irq_data = irq_get_irq_data(client->irq);
|
||||
if (!irq_data)
|
||||
return dev_err_probe(&client->dev, -EINVAL,
|
||||
"invalid IRQ: %d\n", client->irq);
|
||||
|
||||
irq_flags = IRQF_ONESHOT | IRQF_SHARED;
|
||||
irq_flags |= irqd_get_trigger_type(irq_data);
|
||||
|
||||
ret = devm_regmap_add_irq_chip(&client->dev, max77759->regmap_top,
|
||||
client->irq, irq_flags, 0,
|
||||
&max77759_pmic_irq_chip,
|
||||
&irq_chip_data_pmic);
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"failed to add IRQ chip '%s'\n",
|
||||
max77759_pmic_irq_chip.name);
|
||||
|
||||
ret = max77759_add_chained_maxq(client, max77759, irq_chip_data_pmic);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = max77759_add_chained_topsys(client, max77759, irq_chip_data_pmic);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = max77759_add_chained_charger(client, max77759, irq_chip_data_pmic);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO,
|
||||
max77759_cells, ARRAY_SIZE(max77759_cells),
|
||||
NULL, 0,
|
||||
regmap_irq_get_domain(irq_chip_data_pmic));
|
||||
}
|
||||
|
||||
static const struct i2c_device_id max77759_i2c_id[] = {
|
||||
{ "max77759" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, max77759_i2c_id);
|
||||
|
||||
static const struct of_device_id max77759_of_id[] = {
|
||||
{ .compatible = "maxim,max77759", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, max77759_of_id);
|
||||
|
||||
static struct i2c_driver max77759_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "max77759",
|
||||
.of_match_table = max77759_of_id,
|
||||
},
|
||||
.probe = max77759_probe,
|
||||
.id_table = max77759_i2c_id,
|
||||
};
|
||||
module_i2c_driver(max77759_i2c_driver);
|
||||
|
||||
MODULE_AUTHOR("André Draszik <andre.draszik@linaro.org>");
|
||||
MODULE_DESCRIPTION("Maxim MAX77759 core driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -154,6 +154,18 @@ config NVMEM_LPC18XX_OTP
|
|||
To compile this driver as a module, choose M here: the module
|
||||
will be called nvmem_lpc18xx_otp.
|
||||
|
||||
config NVMEM_MAX77759
|
||||
tristate "Maxim Integrated MAX77759 NVMEM Support"
|
||||
depends on MFD_MAX77759
|
||||
default MFD_MAX77759
|
||||
help
|
||||
Say Y here to include support for the user-accessible storage found
|
||||
in Maxim Integrated MAX77759 PMICs. This IC provides space for 30
|
||||
bytes of storage.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called nvmem-max77759.
|
||||
|
||||
config NVMEM_MESON_EFUSE
|
||||
tristate "Amlogic Meson GX eFuse Support"
|
||||
depends on (ARCH_MESON || COMPILE_TEST) && MESON_SM
|
||||
|
|
|
@ -34,6 +34,8 @@ obj-$(CONFIG_NVMEM_LPC18XX_EEPROM) += nvmem_lpc18xx_eeprom.o
|
|||
nvmem_lpc18xx_eeprom-y := lpc18xx_eeprom.o
|
||||
obj-$(CONFIG_NVMEM_LPC18XX_OTP) += nvmem_lpc18xx_otp.o
|
||||
nvmem_lpc18xx_otp-y := lpc18xx_otp.o
|
||||
obj-$(CONFIG_NVMEM_MAX77759) += nvmem-max77759.o
|
||||
nvmem-max77759-y := max77759-nvmem.o
|
||||
obj-$(CONFIG_NVMEM_MESON_EFUSE) += nvmem_meson_efuse.o
|
||||
nvmem_meson_efuse-y := meson-efuse.o
|
||||
obj-$(CONFIG_NVMEM_MESON_MX_EFUSE) += nvmem_meson_mx_efuse.o
|
||||
|
|
145
drivers/nvmem/max77759-nvmem.c
Normal file
145
drivers/nvmem/max77759-nvmem.c
Normal file
|
@ -0,0 +1,145 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
//
|
||||
// Copyright 2020 Google Inc
|
||||
// Copyright 2025 Linaro Ltd.
|
||||
//
|
||||
// NVMEM driver for Maxim MAX77759
|
||||
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/device/driver.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mfd/max77759.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/nvmem-provider.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
#define MAX77759_NVMEM_OPCODE_HEADER_LEN 3
|
||||
/*
|
||||
* NVMEM commands have a three byte header (which becomes part of the command),
|
||||
* so we need to subtract that.
|
||||
*/
|
||||
#define MAX77759_NVMEM_SIZE (MAX77759_MAXQ_OPCODE_MAXLENGTH \
|
||||
- MAX77759_NVMEM_OPCODE_HEADER_LEN)
|
||||
|
||||
struct max77759_nvmem {
|
||||
struct device *dev;
|
||||
struct max77759 *max77759;
|
||||
};
|
||||
|
||||
static int max77759_nvmem_reg_read(void *priv, unsigned int offset,
|
||||
void *val, size_t bytes)
|
||||
{
|
||||
struct max77759_nvmem *nvmem = priv;
|
||||
DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length,
|
||||
MAX77759_NVMEM_OPCODE_HEADER_LEN);
|
||||
DEFINE_FLEX(struct max77759_maxq_response, rsp, rsp, length,
|
||||
MAX77759_MAXQ_OPCODE_MAXLENGTH);
|
||||
int ret;
|
||||
|
||||
cmd->cmd[0] = MAX77759_MAXQ_OPCODE_USER_SPACE_READ;
|
||||
cmd->cmd[1] = offset;
|
||||
cmd->cmd[2] = bytes;
|
||||
rsp->length = bytes + MAX77759_NVMEM_OPCODE_HEADER_LEN;
|
||||
|
||||
ret = max77759_maxq_command(nvmem->max77759, cmd, rsp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (memcmp(cmd->cmd, rsp->rsp, MAX77759_NVMEM_OPCODE_HEADER_LEN)) {
|
||||
dev_warn(nvmem->dev, "protocol error (read)\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
memcpy(val, &rsp->rsp[MAX77759_NVMEM_OPCODE_HEADER_LEN], bytes);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max77759_nvmem_reg_write(void *priv, unsigned int offset,
|
||||
void *val, size_t bytes)
|
||||
{
|
||||
struct max77759_nvmem *nvmem = priv;
|
||||
DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length,
|
||||
MAX77759_MAXQ_OPCODE_MAXLENGTH);
|
||||
DEFINE_FLEX(struct max77759_maxq_response, rsp, rsp, length,
|
||||
MAX77759_MAXQ_OPCODE_MAXLENGTH);
|
||||
int ret;
|
||||
|
||||
cmd->cmd[0] = MAX77759_MAXQ_OPCODE_USER_SPACE_WRITE;
|
||||
cmd->cmd[1] = offset;
|
||||
cmd->cmd[2] = bytes;
|
||||
memcpy(&cmd->cmd[MAX77759_NVMEM_OPCODE_HEADER_LEN], val, bytes);
|
||||
cmd->length = bytes + MAX77759_NVMEM_OPCODE_HEADER_LEN;
|
||||
rsp->length = cmd->length;
|
||||
|
||||
ret = max77759_maxq_command(nvmem->max77759, cmd, rsp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (memcmp(cmd->cmd, rsp->rsp, cmd->length)) {
|
||||
dev_warn(nvmem->dev, "protocol error (write)\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max77759_nvmem_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct nvmem_config config = {
|
||||
.dev = &pdev->dev,
|
||||
.name = dev_name(&pdev->dev),
|
||||
.id = NVMEM_DEVID_NONE,
|
||||
.type = NVMEM_TYPE_EEPROM,
|
||||
.ignore_wp = true,
|
||||
.size = MAX77759_NVMEM_SIZE,
|
||||
.word_size = sizeof(u8),
|
||||
.stride = sizeof(u8),
|
||||
.reg_read = max77759_nvmem_reg_read,
|
||||
.reg_write = max77759_nvmem_reg_write,
|
||||
};
|
||||
struct max77759_nvmem *nvmem;
|
||||
|
||||
nvmem = devm_kzalloc(&pdev->dev, sizeof(*nvmem), GFP_KERNEL);
|
||||
if (!nvmem)
|
||||
return -ENOMEM;
|
||||
|
||||
nvmem->dev = &pdev->dev;
|
||||
nvmem->max77759 = dev_get_drvdata(pdev->dev.parent);
|
||||
|
||||
config.priv = nvmem;
|
||||
|
||||
return PTR_ERR_OR_ZERO(devm_nvmem_register(config.dev, &config));
|
||||
}
|
||||
|
||||
static const struct of_device_id max77759_nvmem_of_id[] = {
|
||||
{ .compatible = "maxim,max77759-nvmem", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, max77759_nvmem_of_id);
|
||||
|
||||
static const struct platform_device_id max77759_nvmem_platform_id[] = {
|
||||
{ "max77759-nvmem", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, max77759_nvmem_platform_id);
|
||||
|
||||
static struct platform_driver max77759_nvmem_driver = {
|
||||
.driver = {
|
||||
.name = "max77759-nvmem",
|
||||
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
||||
.of_match_table = max77759_nvmem_of_id,
|
||||
},
|
||||
.probe = max77759_nvmem_probe,
|
||||
.id_table = max77759_nvmem_platform_id,
|
||||
};
|
||||
|
||||
module_platform_driver(max77759_nvmem_driver);
|
||||
|
||||
MODULE_AUTHOR("André Draszik <andre.draszik@linaro.org>");
|
||||
MODULE_DESCRIPTION("NVMEM driver for Maxim MAX77759");
|
||||
MODULE_LICENSE("GPL");
|
165
include/linux/mfd/max77759.h
Normal file
165
include/linux/mfd/max77759.h
Normal file
|
@ -0,0 +1,165 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright 2020 Google Inc.
|
||||
* Copyright 2025 Linaro Ltd.
|
||||
*
|
||||
* Maxim MAX77759 core driver
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_MFD_MAX77759_H
|
||||
#define __LINUX_MFD_MAX77759_H
|
||||
|
||||
#include <linux/completion.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#define MAX77759_PMIC_REG_PMIC_ID 0x00
|
||||
#define MAX77759_PMIC_REG_PMIC_REVISION 0x01
|
||||
#define MAX77759_PMIC_REG_OTP_REVISION 0x02
|
||||
#define MAX77759_PMIC_REG_INTSRC 0x22
|
||||
#define MAX77759_PMIC_REG_INTSRCMASK 0x23
|
||||
#define MAX77759_PMIC_REG_INTSRC_MAXQ BIT(3)
|
||||
#define MAX77759_PMIC_REG_INTSRC_TOPSYS BIT(1)
|
||||
#define MAX77759_PMIC_REG_INTSRC_CHGR BIT(0)
|
||||
#define MAX77759_PMIC_REG_TOPSYS_INT 0x24
|
||||
#define MAX77759_PMIC_REG_TOPSYS_INT_MASK 0x26
|
||||
#define MAX77759_PMIC_REG_TOPSYS_INT_TSHDN BIT(6)
|
||||
#define MAX77759_PMIC_REG_TOPSYS_INT_SYSOVLO BIT(5)
|
||||
#define MAX77759_PMIC_REG_TOPSYS_INT_SYSUVLO BIT(4)
|
||||
#define MAX77759_PMIC_REG_TOPSYS_INT_FSHIP BIT(0)
|
||||
#define MAX77759_PMIC_REG_I2C_CNFG 0x40
|
||||
#define MAX77759_PMIC_REG_SWRESET 0x50
|
||||
#define MAX77759_PMIC_REG_CONTROL_FG 0x51
|
||||
|
||||
#define MAX77759_MAXQ_REG_UIC_INT1 0x64
|
||||
#define MAX77759_MAXQ_REG_UIC_INT1_APCMDRESI BIT(7)
|
||||
#define MAX77759_MAXQ_REG_UIC_INT1_SYSMSGI BIT(6)
|
||||
#define MAX77759_MAXQ_REG_UIC_INT1_GPIO6I BIT(1)
|
||||
#define MAX77759_MAXQ_REG_UIC_INT1_GPIO5I BIT(0)
|
||||
#define MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(offs, en) (((en) & 1) << (offs))
|
||||
#define MAX77759_MAXQ_REG_UIC_INT1_GPIOxI_MASK(offs) \
|
||||
MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(offs, ~0)
|
||||
#define MAX77759_MAXQ_REG_UIC_INT2 0x65
|
||||
#define MAX77759_MAXQ_REG_UIC_INT3 0x66
|
||||
#define MAX77759_MAXQ_REG_UIC_INT4 0x67
|
||||
#define MAX77759_MAXQ_REG_UIC_UIC_STATUS1 0x68
|
||||
#define MAX77759_MAXQ_REG_UIC_UIC_STATUS2 0x69
|
||||
#define MAX77759_MAXQ_REG_UIC_UIC_STATUS3 0x6a
|
||||
#define MAX77759_MAXQ_REG_UIC_UIC_STATUS4 0x6b
|
||||
#define MAX77759_MAXQ_REG_UIC_UIC_STATUS5 0x6c
|
||||
#define MAX77759_MAXQ_REG_UIC_UIC_STATUS6 0x6d
|
||||
#define MAX77759_MAXQ_REG_UIC_UIC_STATUS7 0x6f
|
||||
#define MAX77759_MAXQ_REG_UIC_UIC_STATUS8 0x6f
|
||||
#define MAX77759_MAXQ_REG_UIC_INT1_M 0x70
|
||||
#define MAX77759_MAXQ_REG_UIC_INT2_M 0x71
|
||||
#define MAX77759_MAXQ_REG_UIC_INT3_M 0x72
|
||||
#define MAX77759_MAXQ_REG_UIC_INT4_M 0x73
|
||||
#define MAX77759_MAXQ_REG_AP_DATAOUT0 0x81
|
||||
#define MAX77759_MAXQ_REG_AP_DATAOUT32 0xa1
|
||||
#define MAX77759_MAXQ_REG_AP_DATAIN0 0xb1
|
||||
#define MAX77759_MAXQ_REG_UIC_SWRST 0xe0
|
||||
|
||||
#define MAX77759_CHGR_REG_CHG_INT 0xb0
|
||||
#define MAX77759_CHGR_REG_CHG_INT2 0xb1
|
||||
#define MAX77759_CHGR_REG_CHG_INT_MASK 0xb2
|
||||
#define MAX77759_CHGR_REG_CHG_INT2_MASK 0xb3
|
||||
#define MAX77759_CHGR_REG_CHG_INT_OK 0xb4
|
||||
#define MAX77759_CHGR_REG_CHG_DETAILS_00 0xb5
|
||||
#define MAX77759_CHGR_REG_CHG_DETAILS_01 0xb6
|
||||
#define MAX77759_CHGR_REG_CHG_DETAILS_02 0xb7
|
||||
#define MAX77759_CHGR_REG_CHG_DETAILS_03 0xb8
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_00 0xb9
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_01 0xba
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_02 0xbb
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_03 0xbc
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_04 0xbd
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_05 0xbe
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_06 0xbf
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_07 0xc0
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_08 0xc1
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_09 0xc2
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_10 0xc3
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_11 0xc4
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_12 0xc5
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_13 0xc6
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_14 0xc7
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_15 0xc8
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_16 0xc9
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_17 0xca
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_18 0xcb
|
||||
#define MAX77759_CHGR_REG_CHG_CNFG_19 0xcc
|
||||
|
||||
/* MaxQ opcodes for max77759_maxq_command() */
|
||||
#define MAX77759_MAXQ_OPCODE_MAXLENGTH (MAX77759_MAXQ_REG_AP_DATAOUT32 - \
|
||||
MAX77759_MAXQ_REG_AP_DATAOUT0 + \
|
||||
1)
|
||||
|
||||
#define MAX77759_MAXQ_OPCODE_GPIO_TRIGGER_READ 0x21
|
||||
#define MAX77759_MAXQ_OPCODE_GPIO_TRIGGER_WRITE 0x22
|
||||
#define MAX77759_MAXQ_OPCODE_GPIO_CONTROL_READ 0x23
|
||||
#define MAX77759_MAXQ_OPCODE_GPIO_CONTROL_WRITE 0x24
|
||||
#define MAX77759_MAXQ_OPCODE_USER_SPACE_READ 0x81
|
||||
#define MAX77759_MAXQ_OPCODE_USER_SPACE_WRITE 0x82
|
||||
|
||||
/**
|
||||
* struct max77759 - core max77759 internal data structure
|
||||
*
|
||||
* @regmap_top: Regmap for accessing TOP registers
|
||||
* @maxq_lock: Lock for serializing access to MaxQ
|
||||
* @regmap_maxq: Regmap for accessing MaxQ registers
|
||||
* @cmd_done: Used to signal completion of a MaxQ command
|
||||
* @regmap_charger: Regmap for accessing charger registers
|
||||
*
|
||||
* The MAX77759 comprises several sub-blocks, namely TOP, MaxQ, Charger,
|
||||
* Fuel Gauge, and TCPCI.
|
||||
*/
|
||||
struct max77759 {
|
||||
struct regmap *regmap_top;
|
||||
|
||||
/* This protects MaxQ commands - only one can be active */
|
||||
struct mutex maxq_lock;
|
||||
struct regmap *regmap_maxq;
|
||||
struct completion cmd_done;
|
||||
|
||||
struct regmap *regmap_charger;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct max77759_maxq_command - structure containing the MaxQ command to
|
||||
* send
|
||||
*
|
||||
* @length: The number of bytes to send.
|
||||
* @cmd: The data to send.
|
||||
*/
|
||||
struct max77759_maxq_command {
|
||||
u8 length;
|
||||
u8 cmd[] __counted_by(length);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct max77759_maxq_response - structure containing the MaxQ response
|
||||
*
|
||||
* @length: The number of bytes to receive.
|
||||
* @rsp: The data received. Must have at least @length bytes space.
|
||||
*/
|
||||
struct max77759_maxq_response {
|
||||
u8 length;
|
||||
u8 rsp[] __counted_by(length);
|
||||
};
|
||||
|
||||
/**
|
||||
* max77759_maxq_command() - issue a MaxQ command and wait for the response
|
||||
* and associated data
|
||||
*
|
||||
* @max77759: The core max77759 device handle.
|
||||
* @cmd: The command to be sent.
|
||||
* @rsp: Any response data associated with the command will be copied here;
|
||||
* can be %NULL if the command has no response (other than ACK).
|
||||
*
|
||||
* Return: 0 on success, a negative error number otherwise.
|
||||
*/
|
||||
int max77759_maxq_command(struct max77759 *max77759,
|
||||
const struct max77759_maxq_command *cmd,
|
||||
struct max77759_maxq_response *rsp);
|
||||
|
||||
#endif /* __LINUX_MFD_MAX77759_H */
|
Loading…
Reference in New Issue
Block a user