mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-12-27 12:21:22 -05:00
Merge tag 'ib-mfd-gpio-input-pinctrl-pwm-v6.18' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd into next
Sync up with MFD tree to bring in support for MAX7360.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/gpio/maxim,max7360-gpio.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Maxim MAX7360 GPIO controller
|
||||
|
||||
maintainers:
|
||||
- Kamel Bouhara <kamel.bouhara@bootlin.com>
|
||||
- Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
|
||||
|
||||
description: |
|
||||
Maxim MAX7360 GPIO controller, in MAX7360 chipset
|
||||
https://www.analog.com/en/products/max7360.html
|
||||
|
||||
The device provides two series of GPIOs, referred here as GPIOs and GPOs.
|
||||
|
||||
PORT0 to PORT7 pins can be used as GPIOs, with support for interrupts and
|
||||
constant-current mode. These pins will also be used by the rotary encoder and
|
||||
PWM functionalities.
|
||||
|
||||
COL2 to COL7 pins can be used as GPOs, there is no input capability. COL pins
|
||||
will be partitioned, with the first pins being affected to the keypad
|
||||
functionality and the last ones as GPOs.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- maxim,max7360-gpio
|
||||
- maxim,max7360-gpo
|
||||
|
||||
gpio-controller: true
|
||||
|
||||
"#gpio-cells":
|
||||
const: 2
|
||||
|
||||
interrupt-controller: true
|
||||
|
||||
"#interrupt-cells":
|
||||
const: 2
|
||||
|
||||
maxim,constant-current-disable:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description:
|
||||
Bit field, each bit disables constant-current output of the associated
|
||||
GPIO, starting from the least significant bit for the first GPIO.
|
||||
maximum: 0xff
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- gpio-controller
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- maxim,max7360-gpio
|
||||
ngpios: false
|
||||
then:
|
||||
required:
|
||||
- interrupt-controller
|
||||
else:
|
||||
properties:
|
||||
interrupt-controller: false
|
||||
maxim,constant-current-disable: false
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
gpio {
|
||||
compatible = "maxim,max7360-gpio";
|
||||
|
||||
gpio-controller;
|
||||
#gpio-cells = <2>;
|
||||
maxim,constant-current-disable = <0x06>;
|
||||
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <2>;
|
||||
};
|
||||
191
Documentation/devicetree/bindings/mfd/maxim,max7360.yaml
Normal file
191
Documentation/devicetree/bindings/mfd/maxim,max7360.yaml
Normal file
@@ -0,0 +1,191 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/mfd/maxim,max7360.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Maxim MAX7360 Keypad, Rotary encoder, PWM and GPIO controller
|
||||
|
||||
maintainers:
|
||||
- Kamel Bouhara <kamel.bouhara@bootlin.com>
|
||||
- Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
|
||||
|
||||
description: |
|
||||
Maxim MAX7360 device, with following functions:
|
||||
- keypad controller
|
||||
- rotary controller
|
||||
- GPIO and GPO controller
|
||||
- PWM controller
|
||||
|
||||
https://www.analog.com/en/products/max7360.html
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/input/matrix-keymap.yaml#
|
||||
- $ref: /schemas/input/input.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- maxim,max7360
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 2
|
||||
|
||||
interrupt-names:
|
||||
items:
|
||||
- const: inti
|
||||
- const: intk
|
||||
|
||||
keypad-debounce-delay-ms:
|
||||
description: Keypad debounce delay in ms
|
||||
minimum: 9
|
||||
maximum: 40
|
||||
default: 9
|
||||
|
||||
rotary-debounce-delay-ms:
|
||||
description: Rotary encoder debounce delay in ms
|
||||
minimum: 0
|
||||
maximum: 15
|
||||
default: 0
|
||||
|
||||
linux,axis:
|
||||
$ref: /schemas/input/rotary-encoder.yaml#/properties/linux,axis
|
||||
|
||||
rotary-encoder,relative-axis:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
description:
|
||||
Register a relative axis rather than an absolute one.
|
||||
|
||||
rotary-encoder,steps:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
default: 24
|
||||
description:
|
||||
Number of steps in a full turnaround of the
|
||||
encoder. Only relevant for absolute axis. Defaults to 24 which is a
|
||||
typical value for such devices.
|
||||
|
||||
rotary-encoder,rollover:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
description:
|
||||
Automatic rollover when the rotary value becomes
|
||||
greater than the specified steps or smaller than 0. For absolute axis only.
|
||||
|
||||
"#pwm-cells":
|
||||
const: 3
|
||||
|
||||
gpio:
|
||||
$ref: /schemas/gpio/maxim,max7360-gpio.yaml#
|
||||
description:
|
||||
PORT0 to PORT7 general purpose input/output pins configuration.
|
||||
|
||||
gpo:
|
||||
$ref: /schemas/gpio/maxim,max7360-gpio.yaml#
|
||||
description: >
|
||||
COL2 to COL7 general purpose output pins configuration. Allows to use
|
||||
unused keypad columns as outputs.
|
||||
|
||||
The MAX7360 has 8 column lines and 6 of them can be used as GPOs. GPIOs
|
||||
numbers used for this gpio-controller node do correspond to the column
|
||||
numbers: values 0 and 1 are never valid, values from 2 to 7 might be valid
|
||||
depending on the value of the keypad,num-column property.
|
||||
|
||||
patternProperties:
|
||||
'-pins$':
|
||||
type: object
|
||||
description:
|
||||
Pinctrl node's client devices use subnodes for desired pin configuration.
|
||||
Client device subnodes use below standard properties.
|
||||
$ref: /schemas/pinctrl/pincfg-node.yaml
|
||||
|
||||
properties:
|
||||
pins:
|
||||
description:
|
||||
List of gpio pins affected by the properties specified in this
|
||||
subnode.
|
||||
items:
|
||||
pattern: '^(PORT[0-7]|ROTARY)$'
|
||||
minItems: 1
|
||||
maxItems: 8
|
||||
|
||||
function:
|
||||
description:
|
||||
Specify the alternative function to be configured for the specified
|
||||
pins.
|
||||
enum: [gpio, pwm, rotary]
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- interrupt-names
|
||||
- linux,keymap
|
||||
- linux,axis
|
||||
- "#pwm-cells"
|
||||
- gpio
|
||||
- gpo
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/input/input.h>
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
io-expander@38 {
|
||||
compatible = "maxim,max7360";
|
||||
reg = <0x38>;
|
||||
|
||||
interrupt-parent = <&gpio1>;
|
||||
interrupts = <23 IRQ_TYPE_LEVEL_LOW>,
|
||||
<24 IRQ_TYPE_LEVEL_LOW>;
|
||||
interrupt-names = "inti", "intk";
|
||||
|
||||
keypad,num-rows = <8>;
|
||||
keypad,num-columns = <4>;
|
||||
linux,keymap = <
|
||||
MATRIX_KEY(0x00, 0x00, KEY_F5)
|
||||
MATRIX_KEY(0x01, 0x00, KEY_F4)
|
||||
MATRIX_KEY(0x02, 0x01, KEY_F6)
|
||||
>;
|
||||
keypad-debounce-delay-ms = <10>;
|
||||
autorepeat;
|
||||
|
||||
rotary-debounce-delay-ms = <2>;
|
||||
linux,axis = <0>; /* REL_X */
|
||||
rotary-encoder,relative-axis;
|
||||
|
||||
#pwm-cells = <3>;
|
||||
|
||||
max7360_gpio: gpio {
|
||||
compatible = "maxim,max7360-gpio";
|
||||
|
||||
gpio-controller;
|
||||
#gpio-cells = <2>;
|
||||
maxim,constant-current-disable = <0x06>;
|
||||
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <0x2>;
|
||||
};
|
||||
|
||||
max7360_gpo: gpo {
|
||||
compatible = "maxim,max7360-gpo";
|
||||
|
||||
gpio-controller;
|
||||
#gpio-cells = <2>;
|
||||
};
|
||||
|
||||
backlight_pins: backlight-pins {
|
||||
pins = "PORT2";
|
||||
function = "pwm";
|
||||
};
|
||||
};
|
||||
};
|
||||
13
MAINTAINERS
13
MAINTAINERS
@@ -15011,6 +15011,19 @@ L: linux-iio@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/iio/temperature/max30208.c
|
||||
|
||||
MAXIM MAX7360 KEYPAD LED MFD DRIVER
|
||||
M: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/gpio/maxim,max7360-gpio.yaml
|
||||
F: Documentation/devicetree/bindings/mfd/maxim,max7360.yaml
|
||||
F: drivers/gpio/gpio-max7360.c
|
||||
F: drivers/input/keyboard/max7360-keypad.c
|
||||
F: drivers/input/misc/max7360-rotary.c
|
||||
F: drivers/mfd/max7360.c
|
||||
F: drivers/pinctrl/pinctrl-max7360.c
|
||||
F: drivers/pwm/pwm-max7360.c
|
||||
F: include/linux/mfd/max7360.h
|
||||
|
||||
MAXIM MAX77650 PMIC MFD DRIVER
|
||||
M: Bartosz Golaszewski <brgl@bgdev.pl>
|
||||
L: linux-kernel@vger.kernel.org
|
||||
|
||||
@@ -1492,6 +1492,18 @@ config GPIO_MADERA
|
||||
help
|
||||
Support for GPIOs on Cirrus Logic Madera class codecs.
|
||||
|
||||
config GPIO_MAX7360
|
||||
tristate "MAX7360 GPIO support"
|
||||
depends on MFD_MAX7360
|
||||
select GPIO_REGMAP
|
||||
select REGMAP_IRQ
|
||||
help
|
||||
Allows to use MAX7360 I/O Expander PWM lines as GPIO and keypad COL
|
||||
lines as GPO.
|
||||
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called gpio-max7360.
|
||||
|
||||
config GPIO_MAX77620
|
||||
tristate "GPIO support for PMIC MAX77620 and MAX20024"
|
||||
depends on MFD_MAX77620
|
||||
|
||||
@@ -106,6 +106,7 @@ obj-$(CONFIG_GPIO_MAX7300) += gpio-max7300.o
|
||||
obj-$(CONFIG_GPIO_MAX7301) += gpio-max7301.o
|
||||
obj-$(CONFIG_GPIO_MAX730X) += gpio-max730x.o
|
||||
obj-$(CONFIG_GPIO_MAX732X) += gpio-max732x.o
|
||||
obj-$(CONFIG_GPIO_MAX7360) += gpio-max7360.o
|
||||
obj-$(CONFIG_GPIO_MAX77620) += gpio-max77620.o
|
||||
obj-$(CONFIG_GPIO_MAX77650) += gpio-max77650.o
|
||||
obj-$(CONFIG_GPIO_MAX77759) += gpio-max77759.o
|
||||
|
||||
257
drivers/gpio/gpio-max7360.c
Normal file
257
drivers/gpio/gpio-max7360.c
Normal file
@@ -0,0 +1,257 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright 2025 Bootlin
|
||||
*
|
||||
* Author: Kamel BOUHARA <kamel.bouhara@bootlin.com>
|
||||
* Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bitmap.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/gpio/regmap.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/mfd/max7360.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#define MAX7360_GPIO_PORT 1
|
||||
#define MAX7360_GPIO_COL 2
|
||||
|
||||
struct max7360_gpio_plat_data {
|
||||
unsigned int function;
|
||||
};
|
||||
|
||||
static struct max7360_gpio_plat_data max7360_gpio_port_plat = { .function = MAX7360_GPIO_PORT };
|
||||
static struct max7360_gpio_plat_data max7360_gpio_col_plat = { .function = MAX7360_GPIO_COL };
|
||||
|
||||
static int max7360_get_available_gpos(struct device *dev, unsigned int *available_gpios)
|
||||
{
|
||||
u32 columns;
|
||||
int ret;
|
||||
|
||||
ret = device_property_read_u32(dev->parent, "keypad,num-columns", &columns);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to read columns count\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
*available_gpios = min(MAX7360_MAX_GPO, MAX7360_MAX_KEY_COLS - columns);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max7360_gpo_init_valid_mask(struct gpio_chip *gc,
|
||||
unsigned long *valid_mask,
|
||||
unsigned int ngpios)
|
||||
{
|
||||
unsigned int available_gpios;
|
||||
int ret;
|
||||
|
||||
ret = max7360_get_available_gpos(gc->parent, &available_gpios);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
bitmap_clear(valid_mask, 0, MAX7360_MAX_KEY_COLS - available_gpios);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max7360_set_gpos_count(struct device *dev, struct regmap *regmap)
|
||||
{
|
||||
/*
|
||||
* MAX7360 COL0 to COL7 pins can be used either as keypad columns,
|
||||
* general purpose output or a mix of both.
|
||||
* By default, all pins are used as keypad, here we update this
|
||||
* configuration to allow to use some of them as GPIOs.
|
||||
*/
|
||||
unsigned int available_gpios;
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
ret = max7360_get_available_gpos(dev, &available_gpios);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Configure which GPIOs will be used for keypad.
|
||||
* MAX7360_REG_DEBOUNCE contains configuration both for keypad debounce
|
||||
* timings and gpos/keypad columns repartition. Only the later is
|
||||
* modified here.
|
||||
*/
|
||||
val = FIELD_PREP(MAX7360_PORTS, available_gpios);
|
||||
ret = regmap_write_bits(regmap, MAX7360_REG_DEBOUNCE, MAX7360_PORTS, val);
|
||||
if (ret)
|
||||
dev_err(dev, "Failed to write max7360 columns/gpos configuration");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int max7360_gpio_reg_mask_xlate(struct gpio_regmap *gpio,
|
||||
unsigned int base, unsigned int offset,
|
||||
unsigned int *reg, unsigned int *mask)
|
||||
{
|
||||
if (base == MAX7360_REG_PWMBASE) {
|
||||
/*
|
||||
* GPIO output is using PWM duty cycle registers: one register
|
||||
* per line, with value being either 0 or 255.
|
||||
*/
|
||||
*reg = base + offset;
|
||||
*mask = GENMASK(7, 0);
|
||||
} else {
|
||||
*reg = base;
|
||||
*mask = BIT(offset);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct regmap_irq max7360_regmap_irqs[MAX7360_MAX_GPIO] = {
|
||||
REGMAP_IRQ_REG(0, 0, BIT(0)),
|
||||
REGMAP_IRQ_REG(1, 0, BIT(1)),
|
||||
REGMAP_IRQ_REG(2, 0, BIT(2)),
|
||||
REGMAP_IRQ_REG(3, 0, BIT(3)),
|
||||
REGMAP_IRQ_REG(4, 0, BIT(4)),
|
||||
REGMAP_IRQ_REG(5, 0, BIT(5)),
|
||||
REGMAP_IRQ_REG(6, 0, BIT(6)),
|
||||
REGMAP_IRQ_REG(7, 0, BIT(7)),
|
||||
};
|
||||
|
||||
static int max7360_handle_mask_sync(const int index,
|
||||
const unsigned int mask_buf_def,
|
||||
const unsigned int mask_buf,
|
||||
void *const irq_drv_data)
|
||||
{
|
||||
struct regmap *regmap = irq_drv_data;
|
||||
int ret;
|
||||
|
||||
for (unsigned int i = 0; i < MAX7360_MAX_GPIO; i++) {
|
||||
ret = regmap_assign_bits(regmap, MAX7360_REG_PWMCFG(i),
|
||||
MAX7360_PORT_CFG_INTERRUPT_MASK, mask_buf & BIT(i));
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max7360_gpio_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct max7360_gpio_plat_data *plat_data;
|
||||
struct gpio_regmap_config gpio_config = { };
|
||||
struct regmap_irq_chip *irq_chip;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct regmap *regmap;
|
||||
unsigned int outconf;
|
||||
int ret;
|
||||
|
||||
regmap = dev_get_regmap(dev->parent, NULL);
|
||||
if (!regmap)
|
||||
return dev_err_probe(dev, -ENODEV, "could not get parent regmap\n");
|
||||
|
||||
plat_data = device_get_match_data(dev);
|
||||
if (plat_data->function == MAX7360_GPIO_PORT) {
|
||||
if (device_property_read_bool(dev, "interrupt-controller")) {
|
||||
/*
|
||||
* Port GPIOs with interrupt-controller property: add IRQ
|
||||
* controller.
|
||||
*/
|
||||
gpio_config.regmap_irq_flags = IRQF_ONESHOT | IRQF_SHARED;
|
||||
gpio_config.regmap_irq_line =
|
||||
fwnode_irq_get_byname(dev_fwnode(dev->parent), "inti");
|
||||
if (gpio_config.regmap_irq_line < 0)
|
||||
return dev_err_probe(dev, gpio_config.regmap_irq_line,
|
||||
"Failed to get IRQ\n");
|
||||
|
||||
/* Create custom IRQ configuration. */
|
||||
irq_chip = devm_kzalloc(dev, sizeof(*irq_chip), GFP_KERNEL);
|
||||
gpio_config.regmap_irq_chip = irq_chip;
|
||||
if (!irq_chip)
|
||||
return -ENOMEM;
|
||||
|
||||
irq_chip->name = dev_name(dev);
|
||||
irq_chip->status_base = MAX7360_REG_GPIOIN;
|
||||
irq_chip->status_is_level = true;
|
||||
irq_chip->num_regs = 1;
|
||||
irq_chip->num_irqs = MAX7360_MAX_GPIO;
|
||||
irq_chip->irqs = max7360_regmap_irqs;
|
||||
irq_chip->handle_mask_sync = max7360_handle_mask_sync;
|
||||
irq_chip->irq_drv_data = regmap;
|
||||
|
||||
for (unsigned int i = 0; i < MAX7360_MAX_GPIO; i++) {
|
||||
ret = regmap_write_bits(regmap, MAX7360_REG_PWMCFG(i),
|
||||
MAX7360_PORT_CFG_INTERRUPT_EDGES,
|
||||
MAX7360_PORT_CFG_INTERRUPT_EDGES);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"Failed to enable interrupts\n");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Port GPIOs: set output mode configuration (constant-current or not).
|
||||
* This property is optional.
|
||||
*/
|
||||
ret = device_property_read_u32(dev, "maxim,constant-current-disable", &outconf);
|
||||
if (!ret) {
|
||||
ret = regmap_write(regmap, MAX7360_REG_GPIOOUTM, outconf);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"Failed to set constant-current configuration\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* Add gpio device. */
|
||||
gpio_config.parent = dev;
|
||||
gpio_config.regmap = regmap;
|
||||
if (plat_data->function == MAX7360_GPIO_PORT) {
|
||||
gpio_config.ngpio = MAX7360_MAX_GPIO;
|
||||
gpio_config.reg_dat_base = GPIO_REGMAP_ADDR(MAX7360_REG_GPIOIN);
|
||||
gpio_config.reg_set_base = GPIO_REGMAP_ADDR(MAX7360_REG_PWMBASE);
|
||||
gpio_config.reg_dir_out_base = GPIO_REGMAP_ADDR(MAX7360_REG_GPIOCTRL);
|
||||
gpio_config.ngpio_per_reg = MAX7360_MAX_GPIO;
|
||||
gpio_config.reg_mask_xlate = max7360_gpio_reg_mask_xlate;
|
||||
} else {
|
||||
ret = max7360_set_gpos_count(dev, regmap);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to set GPOS pin count\n");
|
||||
|
||||
gpio_config.reg_set_base = GPIO_REGMAP_ADDR(MAX7360_REG_PORTS);
|
||||
gpio_config.ngpio = MAX7360_MAX_KEY_COLS;
|
||||
gpio_config.init_valid_mask = max7360_gpo_init_valid_mask;
|
||||
}
|
||||
|
||||
return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config));
|
||||
}
|
||||
|
||||
static const struct of_device_id max7360_gpio_of_match[] = {
|
||||
{
|
||||
.compatible = "maxim,max7360-gpo",
|
||||
.data = &max7360_gpio_col_plat
|
||||
}, {
|
||||
.compatible = "maxim,max7360-gpio",
|
||||
.data = &max7360_gpio_port_plat
|
||||
}, {
|
||||
}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, max7360_gpio_of_match);
|
||||
|
||||
static struct platform_driver max7360_gpio_driver = {
|
||||
.driver = {
|
||||
.name = "max7360-gpio",
|
||||
.of_match_table = max7360_gpio_of_match,
|
||||
},
|
||||
.probe = max7360_gpio_probe,
|
||||
};
|
||||
module_platform_driver(max7360_gpio_driver);
|
||||
|
||||
MODULE_DESCRIPTION("MAX7360 GPIO driver");
|
||||
MODULE_AUTHOR("Kamel BOUHARA <kamel.bouhara@bootlin.com>");
|
||||
MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -32,6 +32,11 @@ struct gpio_regmap {
|
||||
unsigned int reg_dir_in_base;
|
||||
unsigned int reg_dir_out_base;
|
||||
|
||||
#ifdef CONFIG_REGMAP_IRQ
|
||||
int regmap_irq_line;
|
||||
struct regmap_irq_chip_data *irq_chip_data;
|
||||
#endif
|
||||
|
||||
int (*reg_mask_xlate)(struct gpio_regmap *gpio, unsigned int base,
|
||||
unsigned int offset, unsigned int *reg,
|
||||
unsigned int *mask);
|
||||
@@ -215,6 +220,7 @@ EXPORT_SYMBOL_GPL(gpio_regmap_get_drvdata);
|
||||
*/
|
||||
struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config)
|
||||
{
|
||||
struct irq_domain *irq_domain;
|
||||
struct gpio_regmap *gpio;
|
||||
struct gpio_chip *chip;
|
||||
int ret;
|
||||
@@ -255,6 +261,7 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config
|
||||
chip->names = config->names;
|
||||
chip->label = config->label ?: dev_name(config->parent);
|
||||
chip->can_sleep = regmap_might_sleep(config->regmap);
|
||||
chip->init_valid_mask = config->init_valid_mask;
|
||||
|
||||
chip->request = gpiochip_generic_request;
|
||||
chip->free = gpiochip_generic_free;
|
||||
@@ -295,8 +302,22 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config
|
||||
if (ret < 0)
|
||||
goto err_free_gpio;
|
||||
|
||||
if (config->irq_domain) {
|
||||
ret = gpiochip_irqchip_add_domain(chip, config->irq_domain);
|
||||
#ifdef CONFIG_REGMAP_IRQ
|
||||
if (config->regmap_irq_chip) {
|
||||
gpio->regmap_irq_line = config->regmap_irq_line;
|
||||
ret = regmap_add_irq_chip_fwnode(dev_fwnode(config->parent), config->regmap,
|
||||
config->regmap_irq_line, config->regmap_irq_flags,
|
||||
0, config->regmap_irq_chip, &gpio->irq_chip_data);
|
||||
if (ret)
|
||||
goto err_free_gpio;
|
||||
|
||||
irq_domain = regmap_irq_get_domain(gpio->irq_chip_data);
|
||||
} else
|
||||
#endif
|
||||
irq_domain = config->irq_domain;
|
||||
|
||||
if (irq_domain) {
|
||||
ret = gpiochip_irqchip_add_domain(chip, irq_domain);
|
||||
if (ret)
|
||||
goto err_remove_gpiochip;
|
||||
}
|
||||
@@ -317,6 +338,11 @@ EXPORT_SYMBOL_GPL(gpio_regmap_register);
|
||||
*/
|
||||
void gpio_regmap_unregister(struct gpio_regmap *gpio)
|
||||
{
|
||||
#ifdef CONFIG_REGMAP_IRQ
|
||||
if (gpio->irq_chip_data)
|
||||
regmap_del_irq_chip(gpio->regmap_irq_line, gpio->irq_chip_data);
|
||||
#endif
|
||||
|
||||
gpiochip_remove(&gpio->gpio_chip);
|
||||
kfree(gpio);
|
||||
}
|
||||
|
||||
@@ -404,6 +404,18 @@ config KEYBOARD_MAX7359
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called max7359_keypad.
|
||||
|
||||
config KEYBOARD_MAX7360
|
||||
tristate "Maxim MAX7360 Key Switch Controller"
|
||||
select INPUT_MATRIXKMAP
|
||||
depends on I2C
|
||||
depends on MFD_MAX7360
|
||||
help
|
||||
If you say yes here you get support for the keypad controller on the
|
||||
Maxim MAX7360 I/O Expander.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called max7360_keypad.
|
||||
|
||||
config KEYBOARD_MPR121
|
||||
tristate "Freescale MPR121 Touchkey"
|
||||
depends on I2C
|
||||
|
||||
@@ -41,6 +41,7 @@ obj-$(CONFIG_KEYBOARD_LPC32XX) += lpc32xx-keys.o
|
||||
obj-$(CONFIG_KEYBOARD_MAPLE) += maple_keyb.o
|
||||
obj-$(CONFIG_KEYBOARD_MATRIX) += matrix_keypad.o
|
||||
obj-$(CONFIG_KEYBOARD_MAX7359) += max7359_keypad.o
|
||||
obj-$(CONFIG_KEYBOARD_MAX7360) += max7360-keypad.o
|
||||
obj-$(CONFIG_KEYBOARD_MPR121) += mpr121_touchkey.o
|
||||
obj-$(CONFIG_KEYBOARD_MT6779) += mt6779-keypad.o
|
||||
obj-$(CONFIG_KEYBOARD_MTK_PMIC) += mtk-pmic-keys.o
|
||||
|
||||
308
drivers/input/keyboard/max7360-keypad.c
Normal file
308
drivers/input/keyboard/max7360-keypad.c
Normal file
@@ -0,0 +1,308 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright 2025 Bootlin
|
||||
*
|
||||
* Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/device/devres.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/input/matrix_keypad.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/mfd/max7360.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_wakeirq.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
struct max7360_keypad {
|
||||
struct input_dev *input;
|
||||
unsigned int rows;
|
||||
unsigned int cols;
|
||||
unsigned int debounce_ms;
|
||||
int irq;
|
||||
struct regmap *regmap;
|
||||
unsigned short keycodes[MAX7360_MAX_KEY_ROWS * MAX7360_MAX_KEY_COLS];
|
||||
};
|
||||
|
||||
static irqreturn_t max7360_keypad_irq(int irq, void *data)
|
||||
{
|
||||
struct max7360_keypad *max7360_keypad = data;
|
||||
struct device *dev = max7360_keypad->input->dev.parent;
|
||||
unsigned int val;
|
||||
unsigned int row, col;
|
||||
unsigned int release;
|
||||
unsigned int code;
|
||||
int error;
|
||||
|
||||
error = regmap_read(max7360_keypad->regmap, MAX7360_REG_KEYFIFO, &val);
|
||||
if (error) {
|
||||
dev_err(dev, "Failed to read MAX7360 FIFO");
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
/* FIFO overflow: ignore it and get next event. */
|
||||
if (val == MAX7360_FIFO_OVERFLOW) {
|
||||
dev_warn(dev, "max7360 FIFO overflow");
|
||||
error = regmap_read_poll_timeout(max7360_keypad->regmap, MAX7360_REG_KEYFIFO,
|
||||
val, val != MAX7360_FIFO_OVERFLOW, 0, 1000);
|
||||
if (error) {
|
||||
dev_err(dev, "Failed to empty MAX7360 FIFO");
|
||||
return IRQ_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
if (val == MAX7360_FIFO_EMPTY) {
|
||||
dev_dbg(dev, "Got a spurious interrupt");
|
||||
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
row = FIELD_GET(MAX7360_FIFO_ROW, val);
|
||||
col = FIELD_GET(MAX7360_FIFO_COL, val);
|
||||
release = val & MAX7360_FIFO_RELEASE;
|
||||
|
||||
code = MATRIX_SCAN_CODE(row, col, get_count_order(max7360_keypad->cols));
|
||||
|
||||
dev_dbg(dev, "key[%d:%d] %s\n", row, col, release ? "release" : "press");
|
||||
|
||||
input_event(max7360_keypad->input, EV_MSC, MSC_SCAN, code);
|
||||
input_report_key(max7360_keypad->input, max7360_keypad->keycodes[code], !release);
|
||||
input_sync(max7360_keypad->input);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int max7360_keypad_open(struct input_dev *pdev)
|
||||
{
|
||||
struct max7360_keypad *max7360_keypad = input_get_drvdata(pdev);
|
||||
struct device *dev = max7360_keypad->input->dev.parent;
|
||||
int error;
|
||||
|
||||
/* Somebody is using the device: get out of sleep. */
|
||||
error = regmap_write_bits(max7360_keypad->regmap, MAX7360_REG_CONFIG,
|
||||
MAX7360_CFG_SLEEP, MAX7360_CFG_SLEEP);
|
||||
if (error)
|
||||
dev_err(dev, "Failed to write max7360 configuration: %d\n", error);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static void max7360_keypad_close(struct input_dev *pdev)
|
||||
{
|
||||
struct max7360_keypad *max7360_keypad = input_get_drvdata(pdev);
|
||||
struct device *dev = max7360_keypad->input->dev.parent;
|
||||
int error;
|
||||
|
||||
/* Nobody is using the device anymore: go to sleep. */
|
||||
error = regmap_write_bits(max7360_keypad->regmap, MAX7360_REG_CONFIG, MAX7360_CFG_SLEEP, 0);
|
||||
if (error)
|
||||
dev_err(dev, "Failed to write max7360 configuration: %d\n", error);
|
||||
}
|
||||
|
||||
static int max7360_keypad_hw_init(struct max7360_keypad *max7360_keypad)
|
||||
{
|
||||
struct device *dev = max7360_keypad->input->dev.parent;
|
||||
unsigned int val;
|
||||
int error;
|
||||
|
||||
val = max7360_keypad->debounce_ms - MAX7360_DEBOUNCE_MIN;
|
||||
error = regmap_write_bits(max7360_keypad->regmap, MAX7360_REG_DEBOUNCE,
|
||||
MAX7360_DEBOUNCE,
|
||||
FIELD_PREP(MAX7360_DEBOUNCE, val));
|
||||
if (error)
|
||||
return dev_err_probe(dev, error,
|
||||
"Failed to write max7360 debounce configuration\n");
|
||||
|
||||
error = regmap_write_bits(max7360_keypad->regmap, MAX7360_REG_INTERRUPT,
|
||||
MAX7360_INTERRUPT_TIME_MASK,
|
||||
FIELD_PREP(MAX7360_INTERRUPT_TIME_MASK, 1));
|
||||
if (error)
|
||||
return dev_err_probe(dev, error,
|
||||
"Failed to write max7360 keypad interrupt configuration\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max7360_keypad_build_keymap(struct max7360_keypad *max7360_keypad)
|
||||
{
|
||||
struct input_dev *input_dev = max7360_keypad->input;
|
||||
struct device *dev = input_dev->dev.parent->parent;
|
||||
struct matrix_keymap_data keymap_data;
|
||||
const char *propname = "linux,keymap";
|
||||
unsigned int max_keys;
|
||||
int error;
|
||||
int size;
|
||||
|
||||
size = device_property_count_u32(dev, propname);
|
||||
if (size <= 0) {
|
||||
dev_err(dev, "missing or malformed property %s: %d\n", propname, size);
|
||||
return size < 0 ? size : -EINVAL;
|
||||
}
|
||||
|
||||
max_keys = max7360_keypad->cols * max7360_keypad->rows;
|
||||
if (size > max_keys) {
|
||||
dev_err(dev, "%s size overflow (%d vs max %u)\n", propname, size, max_keys);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
u32 *keys __free(kfree) = kmalloc_array(size, sizeof(*keys), GFP_KERNEL);
|
||||
if (!keys)
|
||||
return -ENOMEM;
|
||||
|
||||
error = device_property_read_u32_array(dev, propname, keys, size);
|
||||
if (error) {
|
||||
dev_err(dev, "failed to read %s property: %d\n", propname, error);
|
||||
return error;
|
||||
}
|
||||
|
||||
keymap_data.keymap = keys;
|
||||
keymap_data.keymap_size = size;
|
||||
error = matrix_keypad_build_keymap(&keymap_data, NULL,
|
||||
max7360_keypad->rows, max7360_keypad->cols,
|
||||
max7360_keypad->keycodes, max7360_keypad->input);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max7360_keypad_parse_fw(struct device *dev,
|
||||
struct max7360_keypad *max7360_keypad,
|
||||
bool *autorepeat)
|
||||
{
|
||||
int error;
|
||||
|
||||
error = matrix_keypad_parse_properties(dev->parent, &max7360_keypad->rows,
|
||||
&max7360_keypad->cols);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
if (!max7360_keypad->rows || !max7360_keypad->cols ||
|
||||
max7360_keypad->rows > MAX7360_MAX_KEY_ROWS ||
|
||||
max7360_keypad->cols > MAX7360_MAX_KEY_COLS) {
|
||||
dev_err(dev, "Invalid number of columns or rows (%ux%u)\n",
|
||||
max7360_keypad->cols, max7360_keypad->rows);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*autorepeat = device_property_read_bool(dev->parent, "autorepeat");
|
||||
|
||||
max7360_keypad->debounce_ms = MAX7360_DEBOUNCE_MIN;
|
||||
error = device_property_read_u32(dev->parent, "keypad-debounce-delay-ms",
|
||||
&max7360_keypad->debounce_ms);
|
||||
if (error == -EINVAL) {
|
||||
dev_info(dev, "Using default keypad-debounce-delay-ms: %u\n",
|
||||
max7360_keypad->debounce_ms);
|
||||
} else if (error < 0) {
|
||||
dev_err(dev, "Failed to read keypad-debounce-delay-ms property\n");
|
||||
return error;
|
||||
}
|
||||
|
||||
if (!in_range(max7360_keypad->debounce_ms, MAX7360_DEBOUNCE_MIN,
|
||||
MAX7360_DEBOUNCE_MAX - MAX7360_DEBOUNCE_MIN + 1)) {
|
||||
dev_err(dev, "Invalid keypad-debounce-delay-ms: %u, should be between %u and %u.\n",
|
||||
max7360_keypad->debounce_ms, MAX7360_DEBOUNCE_MIN, MAX7360_DEBOUNCE_MAX);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max7360_keypad_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct max7360_keypad *max7360_keypad;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct input_dev *input;
|
||||
struct regmap *regmap;
|
||||
bool autorepeat;
|
||||
int error;
|
||||
int irq;
|
||||
|
||||
regmap = dev_get_regmap(dev->parent, NULL);
|
||||
if (!regmap)
|
||||
return dev_err_probe(dev, -ENODEV, "Could not get parent regmap\n");
|
||||
|
||||
irq = fwnode_irq_get_byname(dev_fwnode(dev->parent), "intk");
|
||||
if (irq < 0)
|
||||
return dev_err_probe(dev, irq, "Failed to get IRQ\n");
|
||||
|
||||
max7360_keypad = devm_kzalloc(dev, sizeof(*max7360_keypad), GFP_KERNEL);
|
||||
if (!max7360_keypad)
|
||||
return -ENOMEM;
|
||||
|
||||
max7360_keypad->regmap = regmap;
|
||||
|
||||
error = max7360_keypad_parse_fw(dev, max7360_keypad, &autorepeat);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
input = devm_input_allocate_device(dev);
|
||||
if (!input)
|
||||
return -ENOMEM;
|
||||
|
||||
max7360_keypad->input = input;
|
||||
|
||||
input->id.bustype = BUS_I2C;
|
||||
input->name = pdev->name;
|
||||
input->open = max7360_keypad_open;
|
||||
input->close = max7360_keypad_close;
|
||||
|
||||
error = max7360_keypad_build_keymap(max7360_keypad);
|
||||
if (error)
|
||||
return dev_err_probe(dev, error, "Failed to build keymap\n");
|
||||
|
||||
input_set_capability(input, EV_MSC, MSC_SCAN);
|
||||
if (autorepeat)
|
||||
__set_bit(EV_REP, input->evbit);
|
||||
|
||||
input_set_drvdata(input, max7360_keypad);
|
||||
|
||||
error = devm_request_threaded_irq(dev, irq, NULL, max7360_keypad_irq,
|
||||
IRQF_ONESHOT,
|
||||
"max7360-keypad", max7360_keypad);
|
||||
if (error)
|
||||
return dev_err_probe(dev, error, "Failed to register interrupt\n");
|
||||
|
||||
error = input_register_device(input);
|
||||
if (error)
|
||||
return dev_err_probe(dev, error, "Could not register input device\n");
|
||||
|
||||
error = max7360_keypad_hw_init(max7360_keypad);
|
||||
if (error)
|
||||
return dev_err_probe(dev, error, "Failed to initialize max7360 keypad\n");
|
||||
|
||||
device_init_wakeup(dev, true);
|
||||
error = dev_pm_set_wake_irq(dev, irq);
|
||||
if (error)
|
||||
dev_warn(dev, "Failed to set up wakeup irq: %d\n", error);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void max7360_keypad_remove(struct platform_device *pdev)
|
||||
{
|
||||
dev_pm_clear_wake_irq(&pdev->dev);
|
||||
device_init_wakeup(&pdev->dev, false);
|
||||
}
|
||||
|
||||
static struct platform_driver max7360_keypad_driver = {
|
||||
.driver = {
|
||||
.name = "max7360-keypad",
|
||||
},
|
||||
.probe = max7360_keypad_probe,
|
||||
.remove = max7360_keypad_remove,
|
||||
};
|
||||
module_platform_driver(max7360_keypad_driver);
|
||||
|
||||
MODULE_DESCRIPTION("MAX7360 Keypad driver");
|
||||
MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -230,6 +230,16 @@ config INPUT_M68K_BEEP
|
||||
tristate "M68k Beeper support"
|
||||
depends on M68K
|
||||
|
||||
config INPUT_MAX7360_ROTARY
|
||||
tristate "Maxim MAX7360 Rotary Encoder"
|
||||
depends on MFD_MAX7360
|
||||
help
|
||||
If you say yes here you get support for the rotary encoder on the
|
||||
Maxim MAX7360 I/O Expander.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called max7360_rotary.
|
||||
|
||||
config INPUT_MAX77650_ONKEY
|
||||
tristate "Maxim MAX77650 ONKEY support"
|
||||
depends on MFD_MAX77650
|
||||
|
||||
@@ -51,6 +51,7 @@ obj-$(CONFIG_INPUT_IQS7222) += iqs7222.o
|
||||
obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o
|
||||
obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o
|
||||
obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o
|
||||
obj-$(CONFIG_INPUT_MAX7360_ROTARY) += max7360-rotary.o
|
||||
obj-$(CONFIG_INPUT_MAX77650_ONKEY) += max77650-onkey.o
|
||||
obj-$(CONFIG_INPUT_MAX77693_HAPTIC) += max77693-haptic.o
|
||||
obj-$(CONFIG_INPUT_MAX8925_ONKEY) += max8925_onkey.o
|
||||
|
||||
192
drivers/input/misc/max7360-rotary.c
Normal file
192
drivers/input/misc/max7360-rotary.c
Normal file
@@ -0,0 +1,192 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright 2025 Bootlin
|
||||
*
|
||||
* Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/device/devres.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/mfd/max7360.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_wakeirq.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define MAX7360_ROTARY_DEFAULT_STEPS 24
|
||||
|
||||
struct max7360_rotary {
|
||||
struct input_dev *input;
|
||||
struct regmap *regmap;
|
||||
unsigned int debounce_ms;
|
||||
|
||||
unsigned int pos;
|
||||
|
||||
u32 steps;
|
||||
u32 axis;
|
||||
bool relative_axis;
|
||||
bool rollover;
|
||||
};
|
||||
|
||||
static void max7360_rotary_report_event(struct max7360_rotary *max7360_rotary, int steps)
|
||||
{
|
||||
if (max7360_rotary->relative_axis) {
|
||||
input_report_rel(max7360_rotary->input, max7360_rotary->axis, steps);
|
||||
} else {
|
||||
int pos = max7360_rotary->pos;
|
||||
int maxval = max7360_rotary->steps;
|
||||
|
||||
/*
|
||||
* Add steps to the position.
|
||||
* Make sure added steps are always in ]-maxval; maxval[
|
||||
* interval, so (pos + maxval) is always >= 0.
|
||||
* Then set back pos to the [0; maxval[ interval.
|
||||
*/
|
||||
pos += steps % maxval;
|
||||
if (max7360_rotary->rollover)
|
||||
pos = (pos + maxval) % maxval;
|
||||
else
|
||||
pos = clamp(pos, 0, maxval - 1);
|
||||
|
||||
max7360_rotary->pos = pos;
|
||||
input_report_abs(max7360_rotary->input, max7360_rotary->axis, max7360_rotary->pos);
|
||||
}
|
||||
|
||||
input_sync(max7360_rotary->input);
|
||||
}
|
||||
|
||||
static irqreturn_t max7360_rotary_irq(int irq, void *data)
|
||||
{
|
||||
struct max7360_rotary *max7360_rotary = data;
|
||||
struct device *dev = max7360_rotary->input->dev.parent;
|
||||
unsigned int val;
|
||||
int error;
|
||||
|
||||
error = regmap_read(max7360_rotary->regmap, MAX7360_REG_RTR_CNT, &val);
|
||||
if (error < 0) {
|
||||
dev_err(dev, "Failed to read rotary counter\n");
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
if (val == 0)
|
||||
return IRQ_NONE;
|
||||
|
||||
max7360_rotary_report_event(max7360_rotary, sign_extend32(val, 7));
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int max7360_rotary_hw_init(struct max7360_rotary *max7360_rotary)
|
||||
{
|
||||
struct device *dev = max7360_rotary->input->dev.parent;
|
||||
int val;
|
||||
int error;
|
||||
|
||||
val = FIELD_PREP(MAX7360_ROT_DEBOUNCE, max7360_rotary->debounce_ms) |
|
||||
FIELD_PREP(MAX7360_ROT_INTCNT, 1) | MAX7360_ROT_INTCNT_DLY;
|
||||
error = regmap_write(max7360_rotary->regmap, MAX7360_REG_RTRCFG, val);
|
||||
if (error)
|
||||
dev_err(dev, "Failed to set max7360 rotary encoder configuration\n");
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int max7360_rotary_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct max7360_rotary *max7360_rotary;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct input_dev *input;
|
||||
struct regmap *regmap;
|
||||
int irq;
|
||||
int error;
|
||||
|
||||
regmap = dev_get_regmap(dev->parent, NULL);
|
||||
if (!regmap)
|
||||
return dev_err_probe(dev, -ENODEV, "Could not get parent regmap\n");
|
||||
|
||||
irq = fwnode_irq_get_byname(dev_fwnode(dev->parent), "inti");
|
||||
if (irq < 0)
|
||||
return dev_err_probe(dev, irq, "Failed to get IRQ\n");
|
||||
|
||||
max7360_rotary = devm_kzalloc(dev, sizeof(*max7360_rotary), GFP_KERNEL);
|
||||
if (!max7360_rotary)
|
||||
return -ENOMEM;
|
||||
|
||||
max7360_rotary->regmap = regmap;
|
||||
|
||||
device_property_read_u32(dev->parent, "linux,axis", &max7360_rotary->axis);
|
||||
max7360_rotary->rollover = device_property_read_bool(dev->parent,
|
||||
"rotary-encoder,rollover");
|
||||
max7360_rotary->relative_axis =
|
||||
device_property_read_bool(dev->parent, "rotary-encoder,relative-axis");
|
||||
|
||||
error = device_property_read_u32(dev->parent, "rotary-encoder,steps",
|
||||
&max7360_rotary->steps);
|
||||
if (error)
|
||||
max7360_rotary->steps = MAX7360_ROTARY_DEFAULT_STEPS;
|
||||
|
||||
device_property_read_u32(dev->parent, "rotary-debounce-delay-ms",
|
||||
&max7360_rotary->debounce_ms);
|
||||
if (max7360_rotary->debounce_ms > MAX7360_ROT_DEBOUNCE_MAX)
|
||||
return dev_err_probe(dev, -EINVAL, "Invalid debounce timing: %u\n",
|
||||
max7360_rotary->debounce_ms);
|
||||
|
||||
input = devm_input_allocate_device(dev);
|
||||
if (!input)
|
||||
return -ENOMEM;
|
||||
|
||||
max7360_rotary->input = input;
|
||||
|
||||
input->id.bustype = BUS_I2C;
|
||||
input->name = pdev->name;
|
||||
|
||||
if (max7360_rotary->relative_axis)
|
||||
input_set_capability(input, EV_REL, max7360_rotary->axis);
|
||||
else
|
||||
input_set_abs_params(input, max7360_rotary->axis, 0, max7360_rotary->steps, 0, 1);
|
||||
|
||||
error = devm_request_threaded_irq(dev, irq, NULL, max7360_rotary_irq,
|
||||
IRQF_ONESHOT | IRQF_SHARED,
|
||||
"max7360-rotary", max7360_rotary);
|
||||
if (error)
|
||||
return dev_err_probe(dev, error, "Failed to register interrupt\n");
|
||||
|
||||
error = input_register_device(input);
|
||||
if (error)
|
||||
return dev_err_probe(dev, error, "Could not register input device\n");
|
||||
|
||||
error = max7360_rotary_hw_init(max7360_rotary);
|
||||
if (error)
|
||||
return dev_err_probe(dev, error, "Failed to initialize max7360 rotary\n");
|
||||
|
||||
device_init_wakeup(dev, true);
|
||||
error = dev_pm_set_wake_irq(dev, irq);
|
||||
if (error)
|
||||
dev_warn(dev, "Failed to set up wakeup irq: %d\n", error);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void max7360_rotary_remove(struct platform_device *pdev)
|
||||
{
|
||||
dev_pm_clear_wake_irq(&pdev->dev);
|
||||
device_init_wakeup(&pdev->dev, false);
|
||||
}
|
||||
|
||||
static struct platform_driver max7360_rotary_driver = {
|
||||
.driver = {
|
||||
.name = "max7360-rotary",
|
||||
},
|
||||
.probe = max7360_rotary_probe,
|
||||
.remove = max7360_rotary_remove,
|
||||
};
|
||||
module_platform_driver(max7360_rotary_driver);
|
||||
|
||||
MODULE_DESCRIPTION("MAX7360 Rotary driver");
|
||||
MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -2481,5 +2481,19 @@ config MFD_UPBOARD_FPGA
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called upboard-fpga.
|
||||
|
||||
config MFD_MAX7360
|
||||
tristate "Maxim MAX7360 I2C IO Expander"
|
||||
depends on I2C
|
||||
select MFD_CORE
|
||||
select REGMAP_I2C
|
||||
select REGMAP_IRQ
|
||||
help
|
||||
Say yes here to add support for Maxim MAX7360 device, embedding
|
||||
keypad, rotary encoder, PWM and GPIO features.
|
||||
|
||||
This driver provides common support for accessing the device;
|
||||
additional drivers must be enabled in order to use the functionality
|
||||
of the device.
|
||||
|
||||
endmenu
|
||||
endif
|
||||
|
||||
@@ -163,6 +163,7 @@ obj-$(CONFIG_MFD_DA9063) += da9063.o
|
||||
obj-$(CONFIG_MFD_DA9150) += da9150-core.o
|
||||
|
||||
obj-$(CONFIG_MFD_MAX14577) += max14577.o
|
||||
obj-$(CONFIG_MFD_MAX7360) += max7360.o
|
||||
obj-$(CONFIG_MFD_MAX77541) += max77541.o
|
||||
obj-$(CONFIG_MFD_MAX77620) += max77620.o
|
||||
obj-$(CONFIG_MFD_MAX77650) += max77650.o
|
||||
|
||||
171
drivers/mfd/max7360.c
Normal file
171
drivers/mfd/max7360.c
Normal file
@@ -0,0 +1,171 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Maxim MAX7360 Core Driver
|
||||
*
|
||||
* Copyright 2025 Bootlin
|
||||
*
|
||||
* Authors:
|
||||
* Kamel Bouhara <kamel.bouhara@bootlin.com>
|
||||
* Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
|
||||
*/
|
||||
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device/devres.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/max7360.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
static const struct mfd_cell max7360_cells[] = {
|
||||
{ .name = "max7360-pinctrl" },
|
||||
{ .name = "max7360-pwm" },
|
||||
{ .name = "max7360-keypad" },
|
||||
{ .name = "max7360-rotary" },
|
||||
{
|
||||
.name = "max7360-gpo",
|
||||
.of_compatible = "maxim,max7360-gpo",
|
||||
},
|
||||
{
|
||||
.name = "max7360-gpio",
|
||||
.of_compatible = "maxim,max7360-gpio",
|
||||
},
|
||||
};
|
||||
|
||||
static const struct regmap_range max7360_volatile_ranges[] = {
|
||||
regmap_reg_range(MAX7360_REG_KEYFIFO, MAX7360_REG_KEYFIFO),
|
||||
regmap_reg_range(MAX7360_REG_I2C_TIMEOUT, MAX7360_REG_RTR_CNT),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table max7360_volatile_table = {
|
||||
.yes_ranges = max7360_volatile_ranges,
|
||||
.n_yes_ranges = ARRAY_SIZE(max7360_volatile_ranges),
|
||||
};
|
||||
|
||||
static const struct regmap_config max7360_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = MAX7360_REG_PWMCFG(MAX7360_PORT_PWM_COUNT - 1),
|
||||
.volatile_table = &max7360_volatile_table,
|
||||
.cache_type = REGCACHE_MAPLE,
|
||||
};
|
||||
|
||||
static int max7360_mask_irqs(struct regmap *regmap)
|
||||
{
|
||||
struct device *dev = regmap_get_device(regmap);
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* GPIO/PWM interrupts are not masked on reset: as the MAX7360 "INTI"
|
||||
* interrupt line is shared between GPIOs and rotary encoder, this could
|
||||
* result in repeated spurious interrupts on the rotary encoder driver
|
||||
* if the GPIO driver is not loaded. Mask them now to avoid this
|
||||
* situation.
|
||||
*/
|
||||
for (unsigned int i = 0; i < MAX7360_PORT_PWM_COUNT; i++) {
|
||||
ret = regmap_write_bits(regmap, MAX7360_REG_PWMCFG(i),
|
||||
MAX7360_PORT_CFG_INTERRUPT_MASK,
|
||||
MAX7360_PORT_CFG_INTERRUPT_MASK);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"Failed to write MAX7360 port configuration\n");
|
||||
}
|
||||
|
||||
/* Read GPIO in register, to ACK any pending IRQ. */
|
||||
ret = regmap_read(regmap, MAX7360_REG_GPIOIN, &val);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to read GPIO values\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max7360_reset(struct regmap *regmap)
|
||||
{
|
||||
struct device *dev = regmap_get_device(regmap);
|
||||
int ret;
|
||||
|
||||
ret = regmap_write(regmap, MAX7360_REG_GPIOCFG, MAX7360_GPIO_CFG_GPIO_RST);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to reset GPIO configuration: %x\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regcache_drop_region(regmap, MAX7360_REG_GPIOCFG, MAX7360_REG_GPIO_LAST);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to drop regmap cache: %x\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_write(regmap, MAX7360_REG_SLEEP, 0);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to reset autosleep configuration: %x\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_write(regmap, MAX7360_REG_DEBOUNCE, 0);
|
||||
if (ret)
|
||||
dev_err(dev, "Failed to reset GPO port count: %x\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int max7360_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct regmap *regmap;
|
||||
int ret;
|
||||
|
||||
regmap = devm_regmap_init_i2c(client, &max7360_regmap_config);
|
||||
if (IS_ERR(regmap))
|
||||
return dev_err_probe(dev, PTR_ERR(regmap), "Failed to initialise regmap\n");
|
||||
|
||||
ret = max7360_reset(regmap);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to reset device\n");
|
||||
|
||||
/* Get the device out of shutdown mode. */
|
||||
ret = regmap_write_bits(regmap, MAX7360_REG_GPIOCFG,
|
||||
MAX7360_GPIO_CFG_GPIO_EN,
|
||||
MAX7360_GPIO_CFG_GPIO_EN);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to enable GPIO and PWM module\n");
|
||||
|
||||
ret = max7360_mask_irqs(regmap);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Could not mask interrupts\n");
|
||||
|
||||
ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
|
||||
max7360_cells, ARRAY_SIZE(max7360_cells),
|
||||
NULL, 0, NULL);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to register child devices\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id max7360_dt_match[] = {
|
||||
{ .compatible = "maxim,max7360" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, max7360_dt_match);
|
||||
|
||||
static struct i2c_driver max7360_driver = {
|
||||
.driver = {
|
||||
.name = "max7360",
|
||||
.of_match_table = max7360_dt_match,
|
||||
},
|
||||
.probe = max7360_probe,
|
||||
};
|
||||
module_i2c_driver(max7360_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Maxim MAX7360 I2C IO Expander core driver");
|
||||
MODULE_AUTHOR("Kamel Bouhara <kamel.bouhara@bootlin.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -358,6 +358,17 @@ config PINCTRL_LPC18XX
|
||||
help
|
||||
Pinctrl driver for NXP LPC18xx/43xx System Control Unit (SCU).
|
||||
|
||||
config PINCTRL_MAX7360
|
||||
tristate "MAX7360 Pincontrol support"
|
||||
depends on MFD_MAX7360
|
||||
select PINMUX
|
||||
select GENERIC_PINCONF
|
||||
help
|
||||
Say Y here to enable pin control support for Maxim MAX7360 keypad
|
||||
controller.
|
||||
This keypad controller has 8 GPIO pins that may work as GPIO, or PWM,
|
||||
or rotary encoder alternate modes.
|
||||
|
||||
config PINCTRL_MAX77620
|
||||
tristate "MAX77620/MAX20024 Pincontrol support"
|
||||
depends on MFD_MAX77620 && OF
|
||||
|
||||
@@ -37,6 +37,7 @@ obj-$(CONFIG_PINCTRL_FALCON) += pinctrl-falcon.o
|
||||
obj-$(CONFIG_PINCTRL_LOONGSON2) += pinctrl-loongson2.o
|
||||
obj-$(CONFIG_PINCTRL_XWAY) += pinctrl-xway.o
|
||||
obj-$(CONFIG_PINCTRL_LPC18XX) += pinctrl-lpc18xx.o
|
||||
obj-$(CONFIG_PINCTRL_MAX7360) += pinctrl-max7360.o
|
||||
obj-$(CONFIG_PINCTRL_MAX77620) += pinctrl-max77620.o
|
||||
obj-$(CONFIG_PINCTRL_MCP23S08_I2C) += pinctrl-mcp23s08_i2c.o
|
||||
obj-$(CONFIG_PINCTRL_MCP23S08_SPI) += pinctrl-mcp23s08_spi.o
|
||||
|
||||
215
drivers/pinctrl/pinctrl-max7360.c
Normal file
215
drivers/pinctrl/pinctrl-max7360.c
Normal file
@@ -0,0 +1,215 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright 2025 Bootlin
|
||||
*
|
||||
* Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
|
||||
*/
|
||||
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/device/devres.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/mfd/max7360.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/stddef.h>
|
||||
|
||||
#include <linux/pinctrl/pinctrl.h>
|
||||
#include <linux/pinctrl/pinconf-generic.h>
|
||||
#include <linux/pinctrl/pinmux.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "pinmux.h"
|
||||
|
||||
struct max7360_pinctrl {
|
||||
struct pinctrl_dev *pctldev;
|
||||
struct pinctrl_desc pinctrl_desc;
|
||||
};
|
||||
|
||||
static const struct pinctrl_pin_desc max7360_pins[] = {
|
||||
PINCTRL_PIN(0, "PORT0"),
|
||||
PINCTRL_PIN(1, "PORT1"),
|
||||
PINCTRL_PIN(2, "PORT2"),
|
||||
PINCTRL_PIN(3, "PORT3"),
|
||||
PINCTRL_PIN(4, "PORT4"),
|
||||
PINCTRL_PIN(5, "PORT5"),
|
||||
PINCTRL_PIN(6, "PORT6"),
|
||||
PINCTRL_PIN(7, "PORT7"),
|
||||
};
|
||||
|
||||
static const unsigned int port0_pins[] = {0};
|
||||
static const unsigned int port1_pins[] = {1};
|
||||
static const unsigned int port2_pins[] = {2};
|
||||
static const unsigned int port3_pins[] = {3};
|
||||
static const unsigned int port4_pins[] = {4};
|
||||
static const unsigned int port5_pins[] = {5};
|
||||
static const unsigned int port6_pins[] = {6};
|
||||
static const unsigned int port7_pins[] = {7};
|
||||
static const unsigned int rotary_pins[] = {6, 7};
|
||||
|
||||
static const struct pingroup max7360_groups[] = {
|
||||
PINCTRL_PINGROUP("PORT0", port0_pins, ARRAY_SIZE(port0_pins)),
|
||||
PINCTRL_PINGROUP("PORT1", port1_pins, ARRAY_SIZE(port1_pins)),
|
||||
PINCTRL_PINGROUP("PORT2", port2_pins, ARRAY_SIZE(port2_pins)),
|
||||
PINCTRL_PINGROUP("PORT3", port3_pins, ARRAY_SIZE(port3_pins)),
|
||||
PINCTRL_PINGROUP("PORT4", port4_pins, ARRAY_SIZE(port4_pins)),
|
||||
PINCTRL_PINGROUP("PORT5", port5_pins, ARRAY_SIZE(port5_pins)),
|
||||
PINCTRL_PINGROUP("PORT6", port6_pins, ARRAY_SIZE(port6_pins)),
|
||||
PINCTRL_PINGROUP("PORT7", port7_pins, ARRAY_SIZE(port7_pins)),
|
||||
PINCTRL_PINGROUP("ROTARY", rotary_pins, ARRAY_SIZE(rotary_pins)),
|
||||
};
|
||||
|
||||
static int max7360_pinctrl_get_groups_count(struct pinctrl_dev *pctldev)
|
||||
{
|
||||
return ARRAY_SIZE(max7360_groups);
|
||||
}
|
||||
|
||||
static const char *max7360_pinctrl_get_group_name(struct pinctrl_dev *pctldev,
|
||||
unsigned int group)
|
||||
{
|
||||
return max7360_groups[group].name;
|
||||
}
|
||||
|
||||
static int max7360_pinctrl_get_group_pins(struct pinctrl_dev *pctldev,
|
||||
unsigned int group,
|
||||
const unsigned int **pins,
|
||||
unsigned int *num_pins)
|
||||
{
|
||||
*pins = max7360_groups[group].pins;
|
||||
*num_pins = max7360_groups[group].npins;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pinctrl_ops max7360_pinctrl_ops = {
|
||||
.get_groups_count = max7360_pinctrl_get_groups_count,
|
||||
.get_group_name = max7360_pinctrl_get_group_name,
|
||||
.get_group_pins = max7360_pinctrl_get_group_pins,
|
||||
#ifdef CONFIG_OF
|
||||
.dt_node_to_map = pinconf_generic_dt_node_to_map_pin,
|
||||
.dt_free_map = pinconf_generic_dt_free_map,
|
||||
#endif
|
||||
};
|
||||
|
||||
static const char * const simple_groups[] = {
|
||||
"PORT0", "PORT1", "PORT2", "PORT3",
|
||||
"PORT4", "PORT5", "PORT6", "PORT7",
|
||||
};
|
||||
|
||||
static const char * const rotary_groups[] = { "ROTARY" };
|
||||
|
||||
#define MAX7360_PINCTRL_FN_GPIO 0
|
||||
#define MAX7360_PINCTRL_FN_PWM 1
|
||||
#define MAX7360_PINCTRL_FN_ROTARY 2
|
||||
static const struct pinfunction max7360_functions[] = {
|
||||
[MAX7360_PINCTRL_FN_GPIO] = PINCTRL_PINFUNCTION("gpio", simple_groups,
|
||||
ARRAY_SIZE(simple_groups)),
|
||||
[MAX7360_PINCTRL_FN_PWM] = PINCTRL_PINFUNCTION("pwm", simple_groups,
|
||||
ARRAY_SIZE(simple_groups)),
|
||||
[MAX7360_PINCTRL_FN_ROTARY] = PINCTRL_PINFUNCTION("rotary", rotary_groups,
|
||||
ARRAY_SIZE(rotary_groups)),
|
||||
};
|
||||
|
||||
static int max7360_get_functions_count(struct pinctrl_dev *pctldev)
|
||||
{
|
||||
return ARRAY_SIZE(max7360_functions);
|
||||
}
|
||||
|
||||
static const char *max7360_get_function_name(struct pinctrl_dev *pctldev, unsigned int selector)
|
||||
{
|
||||
return max7360_functions[selector].name;
|
||||
}
|
||||
|
||||
static int max7360_get_function_groups(struct pinctrl_dev *pctldev, unsigned int selector,
|
||||
const char * const **groups,
|
||||
unsigned int * const num_groups)
|
||||
{
|
||||
*groups = max7360_functions[selector].groups;
|
||||
*num_groups = max7360_functions[selector].ngroups;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max7360_set_mux(struct pinctrl_dev *pctldev, unsigned int selector,
|
||||
unsigned int group)
|
||||
{
|
||||
struct regmap *regmap = dev_get_regmap(pctldev->dev->parent, NULL);
|
||||
int val;
|
||||
|
||||
/*
|
||||
* GPIO and PWM functions are the same: we only need to handle the
|
||||
* rotary encoder function, on pins 6 and 7.
|
||||
*/
|
||||
if (max7360_groups[group].pins[0] >= 6) {
|
||||
if (selector == MAX7360_PINCTRL_FN_ROTARY)
|
||||
val = MAX7360_GPIO_CFG_RTR_EN;
|
||||
else
|
||||
val = 0;
|
||||
|
||||
return regmap_write_bits(regmap, MAX7360_REG_GPIOCFG, MAX7360_GPIO_CFG_RTR_EN, val);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pinmux_ops max7360_pmxops = {
|
||||
.get_functions_count = max7360_get_functions_count,
|
||||
.get_function_name = max7360_get_function_name,
|
||||
.get_function_groups = max7360_get_function_groups,
|
||||
.set_mux = max7360_set_mux,
|
||||
.strict = true,
|
||||
};
|
||||
|
||||
static int max7360_pinctrl_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct regmap *regmap;
|
||||
struct pinctrl_desc *pd;
|
||||
struct max7360_pinctrl *chip;
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
regmap = dev_get_regmap(dev->parent, NULL);
|
||||
if (!regmap)
|
||||
return dev_err_probe(dev, -ENODEV, "Could not get parent regmap\n");
|
||||
|
||||
chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
|
||||
if (!chip)
|
||||
return -ENOMEM;
|
||||
|
||||
pd = &chip->pinctrl_desc;
|
||||
|
||||
pd->pctlops = &max7360_pinctrl_ops;
|
||||
pd->pmxops = &max7360_pmxops;
|
||||
pd->name = dev_name(dev);
|
||||
pd->pins = max7360_pins;
|
||||
pd->npins = MAX7360_MAX_GPIO;
|
||||
pd->owner = THIS_MODULE;
|
||||
|
||||
/*
|
||||
* This MFD sub-device does not have any associated device tree node:
|
||||
* properties are stored in the device node of the parent (MFD) device
|
||||
* and this same node is used in phandles of client devices.
|
||||
* Reuse this device tree node here, as otherwise the pinctrl subsystem
|
||||
* would be confused by this topology.
|
||||
*/
|
||||
device_set_of_node_from_dev(dev, dev->parent);
|
||||
|
||||
chip->pctldev = devm_pinctrl_register(dev, pd, chip);
|
||||
if (IS_ERR(chip->pctldev))
|
||||
return dev_err_probe(dev, PTR_ERR(chip->pctldev), "can't register controller\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver max7360_pinctrl_driver = {
|
||||
.driver = {
|
||||
.name = "max7360-pinctrl",
|
||||
},
|
||||
.probe = max7360_pinctrl_probe,
|
||||
};
|
||||
module_platform_driver(max7360_pinctrl_driver);
|
||||
|
||||
MODULE_DESCRIPTION("MAX7360 pinctrl driver");
|
||||
MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -432,6 +432,16 @@ config PWM_LPSS_PLATFORM
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called pwm-lpss-platform.
|
||||
|
||||
config PWM_MAX7360
|
||||
tristate "MAX7360 PWMs"
|
||||
depends on MFD_MAX7360
|
||||
help
|
||||
PWM driver for Maxim Integrated MAX7360 multifunction device, with
|
||||
support for up to 8 PWM outputs.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called pwm-max7360.
|
||||
|
||||
config PWM_MC33XS2410
|
||||
tristate "MC33XS2410 PWM support"
|
||||
depends on OF
|
||||
|
||||
@@ -38,6 +38,7 @@ obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o
|
||||
obj-$(CONFIG_PWM_LPSS) += pwm-lpss.o
|
||||
obj-$(CONFIG_PWM_LPSS_PCI) += pwm-lpss-pci.o
|
||||
obj-$(CONFIG_PWM_LPSS_PLATFORM) += pwm-lpss-platform.o
|
||||
obj-$(CONFIG_PWM_MAX7360) += pwm-max7360.o
|
||||
obj-$(CONFIG_PWM_MC33XS2410) += pwm-mc33xs2410.o
|
||||
obj-$(CONFIG_PWM_MEDIATEK) += pwm-mediatek.o
|
||||
obj-$(CONFIG_PWM_MESON) += pwm-meson.o
|
||||
|
||||
209
drivers/pwm/pwm-max7360.c
Normal file
209
drivers/pwm/pwm-max7360.c
Normal file
@@ -0,0 +1,209 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright 2025 Bootlin
|
||||
*
|
||||
* Author: Kamel BOUHARA <kamel.bouhara@bootlin.com>
|
||||
* Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
|
||||
*
|
||||
* PWM functionality of the MAX7360 multi-function device.
|
||||
* https://www.analog.com/media/en/technical-documentation/data-sheets/MAX7360.pdf
|
||||
*
|
||||
* Limitations:
|
||||
* - Only supports normal polarity.
|
||||
* - The period is fixed to 2 ms.
|
||||
* - Only the duty cycle can be changed, new values are applied at the beginning
|
||||
* of the next cycle.
|
||||
* - When disabled, the output is put in Hi-Z immediately.
|
||||
*/
|
||||
#include <linux/bits.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/mfd/max7360.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pwm.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define MAX7360_NUM_PWMS 8
|
||||
#define MAX7360_PWM_MAX 255
|
||||
#define MAX7360_PWM_STEPS 256
|
||||
#define MAX7360_PWM_PERIOD_NS (2 * NSEC_PER_MSEC)
|
||||
|
||||
struct max7360_pwm_waveform {
|
||||
u8 duty_steps;
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
static int max7360_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct regmap *regmap = pwmchip_get_drvdata(chip);
|
||||
|
||||
/*
|
||||
* Make sure we use the individual PWM configuration register and not
|
||||
* the global one.
|
||||
* We never need to use the global one, so there is no need to revert
|
||||
* that in the .free() callback.
|
||||
*/
|
||||
return regmap_write_bits(regmap, MAX7360_REG_PWMCFG(pwm->hwpwm),
|
||||
MAX7360_PORT_CFG_COMMON_PWM, 0);
|
||||
}
|
||||
|
||||
static int max7360_pwm_round_waveform_tohw(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm,
|
||||
const struct pwm_waveform *wf,
|
||||
void *_wfhw)
|
||||
{
|
||||
struct max7360_pwm_waveform *wfhw = _wfhw;
|
||||
u64 duty_steps;
|
||||
|
||||
/*
|
||||
* Ignore user provided values for period_length_ns and duty_offset_ns:
|
||||
* we only support fixed period of MAX7360_PWM_PERIOD_NS and offset of 0.
|
||||
* Values from 0 to 254 as duty_steps will provide duty cycles of 0/256
|
||||
* to 254/256, while value 255 will provide a duty cycle of 100%.
|
||||
*/
|
||||
if (wf->duty_length_ns >= MAX7360_PWM_PERIOD_NS) {
|
||||
duty_steps = MAX7360_PWM_MAX;
|
||||
} else {
|
||||
duty_steps = (u32)wf->duty_length_ns * MAX7360_PWM_STEPS / MAX7360_PWM_PERIOD_NS;
|
||||
if (duty_steps == MAX7360_PWM_MAX)
|
||||
duty_steps = MAX7360_PWM_MAX - 1;
|
||||
}
|
||||
|
||||
wfhw->duty_steps = min(MAX7360_PWM_MAX, duty_steps);
|
||||
wfhw->enabled = !!wf->period_length_ns;
|
||||
|
||||
if (wf->period_length_ns && wf->period_length_ns < MAX7360_PWM_PERIOD_NS)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max7360_pwm_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
const void *_wfhw, struct pwm_waveform *wf)
|
||||
{
|
||||
const struct max7360_pwm_waveform *wfhw = _wfhw;
|
||||
|
||||
wf->period_length_ns = wfhw->enabled ? MAX7360_PWM_PERIOD_NS : 0;
|
||||
wf->duty_offset_ns = 0;
|
||||
|
||||
if (wfhw->enabled) {
|
||||
if (wfhw->duty_steps == MAX7360_PWM_MAX)
|
||||
wf->duty_length_ns = MAX7360_PWM_PERIOD_NS;
|
||||
else
|
||||
wf->duty_length_ns = DIV_ROUND_UP(wfhw->duty_steps * MAX7360_PWM_PERIOD_NS,
|
||||
MAX7360_PWM_STEPS);
|
||||
} else {
|
||||
wf->duty_length_ns = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max7360_pwm_write_waveform(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm,
|
||||
const void *_wfhw)
|
||||
{
|
||||
struct regmap *regmap = pwmchip_get_drvdata(chip);
|
||||
const struct max7360_pwm_waveform *wfhw = _wfhw;
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
if (wfhw->enabled) {
|
||||
ret = regmap_write(regmap, MAX7360_REG_PWM(pwm->hwpwm), wfhw->duty_steps);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
val = wfhw->enabled ? BIT(pwm->hwpwm) : 0;
|
||||
return regmap_write_bits(regmap, MAX7360_REG_GPIOCTRL, BIT(pwm->hwpwm), val);
|
||||
}
|
||||
|
||||
static int max7360_pwm_read_waveform(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm,
|
||||
void *_wfhw)
|
||||
{
|
||||
struct regmap *regmap = pwmchip_get_drvdata(chip);
|
||||
struct max7360_pwm_waveform *wfhw = _wfhw;
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(regmap, MAX7360_REG_GPIOCTRL, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (val & BIT(pwm->hwpwm)) {
|
||||
wfhw->enabled = true;
|
||||
ret = regmap_read(regmap, MAX7360_REG_PWM(pwm->hwpwm), &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
wfhw->duty_steps = val;
|
||||
} else {
|
||||
wfhw->enabled = false;
|
||||
wfhw->duty_steps = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pwm_ops max7360_pwm_ops = {
|
||||
.request = max7360_pwm_request,
|
||||
.round_waveform_tohw = max7360_pwm_round_waveform_tohw,
|
||||
.round_waveform_fromhw = max7360_pwm_round_waveform_fromhw,
|
||||
.read_waveform = max7360_pwm_read_waveform,
|
||||
.write_waveform = max7360_pwm_write_waveform,
|
||||
};
|
||||
|
||||
static int max7360_pwm_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct pwm_chip *chip;
|
||||
struct regmap *regmap;
|
||||
int ret;
|
||||
|
||||
regmap = dev_get_regmap(dev->parent, NULL);
|
||||
if (!regmap)
|
||||
return dev_err_probe(dev, -ENODEV, "Could not get parent regmap\n");
|
||||
|
||||
/*
|
||||
* This MFD sub-device does not have any associated device tree node:
|
||||
* properties are stored in the device node of the parent (MFD) device
|
||||
* and this same node is used in phandles of client devices.
|
||||
* Reuse this device tree node here, as otherwise the PWM subsystem
|
||||
* would be confused by this topology.
|
||||
*/
|
||||
device_set_of_node_from_dev(dev, dev->parent);
|
||||
|
||||
chip = devm_pwmchip_alloc(dev, MAX7360_NUM_PWMS, 0);
|
||||
if (IS_ERR(chip))
|
||||
return PTR_ERR(chip);
|
||||
chip->ops = &max7360_pwm_ops;
|
||||
|
||||
pwmchip_set_drvdata(chip, regmap);
|
||||
|
||||
ret = devm_pwmchip_add(dev, chip);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to add PWM chip\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver max7360_pwm_driver = {
|
||||
.driver = {
|
||||
.name = "max7360-pwm",
|
||||
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
||||
},
|
||||
.probe = max7360_pwm_probe,
|
||||
};
|
||||
module_platform_driver(max7360_pwm_driver);
|
||||
|
||||
MODULE_DESCRIPTION("MAX7360 PWM driver");
|
||||
MODULE_AUTHOR("Kamel BOUHARA <kamel.bouhara@bootlin.com>");
|
||||
MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -6,6 +6,7 @@
|
||||
struct device;
|
||||
struct fwnode_handle;
|
||||
struct gpio_regmap;
|
||||
struct gpio_chip;
|
||||
struct irq_domain;
|
||||
struct regmap;
|
||||
|
||||
@@ -40,6 +41,13 @@ struct regmap;
|
||||
* @drvdata: (Optional) Pointer to driver specific data which is
|
||||
* not used by gpio-remap but is provided "as is" to the
|
||||
* driver callback(s).
|
||||
* @init_valid_mask: (Optional) Routine to initialize @valid_mask, to be used
|
||||
* if not all GPIOs are valid.
|
||||
* @regmap_irq_chip: (Optional) Pointer on an regmap_irq_chip structure. If
|
||||
* set, a regmap-irq device will be created and the IRQ
|
||||
* domain will be set accordingly.
|
||||
* @regmap_irq_line (Optional) The IRQ the device uses to signal interrupts.
|
||||
* @regmap_irq_flags (Optional) The IRQF_ flags to use for the interrupt.
|
||||
*
|
||||
* The ->reg_mask_xlate translates a given base address and GPIO offset to
|
||||
* register and mask pair. The base address is one of the given register
|
||||
@@ -78,10 +86,20 @@ struct gpio_regmap_config {
|
||||
int ngpio_per_reg;
|
||||
struct irq_domain *irq_domain;
|
||||
|
||||
#ifdef CONFIG_REGMAP_IRQ
|
||||
struct regmap_irq_chip *regmap_irq_chip;
|
||||
int regmap_irq_line;
|
||||
unsigned long regmap_irq_flags;
|
||||
#endif
|
||||
|
||||
int (*reg_mask_xlate)(struct gpio_regmap *gpio, unsigned int base,
|
||||
unsigned int offset, unsigned int *reg,
|
||||
unsigned int *mask);
|
||||
|
||||
int (*init_valid_mask)(struct gpio_chip *gc,
|
||||
unsigned long *valid_mask,
|
||||
unsigned int ngpios);
|
||||
|
||||
void *drvdata;
|
||||
};
|
||||
|
||||
|
||||
109
include/linux/mfd/max7360.h
Normal file
109
include/linux/mfd/max7360.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
#ifndef __LINUX_MFD_MAX7360_H
|
||||
#define __LINUX_MFD_MAX7360_H
|
||||
|
||||
#include <linux/bits.h>
|
||||
|
||||
#define MAX7360_MAX_KEY_ROWS 8
|
||||
#define MAX7360_MAX_KEY_COLS 8
|
||||
#define MAX7360_MAX_KEY_NUM (MAX7360_MAX_KEY_ROWS * MAX7360_MAX_KEY_COLS)
|
||||
#define MAX7360_ROW_SHIFT 3
|
||||
|
||||
#define MAX7360_MAX_GPIO 8
|
||||
#define MAX7360_MAX_GPO 6
|
||||
#define MAX7360_PORT_PWM_COUNT 8
|
||||
#define MAX7360_PORT_RTR_PIN (MAX7360_PORT_PWM_COUNT - 1)
|
||||
|
||||
/*
|
||||
* MAX7360 registers
|
||||
*/
|
||||
#define MAX7360_REG_KEYFIFO 0x00
|
||||
#define MAX7360_REG_CONFIG 0x01
|
||||
#define MAX7360_REG_DEBOUNCE 0x02
|
||||
#define MAX7360_REG_INTERRUPT 0x03
|
||||
#define MAX7360_REG_PORTS 0x04
|
||||
#define MAX7360_REG_KEYREP 0x05
|
||||
#define MAX7360_REG_SLEEP 0x06
|
||||
|
||||
/*
|
||||
* MAX7360 GPIO registers
|
||||
*
|
||||
* All these registers are reset together when writing bit 3 of
|
||||
* MAX7360_REG_GPIOCFG.
|
||||
*/
|
||||
#define MAX7360_REG_GPIOCFG 0x40
|
||||
#define MAX7360_REG_GPIOCTRL 0x41
|
||||
#define MAX7360_REG_GPIODEB 0x42
|
||||
#define MAX7360_REG_GPIOCURR 0x43
|
||||
#define MAX7360_REG_GPIOOUTM 0x44
|
||||
#define MAX7360_REG_PWMCOM 0x45
|
||||
#define MAX7360_REG_RTRCFG 0x46
|
||||
#define MAX7360_REG_I2C_TIMEOUT 0x48
|
||||
#define MAX7360_REG_GPIOIN 0x49
|
||||
#define MAX7360_REG_RTR_CNT 0x4A
|
||||
#define MAX7360_REG_PWMBASE 0x50
|
||||
#define MAX7360_REG_PWMCFGBASE 0x58
|
||||
|
||||
#define MAX7360_REG_GPIO_LAST 0x5F
|
||||
|
||||
#define MAX7360_REG_PWM(x) (MAX7360_REG_PWMBASE + (x))
|
||||
#define MAX7360_REG_PWMCFG(x) (MAX7360_REG_PWMCFGBASE + (x))
|
||||
|
||||
/*
|
||||
* Configuration register bits
|
||||
*/
|
||||
#define MAX7360_FIFO_EMPTY 0x3F
|
||||
#define MAX7360_FIFO_OVERFLOW 0x7F
|
||||
#define MAX7360_FIFO_RELEASE BIT(6)
|
||||
#define MAX7360_FIFO_COL GENMASK(5, 3)
|
||||
#define MAX7360_FIFO_ROW GENMASK(2, 0)
|
||||
|
||||
#define MAX7360_CFG_SLEEP BIT(7)
|
||||
#define MAX7360_CFG_INTERRUPT BIT(5)
|
||||
#define MAX7360_CFG_KEY_RELEASE BIT(3)
|
||||
#define MAX7360_CFG_WAKEUP BIT(1)
|
||||
#define MAX7360_CFG_TIMEOUT BIT(0)
|
||||
|
||||
#define MAX7360_DEBOUNCE GENMASK(4, 0)
|
||||
#define MAX7360_DEBOUNCE_MIN 9
|
||||
#define MAX7360_DEBOUNCE_MAX 40
|
||||
#define MAX7360_PORTS GENMASK(8, 5)
|
||||
|
||||
#define MAX7360_INTERRUPT_TIME_MASK GENMASK(4, 0)
|
||||
#define MAX7360_INTERRUPT_FIFO_MASK GENMASK(7, 5)
|
||||
|
||||
#define MAX7360_PORT_CFG_INTERRUPT_MASK BIT(7)
|
||||
#define MAX7360_PORT_CFG_INTERRUPT_EDGES BIT(6)
|
||||
#define MAX7360_PORT_CFG_COMMON_PWM BIT(5)
|
||||
|
||||
/*
|
||||
* Autosleep register values
|
||||
*/
|
||||
#define MAX7360_AUTOSLEEP_8192MS 0x01
|
||||
#define MAX7360_AUTOSLEEP_4096MS 0x02
|
||||
#define MAX7360_AUTOSLEEP_2048MS 0x03
|
||||
#define MAX7360_AUTOSLEEP_1024MS 0x04
|
||||
#define MAX7360_AUTOSLEEP_512MS 0x05
|
||||
#define MAX7360_AUTOSLEEP_256MS 0x06
|
||||
|
||||
#define MAX7360_GPIO_CFG_RTR_EN BIT(7)
|
||||
#define MAX7360_GPIO_CFG_GPIO_EN BIT(4)
|
||||
#define MAX7360_GPIO_CFG_GPIO_RST BIT(3)
|
||||
|
||||
#define MAX7360_ROT_DEBOUNCE GENMASK(3, 0)
|
||||
#define MAX7360_ROT_DEBOUNCE_MIN 0
|
||||
#define MAX7360_ROT_DEBOUNCE_MAX 15
|
||||
#define MAX7360_ROT_INTCNT GENMASK(6, 4)
|
||||
#define MAX7360_ROT_INTCNT_DLY BIT(7)
|
||||
|
||||
#define MAX7360_INT_INTI 0
|
||||
#define MAX7360_INT_INTK 1
|
||||
|
||||
#define MAX7360_INT_GPIO 0
|
||||
#define MAX7360_INT_KEYPAD 1
|
||||
#define MAX7360_INT_ROTARY 2
|
||||
|
||||
#define MAX7360_NR_INTERNAL_IRQS 3
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user