mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-18 22:09:30 +02:00
Merge tag 'gpio-updates-for-v7.3-rc1-v2' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux
Pull gpio updates from Bartosz Golaszewski:
"GPIO core:
- extend the gpio-regmap abstraction layer with more features
allowing users to override configuration setting, translate
register values and masks and enable/disable interrupts
- extend GPIO kunit tests with suites verifying probe ordering by
software node devlink support and software node hogs
- shrink GPIO kunit initialization code
- coding style updates (remove commas from sentinels where
applicable)
- with all users now converted treewide to using real firmware node
links for software node GPIO lookup: remove the deprecated
label-matching mechanism from from GPIO core
- drop redundant return value check of nonseekable_open() in
gpiolib-cdev
- use IRQ trigger helpers where applicable
Driver updates:
- refactor error paths and logging in gpio-nomadik
- use more modern interfaces for getting resources in gpio-rockchip,
gpio-bt8xx and gpio-pca9570
- add missing MODULE_DEVICE_TABLE() to gpio-sifive and gpio-vf610
- drop unused FILONOFF macro from gpio-rcar
- extend build coverage of ioport GPIO drivers with COMPILE_TEST=y
- only enable the gpio-rtd driver by default with ARCH_REALTEK=y to
avoid bloating the build
- refactor coding style in several drivers
- use correct endianess translation in gpio-pcf85x
- add wake-up interrupt support to gpio-mvebu
- apply initial value in direction output setter in gpio-by-pinctrl
Misc:
- replace linux/gpio.h inclusions treewide with linux/gpio/legacy.h
which now exports all the deprecated APIs
- select GPIOLIB_LEGACY in Kconfig where required treewide
- use software nodes for gpio-keys in MFD drivers
Devicetree bindings:
- describe the realtek rtd1625 GPIO controller
- document new models for gpio-pca95xx and gpio-cadence
- document new property in gpio-rockchip"
* tag 'gpio-updates-for-v7.3-rc1-v2' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux: (61 commits)
gpio: gpio-by-pinctrl: Apply initial value in direction output wrapper
dt-bindings: gpio: rockchip,gpio-bank: Add rockchip,grf property
gpio: Use IRQ trigger mask helpers
gpio: allow COMPILE_TEST for IOPORT drivers
gpio: realtek: Add driver for Realtek DHC RTD1625 SoC
gpio: regmap: Add IRQ enable/disable helpers
gpio: regmap: Add set_config callback
gpio: regmap: Add value_xlate callback
gpio: regmap: Add gpio_regmap_operation to extend reg_mask_xlate callback
gpio: regmap: Order kernel-doc descriptions with the actual appearance
gpio: regmap: Apply default resource callbacks for regmap IRQ chip
gpio: regmap: Provide default IRQ resource request and release callbacks
Revert "gpio: realtek: Add driver for Realtek DHC RTD1625 SoC"
gpib: gpio: replace linux/gpio.h inclusion
Input: matrix_keyboard - replace linux/gpio.h inclusion
phy: replace linux/gpio.h inclusions
pcmcia: replace linux/gpio.h inclusions
ASoC: replace linux/gpio.h inclusions
mfd: replace linux/gpio.h inclusions
sh: replace linux/gpio.h inclusions
...
This commit is contained in:
@@ -14,8 +14,11 @@ properties:
|
||||
oneOf:
|
||||
- const: cdns,gpio-r1p02
|
||||
- items:
|
||||
- enum:
|
||||
- axiado,ax3000-gpio
|
||||
- const: axiado,ax3000-gpio
|
||||
- const: cdns,gpio-r1p02
|
||||
- items:
|
||||
- const: axiado,ax3005-gpio
|
||||
- const: axiado,ax3000-gpio
|
||||
- const: cdns,gpio-r1p02
|
||||
|
||||
reg:
|
||||
|
||||
@@ -22,6 +22,9 @@ properties:
|
||||
- items:
|
||||
- const: diodes,pi4ioe5v6534q
|
||||
- const: nxp,pcal6534
|
||||
- items:
|
||||
- const: kinetic,kts1622
|
||||
- const: ti,tcal6416
|
||||
- items:
|
||||
- enum:
|
||||
- exar,xra1202
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
# Copyright 2023 Realtek Semiconductor Corporation
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/gpio/realtek,rtd1625-gpio.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Realtek DHC RTD1625 GPIO controller
|
||||
|
||||
maintainers:
|
||||
- Tzuyi Chang <tychang@realtek.com>
|
||||
|
||||
description: |
|
||||
GPIO controller for the Realtek RTD1625 SoC, featuring a per-pin register
|
||||
architecture that differs significantly from earlier RTD series controllers.
|
||||
Each GPIO has dedicated registers for configuration (direction, input/output
|
||||
values, debounce), and interrupt control supporting edge and level detection
|
||||
modes.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- realtek,rtd1625-iso-gpio
|
||||
- realtek,rtd1625-isom-gpio
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
items:
|
||||
- description: Interrupt number of the assert GPIO interrupt, which is
|
||||
triggered when there is a rising edge.
|
||||
- description: Interrupt number of the deassert GPIO interrupt, which is
|
||||
triggered when there is a falling edge.
|
||||
- description: Interrupt number of the level-sensitive GPIO interrupt,
|
||||
triggered by a configured logic level.
|
||||
|
||||
interrupt-controller: true
|
||||
|
||||
"#interrupt-cells":
|
||||
const: 2
|
||||
|
||||
gpio-ranges: true
|
||||
|
||||
gpio-controller: true
|
||||
|
||||
"#gpio-cells":
|
||||
const: 2
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- gpio-ranges
|
||||
- gpio-controller
|
||||
- "#gpio-cells"
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
gpio@89100 {
|
||||
compatible = "realtek,rtd1625-isom-gpio";
|
||||
reg = <0x89100 0x30>;
|
||||
interrupt-parent = <&iso_m_irq_mux>;
|
||||
interrupts = <0>, <1>, <2>;
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <2>;
|
||||
gpio-ranges = <&isom_pinctrl 0 0 4>;
|
||||
gpio-controller;
|
||||
#gpio-cells = <2>;
|
||||
};
|
||||
@@ -44,6 +44,13 @@ properties:
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
|
||||
rockchip,grf:
|
||||
$ref: /schemas/types.yaml#/definitions/phandle
|
||||
description:
|
||||
The phandle of the syscon node managing the IO control registers
|
||||
of this bank, on SoCs such as the RV1106 where each GPIO bank has
|
||||
its own IOC block.
|
||||
|
||||
patternProperties:
|
||||
"^.+-hog(-[0-9]+)?$":
|
||||
type: object
|
||||
|
||||
@@ -25094,10 +25094,12 @@ R: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
|
||||
R: Daniel Scally <djrscally@gmail.com>
|
||||
R: Heikki Krogerus <heikki.krogerus@linux.intel.com>
|
||||
R: Sakari Ailus <sakari.ailus@linux.intel.com>
|
||||
R: Bartosz Golaszewski <brgl@kernel.org>
|
||||
L: linux-acpi@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/base/property.c
|
||||
F: drivers/base/swnode.c
|
||||
F: drivers/base/test/swnode-devlink-test.c
|
||||
F: include/linux/fwnode.h
|
||||
F: include/linux/property.h
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm.h>
|
||||
#include <asm/bootinfo.h>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/gpio/property.h>
|
||||
#include <linux/init.h>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "bcm47xx_private.h"
|
||||
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <bcm47xx_board.h>
|
||||
#include <bcm47xx.h>
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define BOARD_BCM963XX_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/leds.h>
|
||||
#include <bcm63xx_dev_enet.h>
|
||||
#include <bcm63xx_dev_usb_usbd.h>
|
||||
|
||||
@@ -462,6 +462,7 @@ config CPU_SUBTYPE_SHX3
|
||||
select CPU_SHX3
|
||||
select GENERIC_CLOCKEVENTS_BROADCAST if SMP
|
||||
select GPIOLIB
|
||||
select GPIOLIB_LEGACY
|
||||
select PINCTRL
|
||||
|
||||
# SH4AL-DSP Processor Support
|
||||
|
||||
@@ -80,6 +80,7 @@ config SH_7724_SOLUTION_ENGINE
|
||||
select SOLUTION_ENGINE
|
||||
depends on CPU_SUBTYPE_SH7724
|
||||
select GPIOLIB
|
||||
select GPIOLIB_LEGACY
|
||||
select REGULATOR_FIXED_VOLTAGE if REGULATOR
|
||||
imply SND_SOC_AK4642 if SND_SIMPLE_CARD
|
||||
help
|
||||
@@ -199,6 +200,7 @@ config SH_SH7757LCR
|
||||
bool "SH7757LCR"
|
||||
depends on CPU_SUBTYPE_SH7757
|
||||
select GPIOLIB
|
||||
select GPIOLIB_LEGACY
|
||||
select REGULATOR_FIXED_VOLTAGE if REGULATOR
|
||||
|
||||
config SH_SH7785LCR
|
||||
@@ -226,6 +228,7 @@ config SH_URQUELL
|
||||
bool "Urquell"
|
||||
depends on CPU_SUBTYPE_SH7786
|
||||
select GPIOLIB
|
||||
select GPIOLIB_LEGACY
|
||||
select HAVE_PCI
|
||||
select NO_IOPORT_MAP if !PCI
|
||||
|
||||
@@ -233,6 +236,7 @@ config SH_MIGOR
|
||||
bool "Migo-R"
|
||||
depends on CPU_SUBTYPE_SH7722
|
||||
select GPIOLIB
|
||||
select GPIOLIB_LEGACY
|
||||
select REGULATOR_FIXED_VOLTAGE if REGULATOR
|
||||
help
|
||||
Select Migo-R if configuring for the SH7722 Migo-R platform
|
||||
@@ -242,6 +246,7 @@ config SH_AP325RXA
|
||||
bool "AP-325RXA"
|
||||
depends on CPU_SUBTYPE_SH7723
|
||||
select GPIOLIB
|
||||
select GPIOLIB_LEGACY
|
||||
select REGULATOR_FIXED_VOLTAGE if REGULATOR
|
||||
help
|
||||
Renesas "AP-325RXA" support.
|
||||
@@ -251,6 +256,7 @@ config SH_KFR2R09
|
||||
bool "KFR2R09"
|
||||
depends on CPU_SUBTYPE_SH7724
|
||||
select GPIOLIB
|
||||
select GPIOLIB_LEGACY
|
||||
select REGULATOR_FIXED_VOLTAGE if REGULATOR
|
||||
help
|
||||
"Kit For R2R for 2009" support.
|
||||
@@ -259,6 +265,7 @@ config SH_ECOVEC
|
||||
bool "EcoVec"
|
||||
depends on CPU_SUBTYPE_SH7724
|
||||
select GPIOLIB
|
||||
select GPIOLIB_LEGACY
|
||||
select REGULATOR_FIXED_VOLTAGE if REGULATOR
|
||||
imply SND_SOC_DA7210 if SND_SIMPLE_CARD
|
||||
help
|
||||
@@ -329,6 +336,7 @@ config SH_MAGIC_PANEL_R2
|
||||
bool "Magic Panel R2"
|
||||
depends on CPU_SUBTYPE_SH7720
|
||||
select GPIOLIB
|
||||
select GPIOLIB_LEGACY
|
||||
select REGULATOR_FIXED_VOLTAGE if REGULATOR
|
||||
help
|
||||
Select Magic Panel R2 if configuring for Magic Panel R2.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <linux/irq.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/regulator/fixed.h>
|
||||
#include <linux/regulator/machine.h>
|
||||
#include <linux/smsc911x.h>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/regulator/fixed.h>
|
||||
#include <linux/regulator/machine.h>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <linux/smc91x.h>
|
||||
#include <linux/mtd/physmap.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/sh_intc.h>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include <linux/clkdev.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/i2c.h>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/platform_data/sh_mmcif.h>
|
||||
#include <linux/mtd/physmap.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/platform_data/gpio_backlight.h>
|
||||
#include <linux/platform_data/tmio.h>
|
||||
|
||||
@@ -20,6 +20,7 @@ config SH_R7785RP
|
||||
bool "R7785RP board support"
|
||||
depends on CPU_SUBTYPE_SH7785
|
||||
select GPIOLIB
|
||||
select GPIOLIB_LEGACY
|
||||
|
||||
endchoice
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Copyright (C) 2008 Paul Mundt
|
||||
*/
|
||||
#include <linux/init.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <cpu/sh7785.h>
|
||||
#include <mach/highlander.h>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <video/sh_mobile_lcdc.h>
|
||||
#include <mach/kfr2r09.h>
|
||||
#include <cpu/sh7724.h>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#include <linux/clkdev.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <video/sh_mobile_lcdc.h>
|
||||
#include <cpu/sh7722.h>
|
||||
#include <mach/migor.h>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include <linux/smc91x.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/sh_intc.h>
|
||||
|
||||
@@ -12,16 +12,19 @@ config SH_RSK7201
|
||||
config SH_RSK7203
|
||||
bool "RSK7203"
|
||||
select GPIOLIB
|
||||
select GPIOLIB_LEGACY
|
||||
depends on CPU_SUBTYPE_SH7203
|
||||
|
||||
config SH_RSK7264
|
||||
bool "RSK2+SH7264"
|
||||
select GPIOLIB
|
||||
select GPIOLIB_LEGACY
|
||||
depends on CPU_SUBTYPE_SH7264
|
||||
|
||||
config SH_RSK7269
|
||||
bool "RSK2+SH7269"
|
||||
select GPIOLIB
|
||||
select GPIOLIB_LEGACY
|
||||
depends on CPU_SUBTYPE_SH7269
|
||||
|
||||
endchoice
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/smsc911x.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <asm/machvec.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/input/sh_keysc.h>
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
#ifndef __ASM_SH_MAGICPANELR2_H
|
||||
#define __ASM_SH_MAGICPANELR2_H
|
||||
|
||||
#include <linux/gpio.h>
|
||||
|
||||
#define __IO_PREFIX mpr2
|
||||
#include <asm/io_generic.h>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include <linux/serial.h>
|
||||
#include <linux/serial_sci.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/sh_timer.h>
|
||||
#include <linux/sh_intc.h>
|
||||
#include <cpu/shx3.h>
|
||||
|
||||
@@ -2978,6 +2978,7 @@ config OLPC
|
||||
bool "One Laptop Per Child support"
|
||||
depends on !X86_PAE
|
||||
select GPIOLIB
|
||||
select GPIOLIB_LEGACY
|
||||
select OF
|
||||
select OF_PROMTREE
|
||||
select IRQ_DOMAIN
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#include <linux/cs5535.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
@@ -296,6 +296,26 @@ static int regmap_irq_set_wake(struct irq_data *data, unsigned int on)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regmap_irq_reqres(struct irq_data *data)
|
||||
{
|
||||
struct regmap_irq_chip_data *d = irq_data_get_irq_chip_data(data);
|
||||
irq_hw_number_t hwirq = irqd_to_hwirq(data);
|
||||
|
||||
if (d->chip->irq_reqres)
|
||||
return d->chip->irq_reqres(d->chip->irq_drv_data, hwirq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void regmap_irq_relres(struct irq_data *data)
|
||||
{
|
||||
struct regmap_irq_chip_data *d = irq_data_get_irq_chip_data(data);
|
||||
irq_hw_number_t hwirq = irqd_to_hwirq(data);
|
||||
|
||||
if (d->chip->irq_relres)
|
||||
d->chip->irq_relres(d->chip->irq_drv_data, hwirq);
|
||||
}
|
||||
|
||||
static const struct irq_chip regmap_irq_chip = {
|
||||
.irq_bus_lock = regmap_irq_lock,
|
||||
.irq_bus_sync_unlock = regmap_irq_sync_unlock,
|
||||
@@ -303,6 +323,8 @@ static const struct irq_chip regmap_irq_chip = {
|
||||
.irq_enable = regmap_irq_enable,
|
||||
.irq_set_type = regmap_irq_set_type,
|
||||
.irq_set_wake = regmap_irq_set_wake,
|
||||
.irq_request_resources = regmap_irq_reqres,
|
||||
.irq_release_resources = regmap_irq_relres,
|
||||
};
|
||||
|
||||
static inline int read_sub_irq_data(struct regmap_irq_chip_data *data,
|
||||
|
||||
@@ -699,6 +699,62 @@ software_node_graph_parse_endpoint(const struct fwnode_handle *fwnode,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int software_node_add_links(struct fwnode_handle *fwnode)
|
||||
{
|
||||
const struct software_node_ref_args *ref, *ref_array;
|
||||
struct swnode *swnode = to_swnode(fwnode);
|
||||
const struct property_entry *prop;
|
||||
struct fwnode_handle *refnode;
|
||||
unsigned int count;
|
||||
|
||||
if (!swnode || !swnode->node->properties)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Unlike Device Tree, where phandles appear in many non-supplier
|
||||
* contexts and a curated allowlist is required, a software node only
|
||||
* carries a DEV_PROP_REF property when the author explicitly describes
|
||||
* a reference to another node. Every such reference is therefore an
|
||||
* intentional supplier dependency, so we create fwnode links for all
|
||||
* of them.
|
||||
*/
|
||||
for (prop = swnode->node->properties; prop->name; prop++) {
|
||||
if (prop->type != DEV_PROP_REF || prop->is_inline)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* TODO: Graph "remote-endpoint" references go both ways
|
||||
* between endpoint child nodes and would create endpoint
|
||||
* cycles. Let's leave it out for now until we have potential
|
||||
* users.
|
||||
*/
|
||||
if (!strcmp(prop->name, "remote-endpoint"))
|
||||
continue;
|
||||
|
||||
ref_array = prop->pointer;
|
||||
count = prop->length / sizeof(*ref_array);
|
||||
|
||||
for (unsigned int i = 0; i < count; i++) {
|
||||
ref = &ref_array[i];
|
||||
|
||||
if (ref->swnode)
|
||||
refnode = software_node_fwnode(ref->swnode);
|
||||
else if (ref->fwnode)
|
||||
refnode = ref->fwnode;
|
||||
else
|
||||
continue;
|
||||
|
||||
/* Supplier not registered yet, or self-reference. */
|
||||
if (!refnode || refnode == &swnode->fwnode)
|
||||
continue;
|
||||
|
||||
fwnode_link_add(&swnode->fwnode, refnode, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct fwnode_operations software_node_ops = {
|
||||
.get = software_node_get,
|
||||
.put = software_node_put,
|
||||
@@ -716,6 +772,7 @@ static const struct fwnode_operations software_node_ops = {
|
||||
.graph_get_remote_endpoint = software_node_graph_get_remote_endpoint,
|
||||
.graph_get_port_parent = software_node_graph_get_port_parent,
|
||||
.graph_parse_endpoint = software_node_graph_parse_endpoint,
|
||||
.add_links = software_node_add_links,
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
@@ -787,6 +844,8 @@ static void software_node_release(struct kobject *kobj)
|
||||
{
|
||||
struct swnode *swnode = kobj_to_swnode(kobj);
|
||||
|
||||
fwnode_links_purge(&swnode->fwnode);
|
||||
|
||||
if (swnode->parent) {
|
||||
ida_free(&swnode->parent->child_ids, swnode->id);
|
||||
list_del(&swnode->entry);
|
||||
@@ -1105,6 +1164,17 @@ void software_node_notify(struct device *dev)
|
||||
if (!swnode)
|
||||
return;
|
||||
|
||||
/*
|
||||
* When the software node is the device's secondary firmware node,
|
||||
* the core only records the owning device on the primary fwnode
|
||||
* (see device_add()). fw_devlink resolves a supplier device through
|
||||
* fwnode->dev, so without this a consumer referencing the software
|
||||
* node could never find the supplier device and would defer forever.
|
||||
* Make fwnode.dev point to its owner in that case.
|
||||
*/
|
||||
if (!device_match_fwnode(dev, &swnode->fwnode) && !swnode->fwnode.dev)
|
||||
swnode->fwnode.dev = dev;
|
||||
|
||||
swnode_get(swnode);
|
||||
ret = sysfs_create_link(&dev->kobj, &swnode->kobj, "software_node");
|
||||
if (ret)
|
||||
@@ -1127,6 +1197,15 @@ void software_node_notify_remove(struct device *dev)
|
||||
|
||||
sysfs_remove_link(&swnode->kobj, dev_name(dev));
|
||||
sysfs_remove_link(&dev->kobj, "software_node");
|
||||
|
||||
/*
|
||||
* Drop the device pointer mirrored onto a secondary software node in
|
||||
* software_node_notify(). For a primary software node the core owns
|
||||
* fwnode->dev and clears it in device_del().
|
||||
*/
|
||||
if (!device_match_fwnode(dev, &swnode->fwnode) && swnode->fwnode.dev == dev)
|
||||
swnode->fwnode.dev = NULL;
|
||||
|
||||
swnode_put(swnode);
|
||||
|
||||
if (swnode->managed) {
|
||||
|
||||
@@ -18,3 +18,8 @@ config DRIVER_PE_KUNIT_TEST
|
||||
tristate "KUnit Tests for property entry API" if !KUNIT_ALL_TESTS
|
||||
depends on KUNIT
|
||||
default KUNIT_ALL_TESTS
|
||||
|
||||
config DRIVER_SWNODE_KUNIT_TEST
|
||||
tristate "KUnit Tests for software node fw_devlink links" if !KUNIT_ALL_TESTS
|
||||
depends on KUNIT
|
||||
default KUNIT_ALL_TESTS
|
||||
|
||||
@@ -6,3 +6,5 @@ obj-$(CONFIG_DM_KUNIT_TEST) += platform-device-test.o
|
||||
|
||||
obj-$(CONFIG_DRIVER_PE_KUNIT_TEST) += property-entry-test.o
|
||||
CFLAGS_property-entry-test.o += $(DISABLE_STRUCTLEAK_PLUGIN)
|
||||
|
||||
obj-$(CONFIG_DRIVER_SWNODE_KUNIT_TEST) += swnode-devlink-test.o
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) Qualcomm Technologies, Inc. and/or its subsidiaries
|
||||
*/
|
||||
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/fwnode.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#include <kunit/fwnode.h>
|
||||
#include <kunit/platform_device.h>
|
||||
#include <kunit/test.h>
|
||||
|
||||
static int swnode_count_suppliers(struct fwnode_handle *fwnode)
|
||||
{
|
||||
struct fwnode_link *link;
|
||||
unsigned int count = 0;
|
||||
|
||||
/*
|
||||
* The suppliers and consumers lists should typically only be accessed
|
||||
* with the fwnode_link_lock taken but it's private to the driver core.
|
||||
*
|
||||
* These are tests and at this point nobody should be modifying them so
|
||||
* let's just access the list.
|
||||
*/
|
||||
list_for_each_entry(link, &fwnode->suppliers, c_hook)
|
||||
count++;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* True if a supplier link con->sup exists, checked from both list ends. */
|
||||
static bool swnode_has_link(struct fwnode_handle *consumer,
|
||||
struct fwnode_handle *supplier)
|
||||
{
|
||||
bool from_con = false, from_sup = false;
|
||||
struct fwnode_link *link;
|
||||
|
||||
list_for_each_entry(link, &consumer->suppliers, c_hook) {
|
||||
if (link->supplier == supplier && link->consumer == consumer)
|
||||
from_con = true;
|
||||
}
|
||||
|
||||
list_for_each_entry(link, &supplier->consumers, s_hook) {
|
||||
if (link->supplier == supplier && link->consumer == consumer)
|
||||
from_sup = true;
|
||||
}
|
||||
|
||||
return from_con && from_sup;
|
||||
}
|
||||
|
||||
/* A single reference creates exactly one supplier link, on both list ends. */
|
||||
static void swnode_devlink_test_single_ref(struct kunit *test)
|
||||
{
|
||||
static const struct software_node supp_swnode = {
|
||||
.name = "swnode-devlink-test-supplier",
|
||||
};
|
||||
|
||||
struct fwnode_handle *cons_fwnode, *supp_fwnode;
|
||||
int ret;
|
||||
|
||||
const struct property_entry props[] = {
|
||||
PROPERTY_ENTRY_REF("supplier", &supp_swnode),
|
||||
{ }
|
||||
};
|
||||
|
||||
supp_fwnode = kunit_software_node_register(test, &supp_swnode);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, supp_fwnode);
|
||||
|
||||
cons_fwnode = kunit_fwnode_create_software_node(test, props, NULL);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons_fwnode);
|
||||
|
||||
ret = fwnode_call_int_op(cons_fwnode, add_links);
|
||||
KUNIT_EXPECT_EQ(test, ret, 0);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, swnode_count_suppliers(cons_fwnode), 1);
|
||||
KUNIT_EXPECT_TRUE(test, swnode_has_link(cons_fwnode, supp_fwnode));
|
||||
}
|
||||
|
||||
/* Multiple distinct references create multiple supplier links. */
|
||||
static void swnode_devlink_test_multiple_refs(struct kunit *test)
|
||||
{
|
||||
static const struct software_node supp1_swnode = {
|
||||
.name = "swnode-devlink-test-supplier-1",
|
||||
};
|
||||
static const struct software_node supp2_swnode = {
|
||||
.name = "swnode-devlink-test-supplier-2",
|
||||
};
|
||||
static const struct software_node *supp_nodes[] = {
|
||||
&supp1_swnode, &supp2_swnode, NULL
|
||||
};
|
||||
|
||||
const struct property_entry props[] = {
|
||||
PROPERTY_ENTRY_REF("foo", &supp1_swnode),
|
||||
PROPERTY_ENTRY_REF("bar", &supp2_swnode),
|
||||
{ }
|
||||
};
|
||||
|
||||
struct fwnode_handle *fwnode;
|
||||
int ret;
|
||||
|
||||
ret = kunit_software_node_register_node_group(test, supp_nodes);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
fwnode = kunit_fwnode_create_software_node(test, props, NULL);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode);
|
||||
|
||||
ret = fwnode_call_int_op(fwnode, add_links);
|
||||
KUNIT_EXPECT_EQ(test, ret, 0);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, swnode_count_suppliers(fwnode), 2);
|
||||
KUNIT_EXPECT_TRUE(test, swnode_has_link(fwnode, software_node_fwnode(&supp1_swnode)));
|
||||
KUNIT_EXPECT_TRUE(test, swnode_has_link(fwnode, software_node_fwnode(&supp2_swnode)));
|
||||
}
|
||||
|
||||
/* A reference to an unregistered node creates no link (graceful skip). */
|
||||
static void swnode_devlink_test_unregistered_ref(struct kunit *test)
|
||||
{
|
||||
static const struct software_node supp_swnode = {
|
||||
.name = "swnode-devlink-test-supplier",
|
||||
};
|
||||
|
||||
const struct property_entry props[] = {
|
||||
PROPERTY_ENTRY_REF("supplier", &supp_swnode),
|
||||
{ }
|
||||
};
|
||||
|
||||
struct fwnode_handle *fwnode;
|
||||
int ret;
|
||||
|
||||
fwnode = kunit_fwnode_create_software_node(test, props, NULL);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode);
|
||||
|
||||
ret = fwnode_call_int_op(fwnode, add_links);
|
||||
KUNIT_EXPECT_EQ(test, ret, 0);
|
||||
KUNIT_EXPECT_EQ(test, swnode_count_suppliers(fwnode), 0);
|
||||
}
|
||||
|
||||
/* Graph "remote-endpoint" references are excluded. */
|
||||
static void swnode_devlink_test_remote_endpoint_excluded(struct kunit *test)
|
||||
{
|
||||
static const struct software_node ep_swnode = {
|
||||
.name = "swnode-devlink-test-end-point"
|
||||
};
|
||||
|
||||
const struct property_entry props[] = {
|
||||
PROPERTY_ENTRY_REF("remote-endpoint", &ep_swnode),
|
||||
{ }
|
||||
};
|
||||
|
||||
struct fwnode_handle *cons_fwnode, *supp_fwnode;
|
||||
int ret;
|
||||
|
||||
supp_fwnode = kunit_software_node_register(test, &ep_swnode);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, supp_fwnode);
|
||||
|
||||
cons_fwnode = kunit_fwnode_create_software_node(test, props, NULL);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons_fwnode);
|
||||
|
||||
ret = fwnode_call_int_op(cons_fwnode, add_links);
|
||||
KUNIT_EXPECT_EQ(test, ret, 0);
|
||||
KUNIT_EXPECT_EQ(test, swnode_count_suppliers(cons_fwnode), 0);
|
||||
}
|
||||
|
||||
/* A reference array creates one link per registered element. */
|
||||
static void swnode_devlink_test_ref_array(struct kunit *test)
|
||||
{
|
||||
static const struct software_node supp1_swnode = {
|
||||
.name = "swnode-devlink-test-supplier-1",
|
||||
};
|
||||
static const struct software_node supp2_swnode = {
|
||||
.name = "swnode-devlink-test-supplier-2",
|
||||
};
|
||||
static const struct software_node *supp_nodes[] = {
|
||||
&supp1_swnode, &supp2_swnode, NULL
|
||||
};
|
||||
static const struct software_node_ref_args refs[] = {
|
||||
SOFTWARE_NODE_REFERENCE(&supp1_swnode),
|
||||
SOFTWARE_NODE_REFERENCE(&supp2_swnode, 4, 2),
|
||||
};
|
||||
|
||||
const struct property_entry props[] = {
|
||||
PROPERTY_ENTRY_REF_ARRAY("suppliers", refs),
|
||||
{ }
|
||||
};
|
||||
|
||||
struct fwnode_handle *fwnode;
|
||||
int ret;
|
||||
|
||||
ret = kunit_software_node_register_node_group(test, supp_nodes);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
fwnode = kunit_fwnode_create_software_node(test, props, NULL);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode);
|
||||
|
||||
ret = fwnode_call_int_op(fwnode, add_links);
|
||||
KUNIT_EXPECT_EQ(test, ret, 0);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, swnode_count_suppliers(fwnode), 2);
|
||||
KUNIT_EXPECT_TRUE(test, swnode_has_link(fwnode, software_node_fwnode(&supp1_swnode)));
|
||||
KUNIT_EXPECT_TRUE(test, swnode_has_link(fwnode, software_node_fwnode(&supp2_swnode)));
|
||||
}
|
||||
|
||||
/*
|
||||
* End-to-end test: fw_devlink must defer a consumer's probe until its
|
||||
* supplier has probed.
|
||||
*
|
||||
* The reference created by software_node_add_links() is only useful if the
|
||||
* driver core promotes it to a real device_link and uses it to order probing.
|
||||
* This test drives actual probing through the platform bus and asserts the
|
||||
* supplier binds before the consumer.
|
||||
*/
|
||||
|
||||
#define SWNODE_DEVLINK_TEST_SUPPLIER "swnode-link-supplier"
|
||||
#define SWNODE_DEVLINK_TEST_CONSUMER "swnode-link-consumer"
|
||||
#define SWNODE_DEVLINK_TEST_TIMEOUT_MS (2 * MSEC_PER_SEC)
|
||||
|
||||
struct swnode_test_probe_order {
|
||||
/* Names in the order their drivers' .probe ran. */
|
||||
const char *probed[2];
|
||||
unsigned int count;
|
||||
wait_queue_head_t wq;
|
||||
};
|
||||
|
||||
static int swnode_test_record_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct swnode_test_probe_order *order = platform_get_drvdata(pdev);
|
||||
|
||||
if (order && order->count < ARRAY_SIZE(order->probed)) {
|
||||
order->probed[order->count++] = dev_name(&pdev->dev);
|
||||
wake_up_interruptible(&order->wq);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver swnode_test_supplier_driver = {
|
||||
.probe = swnode_test_record_probe,
|
||||
.driver = {
|
||||
.name = SWNODE_DEVLINK_TEST_SUPPLIER,
|
||||
},
|
||||
};
|
||||
|
||||
static struct platform_driver swnode_test_consumer_driver = {
|
||||
.probe = swnode_test_record_probe,
|
||||
.driver = {
|
||||
.name = SWNODE_DEVLINK_TEST_CONSUMER,
|
||||
},
|
||||
};
|
||||
|
||||
static void swnode_devlink_test_probe_order(struct kunit *test)
|
||||
{
|
||||
static const struct software_node supplier_swnode = {
|
||||
.name = "swnode-devlink-test-supplier",
|
||||
};
|
||||
|
||||
const struct property_entry consumer_props[] = {
|
||||
PROPERTY_ENTRY_REF("supplier-ref", &supplier_swnode),
|
||||
{ }
|
||||
};
|
||||
|
||||
struct platform_device *supplier, *consumer;
|
||||
struct swnode_test_probe_order *order;
|
||||
struct fwnode_handle *fwnode;
|
||||
int ret;
|
||||
|
||||
order = kunit_kzalloc(test, sizeof(*order), GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, order);
|
||||
init_waitqueue_head(&order->wq);
|
||||
|
||||
fwnode = kunit_software_node_register(test, &supplier_swnode);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode);
|
||||
|
||||
ret = kunit_platform_driver_register(test, &swnode_test_supplier_driver);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
ret = kunit_platform_driver_register(test, &swnode_test_consumer_driver);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
supplier = kunit_platform_device_alloc(test, SWNODE_DEVLINK_TEST_SUPPLIER,
|
||||
PLATFORM_DEVID_NONE);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, supplier);
|
||||
consumer = kunit_platform_device_alloc(test, SWNODE_DEVLINK_TEST_CONSUMER,
|
||||
PLATFORM_DEVID_NONE);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, consumer);
|
||||
|
||||
platform_set_drvdata(supplier, order);
|
||||
platform_set_drvdata(consumer, order);
|
||||
|
||||
ret = kunit_device_add_software_node(test, &supplier->dev, &supplier_swnode);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
ret = device_create_managed_software_node(&consumer->dev,
|
||||
consumer_props, NULL);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
ret = kunit_platform_device_add(test, consumer);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
ret = kunit_platform_device_add(test, supplier);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
ret = wait_event_interruptible_timeout(order->wq,
|
||||
order->count == 2,
|
||||
msecs_to_jiffies(SWNODE_DEVLINK_TEST_TIMEOUT_MS));
|
||||
KUNIT_ASSERT_GT(test, ret, 0);
|
||||
|
||||
KUNIT_EXPECT_STREQ(test, order->probed[0], SWNODE_DEVLINK_TEST_SUPPLIER);
|
||||
KUNIT_EXPECT_STREQ(test, order->probed[1], SWNODE_DEVLINK_TEST_CONSUMER);
|
||||
|
||||
/* Tear down the consumer (and its device link) before the supplier. */
|
||||
kunit_platform_device_unregister(test, consumer);
|
||||
}
|
||||
|
||||
static struct kunit_case swnode_test_cases[] = {
|
||||
KUNIT_CASE(swnode_devlink_test_single_ref),
|
||||
KUNIT_CASE(swnode_devlink_test_multiple_refs),
|
||||
KUNIT_CASE(swnode_devlink_test_unregistered_ref),
|
||||
KUNIT_CASE(swnode_devlink_test_remote_endpoint_excluded),
|
||||
KUNIT_CASE(swnode_devlink_test_ref_array),
|
||||
KUNIT_CASE(swnode_devlink_test_probe_order),
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct kunit_suite swnode_test_suite = {
|
||||
.name = "software-node-links",
|
||||
.test_cases = swnode_test_cases,
|
||||
};
|
||||
|
||||
kunit_test_suite(swnode_test_suite);
|
||||
|
||||
MODULE_DESCRIPTION("Test module for software node fw_devlink support");
|
||||
MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -64,7 +64,6 @@
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/irq.h>
|
||||
|
||||
static int sn7516x_used = 1, sn7516x;
|
||||
|
||||
+17
-2
@@ -653,7 +653,7 @@ config GPIO_ROCKCHIP
|
||||
config GPIO_RTD
|
||||
tristate "Realtek DHC GPIO support"
|
||||
depends on ARCH_REALTEK || COMPILE_TEST
|
||||
default y
|
||||
default ARCH_REALTEK
|
||||
select GPIOLIB_IRQCHIP
|
||||
help
|
||||
This option enables support for GPIOs found on Realtek DHC(Digital
|
||||
@@ -663,6 +663,20 @@ config GPIO_RTD
|
||||
Say yes here to support GPIO functionality and GPIO interrupt on
|
||||
Realtek DHC SoCs.
|
||||
|
||||
config GPIO_RTD1625
|
||||
tristate "Realtek DHC RTD1625 GPIO support"
|
||||
depends on ARCH_REALTEK || COMPILE_TEST
|
||||
default ARCH_REALTEK
|
||||
select GPIOLIB_IRQCHIP
|
||||
select GPIO_REGMAP
|
||||
select REGMAP_MMIO
|
||||
help
|
||||
This option enables support for the GPIO controller on Realtek
|
||||
DHC (Digital Home Center) RTD1625 SoC.
|
||||
|
||||
Say yes here to support both basic GPIO line functionality
|
||||
and GPIO interrupt handling capabilities for this platform.
|
||||
|
||||
config GPIO_SAMA5D2_PIOBU
|
||||
tristate "SAMA5D2 PIOBU GPIO support"
|
||||
depends on OF
|
||||
@@ -949,7 +963,8 @@ config GPIO_IDT3243X
|
||||
endmenu
|
||||
|
||||
menu "Port-mapped I/O GPIO drivers"
|
||||
depends on X86 && HAS_IOPORT # I/O space access
|
||||
depends on X86 || COMPILE_TEST
|
||||
depends on HAS_IOPORT # I/O space access
|
||||
|
||||
config GPIO_VX855
|
||||
tristate "VIA VX855/VX875 GPIO"
|
||||
|
||||
@@ -161,6 +161,7 @@ obj-$(CONFIG_GPIO_REALTEK_OTTO) += gpio-realtek-otto.o
|
||||
obj-$(CONFIG_GPIO_REG) += gpio-reg.o
|
||||
obj-$(CONFIG_GPIO_ROCKCHIP) += gpio-rockchip.o
|
||||
obj-$(CONFIG_GPIO_RTD) += gpio-rtd.o
|
||||
obj-$(CONFIG_GPIO_RTD1625) += gpio-rtd1625.o
|
||||
obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o
|
||||
obj-$(CONFIG_GPIO_SAMA5D2_PIOBU) += gpio-sama5d2-piobu.o
|
||||
obj-$(CONFIG_GPIO_SCH311X) += gpio-sch311x.o
|
||||
|
||||
@@ -36,9 +36,10 @@ MODULE_PARM_DESC(irq, "ACCES 104-IDI-48 interrupt line numbers");
|
||||
#define IDI48_IRQ_STATUS 0x7
|
||||
#define IDI48_IRQ_ENABLE IDI48_IRQ_STATUS
|
||||
|
||||
static int idi_48_reg_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
|
||||
unsigned int offset, unsigned int *reg,
|
||||
unsigned int *mask)
|
||||
static int idi_48_reg_mask_xlate(struct gpio_regmap *gpio,
|
||||
enum gpio_regmap_operation op,
|
||||
unsigned int base, unsigned int offset,
|
||||
unsigned int *reg, unsigned int *mask)
|
||||
{
|
||||
const unsigned int line = offset % 8;
|
||||
const unsigned int stride = offset / 8;
|
||||
|
||||
@@ -48,13 +48,11 @@ static void __iomem *amd_fch_gpio_addr(struct amd_fch_gpio_priv *priv,
|
||||
static int amd_fch_gpio_direction_input(struct gpio_chip *gc,
|
||||
unsigned int offset)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc);
|
||||
void __iomem *ptr = amd_fch_gpio_addr(priv, offset);
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
guard(spinlock_irqsave)(&priv->lock);
|
||||
writel_relaxed(readl_relaxed(ptr) & ~AMD_FCH_GPIO_FLAG_DIRECTION, ptr);
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -62,12 +60,11 @@ static int amd_fch_gpio_direction_input(struct gpio_chip *gc,
|
||||
static int amd_fch_gpio_direction_output(struct gpio_chip *gc,
|
||||
unsigned int gpio, int value)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc);
|
||||
void __iomem *ptr = amd_fch_gpio_addr(priv, gpio);
|
||||
u32 val;
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
guard(spinlock_irqsave)(&priv->lock);
|
||||
|
||||
val = readl_relaxed(ptr);
|
||||
if (value)
|
||||
@@ -77,33 +74,28 @@ static int amd_fch_gpio_direction_output(struct gpio_chip *gc,
|
||||
|
||||
writel_relaxed(val | AMD_FCH_GPIO_FLAG_DIRECTION, ptr);
|
||||
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amd_fch_gpio_get_direction(struct gpio_chip *gc, unsigned int gpio)
|
||||
{
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc);
|
||||
void __iomem *ptr = amd_fch_gpio_addr(priv, gpio);
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
guard(spinlock_irqsave)(&priv->lock);
|
||||
ret = (readl_relaxed(ptr) & AMD_FCH_GPIO_FLAG_DIRECTION);
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
|
||||
return ret ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
|
||||
}
|
||||
|
||||
static int amd_fch_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc);
|
||||
void __iomem *ptr = amd_fch_gpio_addr(priv, gpio);
|
||||
u32 mask;
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
guard(spinlock_irqsave)(&priv->lock);
|
||||
|
||||
mask = readl_relaxed(ptr);
|
||||
if (value)
|
||||
@@ -112,22 +104,18 @@ static int amd_fch_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value)
|
||||
mask &= ~AMD_FCH_GPIO_FLAG_WRITE;
|
||||
writel_relaxed(mask, ptr);
|
||||
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amd_fch_gpio_get(struct gpio_chip *gc,
|
||||
unsigned int offset)
|
||||
{
|
||||
unsigned long flags;
|
||||
u32 val;
|
||||
struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc);
|
||||
void __iomem *ptr = amd_fch_gpio_addr(priv, offset);
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
guard(spinlock_irqsave)(&priv->lock);
|
||||
val = readl_relaxed(ptr);
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
|
||||
return FIELD_GET(AMD_FCH_GPIO_FLAG_READ, val);
|
||||
}
|
||||
|
||||
@@ -85,9 +85,11 @@ static int bd72720gpi_get(struct bd72720_gpio *bdgpio, unsigned int reg_offset)
|
||||
static int bd72720gpo_get(struct bd72720_gpio *bdgpio,
|
||||
unsigned int offset)
|
||||
{
|
||||
const int regs[] = { BD72720_REG_GPIO1_CTRL, BD72720_REG_GPIO2_CTRL,
|
||||
BD72720_REG_GPIO3_CTRL, BD72720_REG_GPIO4_CTRL,
|
||||
BD72720_REG_GPIO5_CTRL, BD72720_REG_EPDEN_CTRL };
|
||||
static const int regs[] = {
|
||||
BD72720_REG_GPIO1_CTRL, BD72720_REG_GPIO2_CTRL,
|
||||
BD72720_REG_GPIO3_CTRL, BD72720_REG_GPIO4_CTRL,
|
||||
BD72720_REG_GPIO5_CTRL, BD72720_REG_EPDEN_CTRL
|
||||
};
|
||||
int ret, val;
|
||||
|
||||
ret = regmap_read(bdgpio->regmap, regs[offset], &val);
|
||||
@@ -111,9 +113,11 @@ static int bd72720gpo_set(struct gpio_chip *chip, unsigned int offset,
|
||||
int value)
|
||||
{
|
||||
struct bd72720_gpio *bdgpio = gpiochip_get_data(chip);
|
||||
const int regs[] = { BD72720_REG_GPIO1_CTRL, BD72720_REG_GPIO2_CTRL,
|
||||
BD72720_REG_GPIO3_CTRL, BD72720_REG_GPIO4_CTRL,
|
||||
BD72720_REG_GPIO5_CTRL, BD72720_REG_EPDEN_CTRL };
|
||||
static const int regs[] = {
|
||||
BD72720_REG_GPIO1_CTRL, BD72720_REG_GPIO2_CTRL,
|
||||
BD72720_REG_GPIO3_CTRL, BD72720_REG_GPIO4_CTRL,
|
||||
BD72720_REG_GPIO5_CTRL, BD72720_REG_EPDEN_CTRL
|
||||
};
|
||||
|
||||
if (BIT(offset) & bdgpio->gpio_is_input) {
|
||||
dev_dbg(bdgpio->dev, "pin %d not output.\n", offset);
|
||||
@@ -132,9 +136,11 @@ static int bd72720_gpio_set_config(struct gpio_chip *chip, unsigned int offset,
|
||||
unsigned long config)
|
||||
{
|
||||
struct bd72720_gpio *bdgpio = gpiochip_get_data(chip);
|
||||
const int regs[] = { BD72720_REG_GPIO1_CTRL, BD72720_REG_GPIO2_CTRL,
|
||||
BD72720_REG_GPIO3_CTRL, BD72720_REG_GPIO4_CTRL,
|
||||
BD72720_REG_GPIO5_CTRL, BD72720_REG_EPDEN_CTRL };
|
||||
static const int regs[] = {
|
||||
BD72720_REG_GPIO1_CTRL, BD72720_REG_GPIO2_CTRL,
|
||||
BD72720_REG_GPIO3_CTRL, BD72720_REG_GPIO4_CTRL,
|
||||
BD72720_REG_GPIO5_CTRL, BD72720_REG_EPDEN_CTRL
|
||||
};
|
||||
|
||||
/*
|
||||
* We can only set the output mode, which makes sense only when output
|
||||
|
||||
@@ -154,12 +154,18 @@ static int bt8xxgpio_probe(struct pci_dev *dev,
|
||||
const struct pci_device_id *pci_id)
|
||||
{
|
||||
struct bt8xxgpio *bg;
|
||||
void __iomem *mmio;
|
||||
int err;
|
||||
|
||||
mmio = devm_ioremap_resource(&dev->dev, pci_resource_n(dev, 0));
|
||||
if (IS_ERR(mmio))
|
||||
return PTR_ERR(mmio);
|
||||
|
||||
bg = devm_kzalloc(&dev->dev, sizeof(struct bt8xxgpio), GFP_KERNEL);
|
||||
if (!bg)
|
||||
return -ENOMEM;
|
||||
|
||||
bg->mmio = mmio;
|
||||
bg->pdev = dev;
|
||||
spin_lock_init(&bg->lock);
|
||||
|
||||
@@ -168,24 +174,9 @@ static int bt8xxgpio_probe(struct pci_dev *dev,
|
||||
dev_err(&dev->dev, "can't enable device.\n");
|
||||
return err;
|
||||
}
|
||||
if (!devm_request_mem_region(&dev->dev, pci_resource_start(dev, 0),
|
||||
pci_resource_len(dev, 0),
|
||||
"bt8xxgpio")) {
|
||||
dev_warn(&dev->dev, "can't request iomem (0x%llx).\n",
|
||||
(unsigned long long)pci_resource_start(dev, 0));
|
||||
err = -EBUSY;
|
||||
goto err_disable;
|
||||
}
|
||||
pci_set_master(dev);
|
||||
pci_set_drvdata(dev, bg);
|
||||
|
||||
bg->mmio = devm_ioremap(&dev->dev, pci_resource_start(dev, 0), 0x1000);
|
||||
if (!bg->mmio) {
|
||||
dev_err(&dev->dev, "ioremap() failed\n");
|
||||
err = -EIO;
|
||||
goto err_disable;
|
||||
}
|
||||
|
||||
/* Disable interrupts */
|
||||
bgwrite(0, BT848_INT_MASK);
|
||||
|
||||
|
||||
@@ -54,11 +54,11 @@ static int pin_control_gpio_direction_output(struct gpio_chip *chip,
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = pinctrl_gpio_direction_output(chip, offset);
|
||||
ret = pin_control_gpio_set(chip, offset, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return pin_control_gpio_set(chip, offset, val);
|
||||
return pinctrl_gpio_direction_output(chip, offset);
|
||||
}
|
||||
|
||||
static int pin_control_gpio_probe(struct platform_device *pdev)
|
||||
|
||||
@@ -285,9 +285,9 @@ static void gpio_irq_unmask(struct irq_data *d)
|
||||
|
||||
gpiochip_enable_irq(&chips->chip, hwirq);
|
||||
|
||||
status &= IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING;
|
||||
status &= IRQ_TYPE_EDGE_BOTH;
|
||||
if (!status)
|
||||
status = IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING;
|
||||
status = IRQ_TYPE_EDGE_BOTH;
|
||||
|
||||
if (status & IRQ_TYPE_EDGE_FALLING)
|
||||
writel_relaxed(mask, &g->set_falling);
|
||||
@@ -297,7 +297,7 @@ static void gpio_irq_unmask(struct irq_data *d)
|
||||
|
||||
static int gpio_irq_type(struct irq_data *d, unsigned trigger)
|
||||
{
|
||||
if (trigger & ~(IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING))
|
||||
if (trigger & ~IRQ_TYPE_EDGE_BOTH)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
@@ -400,7 +400,7 @@ static int gpio_irq_type_unbanked(struct irq_data *data, unsigned trigger)
|
||||
|
||||
mask = __gpio_mask(i);
|
||||
|
||||
if (trigger & ~(IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING))
|
||||
if (trigger & ~IRQ_TYPE_EDGE_BOTH)
|
||||
return -EINVAL;
|
||||
|
||||
writel_relaxed(mask, (trigger & IRQ_TYPE_EDGE_FALLING)
|
||||
|
||||
@@ -67,8 +67,8 @@ static int i8255_ppi_init(struct regmap *const map, const unsigned int base)
|
||||
return regmap_write(map, base + I8255_PORTC, 0x00);
|
||||
}
|
||||
|
||||
static int i8255_reg_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
|
||||
unsigned int offset, unsigned int *reg,
|
||||
static int i8255_reg_mask_xlate(struct gpio_regmap *gpio, enum gpio_regmap_operation op,
|
||||
unsigned int base, unsigned int offset, unsigned int *reg,
|
||||
unsigned int *mask)
|
||||
{
|
||||
const unsigned int ppi = offset / I8255_NGPIO;
|
||||
|
||||
@@ -66,9 +66,9 @@ static int idio_16_handle_mask_sync(const int index, const unsigned int mask_buf
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int idio_16_reg_mask_xlate(struct gpio_regmap *const gpio, const unsigned int base,
|
||||
const unsigned int offset, unsigned int *const reg,
|
||||
unsigned int *const mask)
|
||||
static int idio_16_reg_mask_xlate(struct gpio_regmap *const gpio, enum gpio_regmap_operation op,
|
||||
const unsigned int base, const unsigned int offset,
|
||||
unsigned int *const reg, unsigned int *const mask)
|
||||
{
|
||||
unsigned int stride;
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ static int max7360_set_gpos_count(struct device *dev, struct regmap *regmap)
|
||||
}
|
||||
|
||||
static int max7360_gpio_reg_mask_xlate(struct gpio_regmap *gpio,
|
||||
enum gpio_regmap_operation op,
|
||||
unsigned int base, unsigned int offset,
|
||||
unsigned int *reg, unsigned int *mask)
|
||||
{
|
||||
@@ -127,7 +128,7 @@ static int max7360_handle_mask_sync(const int index,
|
||||
const unsigned int mask_buf,
|
||||
void *const irq_drv_data)
|
||||
{
|
||||
struct regmap *regmap = irq_drv_data;
|
||||
struct regmap *regmap = gpio_regmap_get_drvdata(irq_drv_data);
|
||||
int ret;
|
||||
|
||||
for (unsigned int i = 0; i < MAX7360_MAX_GPIO; i++) {
|
||||
@@ -170,7 +171,6 @@ static int max7360_gpio_probe(struct platform_device *pdev)
|
||||
|
||||
/* 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;
|
||||
|
||||
@@ -181,7 +181,9 @@ static int max7360_gpio_probe(struct platform_device *pdev)
|
||||
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;
|
||||
|
||||
gpio_config.regmap_irq_chip = irq_chip;
|
||||
gpio_config.drvdata = regmap;
|
||||
|
||||
for (unsigned int i = 0; i < MAX7360_MAX_GPIO; i++) {
|
||||
ret = regmap_write_bits(regmap, MAX7360_REG_PWMCFG(i),
|
||||
|
||||
+21
-21
@@ -68,7 +68,7 @@ mt7621_gpio_gc_to_priv(struct gpio_chip *gc)
|
||||
}
|
||||
|
||||
static inline struct mtk_gc *
|
||||
to_mediatek_gpio(struct gpio_chip *chip)
|
||||
to_mt7621_gpio(struct gpio_chip *chip)
|
||||
{
|
||||
struct gpio_generic_chip *gen_gc = to_gpio_generic_chip(chip);
|
||||
|
||||
@@ -137,7 +137,7 @@ mt7621_gpio_hwirq_to_offset(irq_hw_number_t hwirq, struct mtk_gc *bank)
|
||||
}
|
||||
|
||||
static void
|
||||
mediatek_gpio_irq_unmask(struct irq_data *d)
|
||||
mt7621_gpio_irq_unmask(struct irq_data *d)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct mtk_gc *rg = gpiochip_get_data(gc);
|
||||
@@ -159,7 +159,7 @@ mediatek_gpio_irq_unmask(struct irq_data *d)
|
||||
}
|
||||
|
||||
static void
|
||||
mediatek_gpio_irq_mask(struct irq_data *d)
|
||||
mt7621_gpio_irq_mask(struct irq_data *d)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct mtk_gc *rg = gpiochip_get_data(gc);
|
||||
@@ -181,7 +181,7 @@ mediatek_gpio_irq_mask(struct irq_data *d)
|
||||
}
|
||||
|
||||
static int
|
||||
mediatek_gpio_irq_type(struct irq_data *d, unsigned int type)
|
||||
mt7621_gpio_irq_type(struct irq_data *d, unsigned int type)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct mtk_gc *rg = gpiochip_get_data(gc);
|
||||
@@ -194,7 +194,7 @@ mediatek_gpio_irq_type(struct irq_data *d, unsigned int type)
|
||||
rg->hlevel | rg->llevel) & mask)
|
||||
return 0;
|
||||
|
||||
type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING;
|
||||
type = IRQ_TYPE_EDGE_BOTH;
|
||||
}
|
||||
|
||||
rg->rising &= ~mask;
|
||||
@@ -245,11 +245,11 @@ mt7621_gpio_irq_relres(struct irq_data *d)
|
||||
}
|
||||
|
||||
static int
|
||||
mediatek_gpio_xlate(struct gpio_chip *chip,
|
||||
mt7621_gpio_xlate(struct gpio_chip *chip,
|
||||
const struct of_phandle_args *spec, u32 *flags)
|
||||
{
|
||||
int gpio = spec->args[0];
|
||||
struct mtk_gc *rg = to_mediatek_gpio(chip);
|
||||
struct mtk_gc *rg = to_mt7621_gpio(chip);
|
||||
|
||||
if (rg->bank != gpio / MTK_BANK_WIDTH)
|
||||
return -EINVAL;
|
||||
@@ -264,10 +264,10 @@ static const struct irq_chip mt7621_irq_chip = {
|
||||
.name = "mt7621-gpio",
|
||||
.irq_request_resources = mt7621_gpio_irq_reqres,
|
||||
.irq_release_resources = mt7621_gpio_irq_relres,
|
||||
.irq_mask_ack = mediatek_gpio_irq_mask,
|
||||
.irq_mask = mediatek_gpio_irq_mask,
|
||||
.irq_unmask = mediatek_gpio_irq_unmask,
|
||||
.irq_set_type = mediatek_gpio_irq_type,
|
||||
.irq_mask_ack = mt7621_gpio_irq_mask,
|
||||
.irq_mask = mt7621_gpio_irq_mask,
|
||||
.irq_unmask = mt7621_gpio_irq_unmask,
|
||||
.irq_set_type = mt7621_gpio_irq_type,
|
||||
.flags = IRQCHIP_IMMUTABLE,
|
||||
};
|
||||
|
||||
@@ -380,7 +380,7 @@ mt7621_gpio_to_irq(struct gpio_chip *gc, unsigned int offset)
|
||||
}
|
||||
|
||||
static int
|
||||
mediatek_gpio_bank_probe(struct device *dev, int bank)
|
||||
mt7621_gpio_bank_probe(struct device *dev, int bank)
|
||||
{
|
||||
struct gpio_generic_chip_config config;
|
||||
struct mtk *mtk = dev_get_drvdata(dev);
|
||||
@@ -416,7 +416,7 @@ mediatek_gpio_bank_probe(struct device *dev, int bank)
|
||||
}
|
||||
|
||||
rg->chip.gc.of_gpio_n_cells = 2;
|
||||
rg->chip.gc.of_xlate = mediatek_gpio_xlate;
|
||||
rg->chip.gc.of_xlate = mt7621_gpio_xlate;
|
||||
rg->chip.gc.ngpio = MTK_BANK_WIDTH;
|
||||
rg->chip.gc.label = devm_kasprintf(dev, GFP_KERNEL, "%s-bank%d",
|
||||
dev_name(dev), bank);
|
||||
@@ -443,7 +443,7 @@ mediatek_gpio_bank_probe(struct device *dev, int bank)
|
||||
}
|
||||
|
||||
static int
|
||||
mediatek_gpio_probe(struct platform_device *pdev)
|
||||
mt7621_gpio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct mtk *mtk;
|
||||
@@ -477,7 +477,7 @@ mediatek_gpio_probe(struct platform_device *pdev)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < MTK_BANK_CNT; i++) {
|
||||
ret = mediatek_gpio_bank_probe(dev, i);
|
||||
ret = mt7621_gpio_bank_probe(dev, i);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@@ -485,18 +485,18 @@ mediatek_gpio_probe(struct platform_device *pdev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id mediatek_gpio_match[] = {
|
||||
static const struct of_device_id mt7621_gpio_match[] = {
|
||||
{ .compatible = "mediatek,mt7621-gpio" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mediatek_gpio_match);
|
||||
MODULE_DEVICE_TABLE(of, mt7621_gpio_match);
|
||||
|
||||
static struct platform_driver mediatek_gpio_driver = {
|
||||
.probe = mediatek_gpio_probe,
|
||||
static struct platform_driver mt7621_gpio_driver = {
|
||||
.probe = mt7621_gpio_probe,
|
||||
.driver = {
|
||||
.name = "mt7621_gpio",
|
||||
.of_match_table = mediatek_gpio_match,
|
||||
.of_match_table = mt7621_gpio_match,
|
||||
},
|
||||
};
|
||||
|
||||
builtin_platform_driver(mediatek_gpio_driver);
|
||||
builtin_platform_driver(mt7621_gpio_driver);
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqchip/chained_irq.h>
|
||||
@@ -114,7 +115,7 @@ struct mvebu_gpio_chip {
|
||||
struct regmap *regs;
|
||||
u32 offset;
|
||||
struct regmap *percpu_regs;
|
||||
int irqbase;
|
||||
int bank_irq[4];
|
||||
struct irq_domain *domain;
|
||||
int soc_variant;
|
||||
|
||||
@@ -603,6 +604,34 @@ static const struct regmap_config mvebu_gpio_regmap_config = {
|
||||
.val_bits = 32,
|
||||
};
|
||||
|
||||
/*
|
||||
* Forward wake-up configuration to the parent bank IRQ.
|
||||
* @d: interrupt data
|
||||
* @enable: enable as wake-up if non-zero
|
||||
*
|
||||
* Return: 0 on success, or a negative error code.
|
||||
*/
|
||||
static int mvebu_gpio_set_wake_irq(struct irq_data *d, unsigned int enable)
|
||||
{
|
||||
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
|
||||
struct mvebu_gpio_chip *mvchip = gc->private;
|
||||
int bank;
|
||||
int irq;
|
||||
|
||||
bank = d->hwirq / 8;
|
||||
if (bank >= ARRAY_SIZE(mvchip->bank_irq))
|
||||
return -EINVAL;
|
||||
|
||||
irq = mvchip->bank_irq[bank];
|
||||
if (irq <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (enable)
|
||||
return enable_irq_wake(irq);
|
||||
|
||||
return disable_irq_wake(irq);
|
||||
}
|
||||
|
||||
/*
|
||||
* Functions implementing the pwm_chip methods
|
||||
*/
|
||||
@@ -1253,7 +1282,7 @@ static int mvebu_gpio_probe(struct platform_device *pdev)
|
||||
|
||||
err = irq_alloc_domain_generic_chips(
|
||||
mvchip->domain, ngpios, 2, np->name, handle_level_irq,
|
||||
IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_LEVEL, 0, 0);
|
||||
IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_LEVEL, 0, IRQ_GC_INIT_NESTED_LOCK);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "couldn't allocate irq chips %s (DT).\n",
|
||||
mvchip->chip.label);
|
||||
@@ -1267,18 +1296,22 @@ static int mvebu_gpio_probe(struct platform_device *pdev)
|
||||
gc = irq_get_domain_generic_chip(mvchip->domain, 0);
|
||||
gc->private = mvchip;
|
||||
ct = &gc->chip_types[0];
|
||||
ct->type = IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW;
|
||||
ct->type = IRQ_TYPE_LEVEL_MASK;
|
||||
ct->chip.irq_mask = mvebu_gpio_level_irq_mask;
|
||||
ct->chip.irq_unmask = mvebu_gpio_level_irq_unmask;
|
||||
ct->chip.irq_set_type = mvebu_gpio_irq_set_type;
|
||||
ct->chip.irq_set_wake = mvebu_gpio_set_wake_irq;
|
||||
ct->chip.flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_MASK_ON_SUSPEND;
|
||||
ct->chip.name = mvchip->chip.label;
|
||||
|
||||
ct = &gc->chip_types[1];
|
||||
ct->type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING;
|
||||
ct->type = IRQ_TYPE_EDGE_BOTH;
|
||||
ct->chip.irq_ack = mvebu_gpio_irq_ack;
|
||||
ct->chip.irq_mask = mvebu_gpio_edge_irq_mask;
|
||||
ct->chip.irq_unmask = mvebu_gpio_edge_irq_unmask;
|
||||
ct->chip.irq_set_type = mvebu_gpio_irq_set_type;
|
||||
ct->chip.irq_set_wake = mvebu_gpio_set_wake_irq;
|
||||
ct->chip.flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_MASK_ON_SUSPEND;
|
||||
ct->handler = handle_edge_irq;
|
||||
ct->chip.name = mvchip->chip.label;
|
||||
|
||||
@@ -1287,13 +1320,14 @@ static int mvebu_gpio_probe(struct platform_device *pdev)
|
||||
* interrupt handlers, with each handler dealing with 8 GPIO
|
||||
* pins.
|
||||
*/
|
||||
for (i = 0; i < 4; i++) {
|
||||
for (i = 0; i < ARRAY_SIZE(mvchip->bank_irq); i++) {
|
||||
int irq = platform_get_irq_optional(pdev, i);
|
||||
|
||||
if (irq < 0)
|
||||
continue;
|
||||
irq_set_chained_handler_and_data(irq, mvebu_gpio_irq_handler,
|
||||
mvchip);
|
||||
mvchip->bank_irq[i] = irq;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -198,7 +198,7 @@ static int mxs_gpio_init_gc(struct mxs_gpio_port *port, int irq_base)
|
||||
gc->private = port;
|
||||
|
||||
ct = &gc->chip_types[0];
|
||||
ct->type = IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW;
|
||||
ct->type = IRQ_TYPE_LEVEL_MASK;
|
||||
ct->chip.irq_ack = irq_gc_ack_set_bit;
|
||||
ct->chip.irq_mask = irq_gc_mask_disable_reg;
|
||||
ct->chip.irq_unmask = irq_gc_unmask_enable_reg;
|
||||
@@ -210,7 +210,7 @@ static int mxs_gpio_init_gc(struct mxs_gpio_port *port, int irq_base)
|
||||
ct->regs.disable = PINCTRL_PIN2IRQ(port) + MXS_CLR;
|
||||
|
||||
ct = &gc->chip_types[1];
|
||||
ct->type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING;
|
||||
ct->type = IRQ_TYPE_EDGE_BOTH;
|
||||
ct->chip.irq_ack = irq_gc_ack_set_bit;
|
||||
ct->chip.irq_mask = irq_gc_mask_disable_reg;
|
||||
ct->chip.irq_unmask = irq_gc_unmask_enable_reg;
|
||||
|
||||
+32
-32
@@ -519,22 +519,23 @@ struct nmk_gpio_chip *nmk_gpio_populate_chip(struct fwnode_handle *fwnode,
|
||||
|
||||
gpio_dev = bus_find_device_by_fwnode(&platform_bus_type, fwnode);
|
||||
if (!gpio_dev) {
|
||||
dev_err(dev, "populate \"%pfwP\": device not found\n", fwnode);
|
||||
return ERR_PTR(-ENODEV);
|
||||
ret = -ENODEV;
|
||||
dev_err_probe(dev, ret, "populate \"%pfwP\": device not found\n", fwnode);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
gpio_pdev = to_platform_device(gpio_dev);
|
||||
|
||||
if (device_property_read_u32(gpio_dev, "gpio-bank", &id)) {
|
||||
dev_err(dev, "populate: gpio-bank property not found\n");
|
||||
platform_device_put(gpio_pdev);
|
||||
return ERR_PTR(-EINVAL);
|
||||
ret = -EINVAL;
|
||||
dev_err_probe(dev, ret, "populate: gpio-bank property not found\n");
|
||||
goto err_put_pdev;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PINCTRL_NOMADIK
|
||||
if (id >= ARRAY_SIZE(nmk_gpio_chips)) {
|
||||
dev_err(dev, "populate: invalid id: %u\n", id);
|
||||
platform_device_put(gpio_pdev);
|
||||
return ERR_PTR(-EINVAL);
|
||||
ret = -EINVAL;
|
||||
dev_err_probe(dev, ret, "populate: invalid id: %u\n", id);
|
||||
goto err_put_pdev;
|
||||
}
|
||||
/* Already populated? */
|
||||
nmk_chip = nmk_gpio_chips[id];
|
||||
@@ -546,8 +547,8 @@ struct nmk_gpio_chip *nmk_gpio_populate_chip(struct fwnode_handle *fwnode,
|
||||
|
||||
nmk_chip = devm_kzalloc(dev, sizeof(*nmk_chip), GFP_KERNEL);
|
||||
if (!nmk_chip) {
|
||||
platform_device_put(gpio_pdev);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
ret = -ENOMEM;
|
||||
goto err_put_pdev;
|
||||
}
|
||||
|
||||
if (device_property_read_u32(gpio_dev, "ngpios", &ngpio)) {
|
||||
@@ -568,16 +569,17 @@ struct nmk_gpio_chip *nmk_gpio_populate_chip(struct fwnode_handle *fwnode,
|
||||
res = platform_get_resource(gpio_pdev, IORESOURCE_MEM, 0);
|
||||
base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(base)) {
|
||||
platform_device_put(gpio_pdev);
|
||||
return ERR_CAST(base);
|
||||
ret = PTR_ERR(base);
|
||||
goto err_put_pdev;
|
||||
}
|
||||
nmk_chip->addr = base;
|
||||
|
||||
/* NOTE: do not use devm_ here! */
|
||||
clk = clk_get_optional(gpio_dev, NULL);
|
||||
if (IS_ERR(clk)) {
|
||||
platform_device_put(gpio_pdev);
|
||||
return ERR_CAST(clk);
|
||||
ret = PTR_ERR(clk);
|
||||
dev_err_probe(dev, ret, "failed getting clock\n");
|
||||
goto err_put_pdev;
|
||||
}
|
||||
clk_prepare(clk);
|
||||
nmk_chip->clk = clk;
|
||||
@@ -585,12 +587,9 @@ struct nmk_gpio_chip *nmk_gpio_populate_chip(struct fwnode_handle *fwnode,
|
||||
/* NOTE: do not use devm_ here! */
|
||||
reset = reset_control_get_optional_shared(gpio_dev, NULL);
|
||||
if (IS_ERR(reset)) {
|
||||
clk_unprepare(clk);
|
||||
clk_put(clk);
|
||||
platform_device_put(gpio_pdev);
|
||||
dev_err(dev, "failed getting reset control: %pe\n",
|
||||
reset);
|
||||
return ERR_CAST(reset);
|
||||
ret = PTR_ERR(reset);
|
||||
dev_err_probe(dev, ret, "failed getting reset control\n");
|
||||
goto err_unprepare_clk;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -600,18 +599,23 @@ struct nmk_gpio_chip *nmk_gpio_populate_chip(struct fwnode_handle *fwnode,
|
||||
*/
|
||||
ret = reset_control_deassert(reset);
|
||||
if (ret) {
|
||||
reset_control_put(reset);
|
||||
clk_unprepare(clk);
|
||||
clk_put(clk);
|
||||
platform_device_put(gpio_pdev);
|
||||
dev_err(dev, "failed reset deassert: %d\n", ret);
|
||||
return ERR_PTR(ret);
|
||||
dev_err_probe(dev, ret, "failed reset deassert\n");
|
||||
goto err_put_reset;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PINCTRL_NOMADIK
|
||||
nmk_gpio_chips[id] = nmk_chip;
|
||||
#endif
|
||||
return nmk_chip;
|
||||
|
||||
err_put_reset:
|
||||
reset_control_put(reset);
|
||||
err_unprepare_clk:
|
||||
clk_unprepare(clk);
|
||||
clk_put(clk);
|
||||
err_put_pdev:
|
||||
platform_device_put(gpio_pdev);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static void nmk_gpio_irq_print_chip(struct irq_data *d, struct seq_file *p)
|
||||
@@ -647,10 +651,8 @@ static int nmk_gpio_probe(struct platform_device *pdev)
|
||||
int ret;
|
||||
|
||||
nmk_chip = nmk_gpio_populate_chip(dev_fwnode(dev), pdev);
|
||||
if (IS_ERR(nmk_chip)) {
|
||||
dev_err(dev, "could not populate nmk chip struct\n");
|
||||
if (IS_ERR(nmk_chip))
|
||||
return PTR_ERR(nmk_chip);
|
||||
}
|
||||
|
||||
supports_sleepmode =
|
||||
device_property_read_bool(dev, "st,supports-sleepmode");
|
||||
@@ -693,7 +695,7 @@ static int nmk_gpio_probe(struct platform_device *pdev)
|
||||
ret = devm_request_irq(dev, irq, nmk_gpio_irq_handler, IRQF_SHARED,
|
||||
dev_name(dev), nmk_chip);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed requesting IRQ\n");
|
||||
dev_err_probe(dev, ret, "failed requesting IRQ\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -709,8 +711,6 @@ static int nmk_gpio_probe(struct platform_device *pdev)
|
||||
|
||||
platform_set_drvdata(pdev, nmk_chip);
|
||||
|
||||
dev_info(dev, "chip registered\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -432,8 +432,7 @@ static int omap_gpio_irq_type(struct irq_data *d, unsigned type)
|
||||
if (type & ~IRQ_TYPE_SENSE_MASK)
|
||||
return -EINVAL;
|
||||
|
||||
if (!bank->regs->leveldetect0 &&
|
||||
(type & (IRQ_TYPE_LEVEL_LOW|IRQ_TYPE_LEVEL_HIGH)))
|
||||
if (!bank->regs->leveldetect0 && (type & IRQ_TYPE_LEVEL_MASK))
|
||||
return -EINVAL;
|
||||
|
||||
raw_spin_lock_irqsave(&bank->lock, flags);
|
||||
@@ -450,9 +449,9 @@ static int omap_gpio_irq_type(struct irq_data *d, unsigned type)
|
||||
}
|
||||
raw_spin_unlock_irqrestore(&bank->lock, flags);
|
||||
|
||||
if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH))
|
||||
if (type & IRQ_TYPE_LEVEL_MASK)
|
||||
irq_set_handler_locked(d, handle_level_irq);
|
||||
else if (type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING))
|
||||
else if (type & IRQ_TYPE_EDGE_BOTH)
|
||||
/*
|
||||
* Edge IRQs are already cleared/acked in irq_handler and
|
||||
* not need to be masked, as result handle_edge_irq()
|
||||
@@ -702,7 +701,7 @@ static void omap_gpio_unmask_irq(struct irq_data *d)
|
||||
* after enabing the interrupt to clear the wakeup status.
|
||||
*/
|
||||
if (bank->regs->leveldetect0 && bank->regs->wkup_en &&
|
||||
trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW))
|
||||
trigger & IRQ_TYPE_LEVEL_MASK)
|
||||
omap_clear_gpio_irqstatus(bank, offset);
|
||||
|
||||
if (trigger)
|
||||
|
||||
@@ -133,7 +133,7 @@ static int pca9570_probe(struct i2c_client *client)
|
||||
gpio->chip.get = pca9570_get;
|
||||
gpio->chip.set = pca9570_set;
|
||||
gpio->chip.base = -1;
|
||||
gpio->chip_data = device_get_match_data(&client->dev);
|
||||
gpio->chip_data = i2c_get_match_data(client);
|
||||
gpio->chip.ngpio = gpio->chip_data->ngpio;
|
||||
gpio->chip.can_sleep = true;
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
#include <asm/byteorder.h>
|
||||
|
||||
static const struct i2c_device_id pcf857x_id[] = {
|
||||
{ .name = "pcf8574", .driver_data = 8 },
|
||||
{ .name = "pcf8574a", .driver_data = 8 },
|
||||
@@ -96,22 +98,23 @@ static int i2c_read_le8(struct i2c_client *client)
|
||||
|
||||
static int i2c_write_le16(struct i2c_client *client, unsigned int word)
|
||||
{
|
||||
u8 buf[2] = { word & 0xff, word >> 8, };
|
||||
__le16 buf = cpu_to_le16(word);
|
||||
int status;
|
||||
|
||||
status = i2c_master_send(client, buf, 2);
|
||||
status = i2c_master_send(client, (char *)&buf, sizeof(buf));
|
||||
return (status < 0) ? status : 0;
|
||||
}
|
||||
|
||||
static int i2c_read_le16(struct i2c_client *client)
|
||||
{
|
||||
u8 buf[2];
|
||||
__le16 buf;
|
||||
int status;
|
||||
|
||||
status = i2c_master_recv(client, buf, 2);
|
||||
status = i2c_master_recv(client, (char *)&buf, sizeof(buf));
|
||||
if (status < 0)
|
||||
return status;
|
||||
return (buf[1] << 8) | buf[0];
|
||||
|
||||
return le16_to_cpu(buf);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
@@ -225,9 +225,9 @@ exit_unlock:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int idio_24_reg_mask_xlate(struct gpio_regmap *const gpio, const unsigned int base,
|
||||
const unsigned int offset, unsigned int *const reg,
|
||||
unsigned int *const mask)
|
||||
static int idio_24_reg_mask_xlate(struct gpio_regmap *const gpio, enum gpio_regmap_operation op,
|
||||
const unsigned int base, const unsigned int offset,
|
||||
unsigned int *const reg, unsigned int *const mask)
|
||||
{
|
||||
const unsigned int out_stride = offset / IDIO_24_NGPIO_PER_REG;
|
||||
const unsigned int in_stride = (offset - 24) / IDIO_24_NGPIO_PER_REG;
|
||||
|
||||
@@ -132,8 +132,7 @@ static int pl061_irq_type(struct irq_data *d, unsigned trigger)
|
||||
if (offset < 0 || offset >= PL061_GPIO_NR)
|
||||
return -EINVAL;
|
||||
|
||||
if ((trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) &&
|
||||
(trigger & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING)))
|
||||
if ((trigger & IRQ_TYPE_LEVEL_MASK) && (trigger & IRQ_TYPE_EDGE_BOTH))
|
||||
{
|
||||
dev_err(gc->parent,
|
||||
"trying to configure line %d for both level and edge "
|
||||
@@ -149,7 +148,7 @@ static int pl061_irq_type(struct irq_data *d, unsigned trigger)
|
||||
gpiois = readb(pl061->base + GPIOIS);
|
||||
gpioibe = readb(pl061->base + GPIOIBE);
|
||||
|
||||
if (trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) {
|
||||
if (trigger & IRQ_TYPE_LEVEL_MASK) {
|
||||
bool polarity = trigger & IRQ_TYPE_LEVEL_HIGH;
|
||||
|
||||
/* Disable edge detection */
|
||||
|
||||
@@ -406,7 +406,7 @@ static int pxa_gpio_irq_type(struct irq_data *d, unsigned int type)
|
||||
if (__gpio_is_occupied(pchip, gpio))
|
||||
return 0;
|
||||
|
||||
type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING;
|
||||
type = IRQ_TYPE_EDGE_BOTH;
|
||||
}
|
||||
|
||||
gpdr = readl_relaxed(c->regbase + GPDR_OFFSET);
|
||||
|
||||
@@ -59,7 +59,6 @@ struct gpio_rcar_priv {
|
||||
#define MSKCLR 0x1c /* Interrupt Mask Clear Register */
|
||||
#define POSNEG 0x20 /* Positive/Negative Logic Select Register */
|
||||
#define EDGLEVEL 0x24 /* Edge/level Select Register */
|
||||
#define FILONOFF 0x28 /* Chattering Prevention On/Off Register */
|
||||
#define OUTDTSEL 0x40 /* Output Data Select Register */
|
||||
#define BOTHEDGE 0x4c /* One Edge/Both Edge Select Register */
|
||||
#define INEN 0x50 /* General Input Enable Register */
|
||||
|
||||
@@ -169,9 +169,9 @@ static int rda_gpio_irq_set_type(struct irq_data *data, unsigned int flow_type)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (flow_type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH))
|
||||
if (flow_type & IRQ_TYPE_LEVEL_MASK)
|
||||
irq_set_handler_locked(data, handle_level_irq);
|
||||
else if (flow_type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING))
|
||||
else if (flow_type & IRQ_TYPE_EDGE_BOTH)
|
||||
irq_set_handler_locked(data, handle_edge_irq);
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/compiler_attributes.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
@@ -39,9 +40,19 @@ struct gpio_regmap {
|
||||
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);
|
||||
int (*reg_mask_xlate)(struct gpio_regmap *gpio,
|
||||
enum gpio_regmap_operation op,
|
||||
unsigned int base, unsigned int offset,
|
||||
unsigned int *reg, unsigned int *mask);
|
||||
|
||||
int (*value_xlate)(struct gpio_regmap *gpio,
|
||||
enum gpio_regmap_operation op,
|
||||
unsigned int base, unsigned int offset,
|
||||
unsigned int reg, unsigned int *mask,
|
||||
unsigned int *val);
|
||||
|
||||
int (*set_config)(struct gpio_regmap *gpio, struct gpio_chip *chip,
|
||||
unsigned int offset, unsigned long config);
|
||||
|
||||
void *driver_data;
|
||||
};
|
||||
@@ -55,6 +66,7 @@ static unsigned int gpio_regmap_addr(unsigned int addr)
|
||||
}
|
||||
|
||||
static int gpio_regmap_simple_xlate(struct gpio_regmap *gpio,
|
||||
enum gpio_regmap_operation __maybe_unused op,
|
||||
unsigned int base, unsigned int offset,
|
||||
unsigned int *reg, unsigned int *mask)
|
||||
{
|
||||
@@ -79,7 +91,7 @@ static int gpio_regmap_get(struct gpio_chip *chip, unsigned int offset)
|
||||
else
|
||||
base = gpio_regmap_addr(gpio->reg_set_base);
|
||||
|
||||
ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask);
|
||||
ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_GET_OP, base, offset, ®, &mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -102,7 +114,7 @@ static int gpio_regmap_set(struct gpio_chip *chip, unsigned int offset,
|
||||
unsigned int reg, mask, mask_val;
|
||||
int ret;
|
||||
|
||||
ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask);
|
||||
ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_SET_OP, base, offset, ®, &mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -111,6 +123,13 @@ static int gpio_regmap_set(struct gpio_chip *chip, unsigned int offset,
|
||||
else
|
||||
mask_val = 0;
|
||||
|
||||
if (gpio->value_xlate) {
|
||||
ret = gpio->value_xlate(gpio, GPIO_REGMAP_SET_OP, base, offset,
|
||||
reg, &mask, &mask_val);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ignore input values which shadow the old output value */
|
||||
if (gpio->reg_dat_base == gpio->reg_set_base)
|
||||
ret = regmap_write_bits(gpio->regmap, reg, mask, mask_val);
|
||||
@@ -124,7 +143,7 @@ static int gpio_regmap_set_with_clear(struct gpio_chip *chip,
|
||||
unsigned int offset, int val)
|
||||
{
|
||||
struct gpio_regmap *gpio = gpiochip_get_data(chip);
|
||||
unsigned int base, reg, mask;
|
||||
unsigned int base, reg, mask, value = 0;
|
||||
int ret;
|
||||
|
||||
if (val)
|
||||
@@ -132,10 +151,17 @@ static int gpio_regmap_set_with_clear(struct gpio_chip *chip,
|
||||
else
|
||||
base = gpio_regmap_addr(gpio->reg_clr_base);
|
||||
|
||||
ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask);
|
||||
ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_SET_OP, base, offset, ®, &mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (gpio->value_xlate) {
|
||||
ret = gpio->value_xlate(gpio, GPIO_REGMAP_SET_OP, base, offset,
|
||||
reg, &mask, &value);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return regmap_write(gpio->regmap, reg, mask);
|
||||
}
|
||||
|
||||
@@ -182,7 +208,7 @@ static int gpio_regmap_get_direction(struct gpio_chip *chip,
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask);
|
||||
ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_GET_DIR_OP, base, offset, ®, &mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -236,7 +262,7 @@ static int gpio_regmap_set_direction(struct gpio_chip *chip,
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask);
|
||||
ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_SET_DIR_OP, base, offset, ®, &mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -245,6 +271,13 @@ static int gpio_regmap_set_direction(struct gpio_chip *chip,
|
||||
else
|
||||
val = output ? mask : 0;
|
||||
|
||||
if (gpio->value_xlate) {
|
||||
ret = gpio->value_xlate(gpio, GPIO_REGMAP_SET_DIR_OP, base, offset,
|
||||
reg, &mask, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return regmap_update_bits(gpio->regmap, reg, mask, val);
|
||||
}
|
||||
|
||||
@@ -276,12 +309,55 @@ static int gpio_regmap_direction_output(struct gpio_chip *chip,
|
||||
return gpio_regmap_set_direction(chip, offset, true);
|
||||
}
|
||||
|
||||
static int gpio_regmap_set_config(struct gpio_chip *chip,
|
||||
unsigned int offset,
|
||||
unsigned long cfg)
|
||||
{
|
||||
struct gpio_regmap *gpio = gpiochip_get_data(chip);
|
||||
|
||||
return gpio->set_config(gpio, chip, offset, cfg);
|
||||
}
|
||||
|
||||
int gpio_regmap_reqres_irq(struct gpio_regmap *gpio, unsigned int offset)
|
||||
{
|
||||
return gpiochip_reqres_irq(&gpio->gpio_chip, offset);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpio_regmap_reqres_irq);
|
||||
|
||||
void gpio_regmap_relres_irq(struct gpio_regmap *gpio, unsigned int offset)
|
||||
{
|
||||
gpiochip_relres_irq(&gpio->gpio_chip, offset);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpio_regmap_relres_irq);
|
||||
|
||||
static int __maybe_unused gpio_regmap_irq_reqres(void *irq_drv_data, irq_hw_number_t hwirq)
|
||||
{
|
||||
return gpio_regmap_reqres_irq(irq_drv_data, hwirq);
|
||||
}
|
||||
|
||||
static void __maybe_unused gpio_regmap_irq_relres(void *irq_drv_data, irq_hw_number_t hwirq)
|
||||
{
|
||||
gpio_regmap_relres_irq(irq_drv_data, hwirq);
|
||||
}
|
||||
|
||||
void *gpio_regmap_get_drvdata(struct gpio_regmap *gpio)
|
||||
{
|
||||
return gpio->driver_data;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpio_regmap_get_drvdata);
|
||||
|
||||
void gpio_regmap_enable_irq(struct gpio_regmap *gpio, irq_hw_number_t hwirq)
|
||||
{
|
||||
gpiochip_enable_irq(&gpio->gpio_chip, hwirq);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpio_regmap_enable_irq);
|
||||
|
||||
void gpio_regmap_disable_irq(struct gpio_regmap *gpio, irq_hw_number_t hwirq)
|
||||
{
|
||||
gpiochip_disable_irq(&gpio->gpio_chip, hwirq);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpio_regmap_disable_irq);
|
||||
|
||||
/**
|
||||
* gpio_regmap_register() - Register a generic regmap GPIO controller
|
||||
* @config: configuration for gpio_regmap
|
||||
@@ -390,6 +466,13 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config
|
||||
if (!gpio->reg_mask_xlate)
|
||||
gpio->reg_mask_xlate = gpio_regmap_simple_xlate;
|
||||
|
||||
gpio->value_xlate = config->value_xlate;
|
||||
|
||||
if (config->set_config) {
|
||||
gpio->set_config = config->set_config;
|
||||
chip->set_config = gpio_regmap_set_config;
|
||||
}
|
||||
|
||||
ret = gpiochip_add_data(chip, gpio);
|
||||
if (ret < 0)
|
||||
goto err_free_bitmap_output;
|
||||
@@ -397,6 +480,9 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config
|
||||
#ifdef CONFIG_REGMAP_IRQ
|
||||
if (config->regmap_irq_chip) {
|
||||
gpio->regmap_irq_line = config->regmap_irq_line;
|
||||
config->regmap_irq_chip->irq_reqres = gpio_regmap_irq_reqres;
|
||||
config->regmap_irq_chip->irq_relres = gpio_regmap_irq_relres;
|
||||
config->regmap_irq_chip->irq_drv_data = gpio;
|
||||
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);
|
||||
|
||||
@@ -647,21 +647,17 @@ static void rockchip_clk_put(void *data)
|
||||
|
||||
static int rockchip_get_bank_data(struct rockchip_pin_bank *bank)
|
||||
{
|
||||
struct resource res;
|
||||
struct platform_device *pdev = to_platform_device(bank->dev);
|
||||
int id = 0, ret;
|
||||
|
||||
if (of_address_to_resource(bank->of_node, 0, &res)) {
|
||||
dev_err(bank->dev, "cannot find IO resource for bank\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
bank->reg_base = devm_ioremap_resource(bank->dev, &res);
|
||||
bank->reg_base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(bank->reg_base))
|
||||
return PTR_ERR(bank->reg_base);
|
||||
|
||||
bank->irq = irq_of_parse_and_map(bank->of_node, 0);
|
||||
if (!bank->irq)
|
||||
return -EINVAL;
|
||||
ret = platform_get_irq(pdev, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
bank->irq = ret;
|
||||
|
||||
bank->clk = devm_clk_get_enabled(bank->dev, NULL);
|
||||
if (IS_ERR(bank->clk))
|
||||
|
||||
@@ -0,0 +1,677 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Realtek DHC RTD1625 gpio driver
|
||||
*
|
||||
* Copyright (c) 2023-2026 Realtek Semiconductor Corp.
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/gpio/regmap.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irqchip.h>
|
||||
#include <linux/irqchip/chained_irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define RTD1625_GPIO_DIR BIT(0)
|
||||
#define RTD1625_GPIO_OUT BIT(2)
|
||||
#define RTD1625_GPIO_IN BIT(4)
|
||||
#define RTD1625_GPIO_EDGE_INT_DP BIT(6)
|
||||
#define RTD1625_GPIO_EDGE_INT_EN BIT(8)
|
||||
#define RTD1625_GPIO_LEVEL_INT_EN BIT(16)
|
||||
#define RTD1625_GPIO_LEVEL_INT_DP BIT(18)
|
||||
#define RTD1625_GPIO_DEBOUNCE GENMASK(30, 28)
|
||||
#define RTD1625_GPIO_DEBOUNCE_WREN BIT(31)
|
||||
|
||||
#define RTD1625_GPIO_WREN(x) ((x) << 1)
|
||||
|
||||
/* Write-enable masks for all GPIO configs and reserved hardware bits */
|
||||
#define RTD1625_ISO_GPIO_WREN_ALL 0x8000aa8a
|
||||
#define RTD1625_ISOM_GPIO_WREN_ALL 0x800aaa8a
|
||||
|
||||
#define RTD1625_GPIO_DEBOUNCE_1US 0
|
||||
#define RTD1625_GPIO_DEBOUNCE_10US 1
|
||||
#define RTD1625_GPIO_DEBOUNCE_100US 2
|
||||
#define RTD1625_GPIO_DEBOUNCE_1MS 3
|
||||
#define RTD1625_GPIO_DEBOUNCE_10MS 4
|
||||
#define RTD1625_GPIO_DEBOUNCE_20MS 5
|
||||
#define RTD1625_GPIO_DEBOUNCE_30MS 6
|
||||
#define RTD1625_GPIO_DEBOUNCE_50MS 7
|
||||
|
||||
#define GPIO_CONTROL(gpio) ((gpio) * 4)
|
||||
|
||||
static struct lock_class_key rtd1625_gpio_irq_lock_class;
|
||||
static struct lock_class_key rtd1625_gpio_irq_request_class;
|
||||
|
||||
enum rtd1625_irq_index {
|
||||
RTD1625_IRQ_ASSERT,
|
||||
RTD1625_IRQ_DEASSERT,
|
||||
RTD1625_IRQ_LEVEL,
|
||||
RTD1625_MAX_IRQS
|
||||
};
|
||||
|
||||
/**
|
||||
* struct rtd1625_gpio_info - Specific GPIO register information
|
||||
* @num_gpios: The number of GPIOs
|
||||
* @irq_type_support: Supported IRQ types
|
||||
* @base_offset: Offset for GPIO controller register
|
||||
* @gpa_offset: Offset for GPIO assert interrupt status register
|
||||
* @gpda_offset: Offset for GPIO deassert interrupt status register
|
||||
* @level_offset: Offset of level interrupt status register
|
||||
* @write_en_all: Write-enable mask for all configurable bits
|
||||
*/
|
||||
struct rtd1625_gpio_info {
|
||||
unsigned int num_gpios;
|
||||
unsigned int irq_type_support;
|
||||
unsigned int base_offset;
|
||||
unsigned int gpa_offset;
|
||||
unsigned int gpda_offset;
|
||||
unsigned int level_offset;
|
||||
unsigned int write_en_all;
|
||||
};
|
||||
|
||||
struct rtd1625_gpio {
|
||||
struct gpio_regmap *gpio_reg;
|
||||
const struct rtd1625_gpio_info *info;
|
||||
struct regmap *regmap;
|
||||
unsigned int irqs[RTD1625_MAX_IRQS];
|
||||
raw_spinlock_t lock;
|
||||
struct irq_domain *domain;
|
||||
unsigned int *save_regs;
|
||||
};
|
||||
|
||||
static unsigned int rtd1625_gpio_gpa_offset(struct rtd1625_gpio *data, unsigned int offset)
|
||||
{
|
||||
return data->info->gpa_offset + ((offset / 32) * 4);
|
||||
}
|
||||
|
||||
static unsigned int rtd1625_gpio_gpda_offset(struct rtd1625_gpio *data, unsigned int offset)
|
||||
{
|
||||
return data->info->gpda_offset + ((offset / 32) * 4);
|
||||
}
|
||||
|
||||
static unsigned int rtd1625_gpio_level_offset(struct rtd1625_gpio *data, unsigned int offset)
|
||||
{
|
||||
return data->info->level_offset + ((offset / 32) * 4);
|
||||
}
|
||||
|
||||
static int rtd1625_reg_mask_xlate(struct gpio_regmap *gpio, enum gpio_regmap_operation op,
|
||||
unsigned int base, unsigned int offset, unsigned int *reg,
|
||||
unsigned int *mask)
|
||||
{
|
||||
/* Each GPIO has its own dedicated 32-bit register */
|
||||
struct rtd1625_gpio *data = gpio_regmap_get_drvdata(gpio);
|
||||
int val = 0, ret = 0;
|
||||
*reg = base + offset * 4;
|
||||
|
||||
switch (op) {
|
||||
case GPIO_REGMAP_SET_OP:
|
||||
*mask = RTD1625_GPIO_OUT;
|
||||
return 0;
|
||||
|
||||
case GPIO_REGMAP_GET_OP:
|
||||
ret = regmap_read(data->regmap, *reg, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (val & RTD1625_GPIO_DIR)
|
||||
*mask = RTD1625_GPIO_OUT;
|
||||
else
|
||||
*mask = RTD1625_GPIO_IN;
|
||||
return 0;
|
||||
|
||||
case GPIO_REGMAP_GET_DIR_OP:
|
||||
case GPIO_REGMAP_SET_DIR_OP:
|
||||
*mask = RTD1625_GPIO_DIR;
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int rtd1625_value_xlate(struct gpio_regmap *gpio,
|
||||
enum gpio_regmap_operation op,
|
||||
unsigned int base, unsigned int offset,
|
||||
unsigned int reg, unsigned int *mask,
|
||||
unsigned int *val)
|
||||
{
|
||||
switch (op) {
|
||||
case GPIO_REGMAP_SET_OP:
|
||||
*val |= RTD1625_GPIO_WREN(RTD1625_GPIO_OUT);
|
||||
*mask |= RTD1625_GPIO_WREN(RTD1625_GPIO_OUT);
|
||||
return 0;
|
||||
|
||||
case GPIO_REGMAP_SET_DIR_OP:
|
||||
*val |= RTD1625_GPIO_WREN(RTD1625_GPIO_DIR);
|
||||
*mask |= RTD1625_GPIO_WREN(RTD1625_GPIO_DIR);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int rtd1625_gpio_set_debounce(struct rtd1625_gpio *data, unsigned int offset,
|
||||
unsigned int debounce)
|
||||
{
|
||||
u8 deb_val;
|
||||
u32 val;
|
||||
|
||||
switch (debounce) {
|
||||
case 1:
|
||||
deb_val = RTD1625_GPIO_DEBOUNCE_1US;
|
||||
break;
|
||||
case 10:
|
||||
deb_val = RTD1625_GPIO_DEBOUNCE_10US;
|
||||
break;
|
||||
case 100:
|
||||
deb_val = RTD1625_GPIO_DEBOUNCE_100US;
|
||||
break;
|
||||
case 1000:
|
||||
deb_val = RTD1625_GPIO_DEBOUNCE_1MS;
|
||||
break;
|
||||
case 10000:
|
||||
deb_val = RTD1625_GPIO_DEBOUNCE_10MS;
|
||||
break;
|
||||
case 20000:
|
||||
deb_val = RTD1625_GPIO_DEBOUNCE_20MS;
|
||||
break;
|
||||
case 30000:
|
||||
deb_val = RTD1625_GPIO_DEBOUNCE_30MS;
|
||||
break;
|
||||
case 50000:
|
||||
deb_val = RTD1625_GPIO_DEBOUNCE_50MS;
|
||||
break;
|
||||
default:
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
val = FIELD_PREP(RTD1625_GPIO_DEBOUNCE, deb_val) | RTD1625_GPIO_DEBOUNCE_WREN;
|
||||
|
||||
guard(raw_spinlock_irqsave)(&data->lock);
|
||||
|
||||
return regmap_write(data->regmap, data->info->base_offset + GPIO_CONTROL(offset), val);
|
||||
}
|
||||
|
||||
static int rtd1625_gpio_set_config(struct gpio_regmap *gpio, struct gpio_chip *chip,
|
||||
unsigned int offset, unsigned long config)
|
||||
{
|
||||
struct rtd1625_gpio *data = gpio_regmap_get_drvdata(gpio);
|
||||
u32 debounce;
|
||||
|
||||
if (pinconf_to_config_param(config) == PIN_CONFIG_INPUT_DEBOUNCE) {
|
||||
debounce = pinconf_to_config_argument(config);
|
||||
return rtd1625_gpio_set_debounce(data, offset, debounce);
|
||||
}
|
||||
|
||||
return gpiochip_generic_config(chip, offset, config);
|
||||
}
|
||||
|
||||
static void rtd1625_gpio_irq_handle(struct irq_desc *desc)
|
||||
{
|
||||
unsigned int (*get_reg_offset)(struct rtd1625_gpio *gpio, unsigned int offset);
|
||||
struct rtd1625_gpio *data = irq_desc_get_handler_data(desc);
|
||||
struct irq_chip *chip = irq_desc_get_chip(desc);
|
||||
unsigned int irq = irq_desc_get_irq(desc);
|
||||
struct irq_domain *domain = data->domain;
|
||||
unsigned int reg_offset, i, j, val;
|
||||
irq_hw_number_t hwirq;
|
||||
unsigned long status;
|
||||
u32 irq_type;
|
||||
int ret;
|
||||
|
||||
if (irq == data->irqs[RTD1625_IRQ_ASSERT])
|
||||
get_reg_offset = &rtd1625_gpio_gpa_offset;
|
||||
else if (irq == data->irqs[RTD1625_IRQ_DEASSERT])
|
||||
get_reg_offset = &rtd1625_gpio_gpda_offset;
|
||||
else if (irq == data->irqs[RTD1625_IRQ_LEVEL])
|
||||
get_reg_offset = &rtd1625_gpio_level_offset;
|
||||
else
|
||||
return;
|
||||
|
||||
chained_irq_enter(chip, desc);
|
||||
|
||||
for (i = 0; i < data->info->num_gpios; i += 32) {
|
||||
reg_offset = get_reg_offset(data, i);
|
||||
ret = regmap_read(data->regmap, reg_offset, &val);
|
||||
if (ret) {
|
||||
pr_err_ratelimited("Failed to read IRQ status for GPIO %u: %d\n", i, ret);
|
||||
continue;
|
||||
}
|
||||
|
||||
status = val;
|
||||
|
||||
/*
|
||||
* Hardware quirk: The controller fires both "assert" and "de-assert"
|
||||
* interrupts simultaneously on any edge toggle.
|
||||
* We must pre-clear edge interrupts here. If we drop an unwanted
|
||||
* de-assert interrupt below, it will never reach the IRQ core
|
||||
* (generic_handle_domain_irq), meaning ->irq_ack() won't be called.
|
||||
* Failing to clear it here leads to an interrupt storm.
|
||||
*/
|
||||
if (irq != data->irqs[RTD1625_IRQ_LEVEL]) {
|
||||
ret = regmap_write(data->regmap, reg_offset, status);
|
||||
if (ret)
|
||||
pr_err_ratelimited("Failed to clear edge IRQ for GPIO %u: %d\n",
|
||||
i, ret);
|
||||
}
|
||||
|
||||
for_each_set_bit(j, &status, 32) {
|
||||
hwirq = i + j;
|
||||
irq_type = irq_get_trigger_type(irq_find_mapping(domain, hwirq));
|
||||
|
||||
/*
|
||||
* Filter out the hardware-forced de-assert interrupt unless
|
||||
* the user explicitly requested IRQ_TYPE_EDGE_BOTH.
|
||||
*/
|
||||
if (irq == data->irqs[RTD1625_IRQ_DEASSERT] &&
|
||||
irq_type != IRQ_TYPE_EDGE_BOTH)
|
||||
continue;
|
||||
|
||||
generic_handle_domain_irq(domain, hwirq);
|
||||
}
|
||||
}
|
||||
|
||||
chained_irq_exit(chip, desc);
|
||||
}
|
||||
|
||||
static void rtd1625_gpio_ack_irq(struct irq_data *d)
|
||||
{
|
||||
struct rtd1625_gpio *data = irq_data_get_irq_chip_data(d);
|
||||
irq_hw_number_t hwirq = irqd_to_hwirq(d);
|
||||
u32 irq_type = irqd_get_trigger_type(d);
|
||||
u32 bit_mask = BIT(hwirq % 32);
|
||||
int reg_offset;
|
||||
|
||||
if (irq_type & IRQ_TYPE_LEVEL_MASK) {
|
||||
reg_offset = rtd1625_gpio_level_offset(data, hwirq);
|
||||
regmap_write(data->regmap, reg_offset, bit_mask);
|
||||
}
|
||||
}
|
||||
|
||||
static void rtd1625_gpio_enable_edge_irq(struct rtd1625_gpio *data, irq_hw_number_t hwirq)
|
||||
{
|
||||
int gpda_reg_offset = rtd1625_gpio_gpda_offset(data, hwirq);
|
||||
int gpa_reg_offset = rtd1625_gpio_gpa_offset(data, hwirq);
|
||||
u32 clr_mask = BIT(hwirq % 32);
|
||||
u32 val;
|
||||
|
||||
guard(raw_spinlock_irqsave)(&data->lock);
|
||||
|
||||
regmap_write(data->regmap, gpa_reg_offset, clr_mask);
|
||||
regmap_write(data->regmap, gpda_reg_offset, clr_mask);
|
||||
val = RTD1625_GPIO_EDGE_INT_EN | RTD1625_GPIO_WREN(RTD1625_GPIO_EDGE_INT_EN);
|
||||
regmap_write(data->regmap, data->info->base_offset + GPIO_CONTROL(hwirq), val);
|
||||
}
|
||||
|
||||
static void rtd1625_gpio_disable_edge_irq(struct rtd1625_gpio *data, irq_hw_number_t hwirq)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
guard(raw_spinlock_irqsave)(&data->lock);
|
||||
|
||||
val = RTD1625_GPIO_WREN(RTD1625_GPIO_EDGE_INT_EN);
|
||||
regmap_write(data->regmap, data->info->base_offset + GPIO_CONTROL(hwirq), val);
|
||||
}
|
||||
|
||||
static void rtd1625_gpio_enable_level_irq(struct rtd1625_gpio *data, irq_hw_number_t hwirq)
|
||||
{
|
||||
int level_reg_offset = rtd1625_gpio_level_offset(data, hwirq);
|
||||
u32 clr_mask = BIT(hwirq % 32);
|
||||
u32 val;
|
||||
|
||||
guard(raw_spinlock_irqsave)(&data->lock);
|
||||
|
||||
regmap_write(data->regmap, level_reg_offset, clr_mask);
|
||||
val = RTD1625_GPIO_LEVEL_INT_EN | RTD1625_GPIO_WREN(RTD1625_GPIO_LEVEL_INT_EN);
|
||||
regmap_write(data->regmap, data->info->base_offset + GPIO_CONTROL(hwirq), val);
|
||||
}
|
||||
|
||||
static void rtd1625_gpio_disable_level_irq(struct rtd1625_gpio *data, irq_hw_number_t hwirq)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
guard(raw_spinlock_irqsave)(&data->lock);
|
||||
|
||||
val = RTD1625_GPIO_WREN(RTD1625_GPIO_LEVEL_INT_EN);
|
||||
regmap_write(data->regmap, data->info->base_offset + GPIO_CONTROL(hwirq), val);
|
||||
}
|
||||
|
||||
static void rtd1625_gpio_enable_irq(struct irq_data *d)
|
||||
{
|
||||
struct rtd1625_gpio *data = irq_data_get_irq_chip_data(d);
|
||||
irq_hw_number_t hwirq = irqd_to_hwirq(d);
|
||||
u32 irq_type = irqd_get_trigger_type(d);
|
||||
|
||||
gpio_regmap_enable_irq(data->gpio_reg, hwirq);
|
||||
|
||||
if (irq_type & IRQ_TYPE_EDGE_BOTH)
|
||||
rtd1625_gpio_enable_edge_irq(data, hwirq);
|
||||
else if (irq_type & IRQ_TYPE_LEVEL_MASK)
|
||||
rtd1625_gpio_enable_level_irq(data, hwirq);
|
||||
}
|
||||
|
||||
static void rtd1625_gpio_disable_irq(struct irq_data *d)
|
||||
{
|
||||
struct rtd1625_gpio *data = irq_data_get_irq_chip_data(d);
|
||||
irq_hw_number_t hwirq = irqd_to_hwirq(d);
|
||||
u32 irq_type = irqd_get_trigger_type(d);
|
||||
|
||||
if (irq_type & IRQ_TYPE_EDGE_BOTH)
|
||||
rtd1625_gpio_disable_edge_irq(data, hwirq);
|
||||
else if (irq_type & IRQ_TYPE_LEVEL_MASK)
|
||||
rtd1625_gpio_disable_level_irq(data, hwirq);
|
||||
|
||||
gpio_regmap_disable_irq(data->gpio_reg, hwirq);
|
||||
}
|
||||
|
||||
static int rtd1625_gpio_irq_set_level_type(struct irq_data *d, bool level)
|
||||
{
|
||||
u32 val = RTD1625_GPIO_WREN(RTD1625_GPIO_LEVEL_INT_DP);
|
||||
irq_hw_number_t hwirq = irqd_to_hwirq(d);
|
||||
struct rtd1625_gpio *data;
|
||||
int ret;
|
||||
|
||||
data = irq_data_get_irq_chip_data(d);
|
||||
if (!(data->info->irq_type_support & IRQ_TYPE_LEVEL_MASK))
|
||||
return -EINVAL;
|
||||
|
||||
if (level)
|
||||
val |= RTD1625_GPIO_LEVEL_INT_DP;
|
||||
|
||||
scoped_guard(raw_spinlock_irqsave, &data->lock) {
|
||||
ret = regmap_write(data->regmap, data->info->base_offset + GPIO_CONTROL(hwirq),
|
||||
val);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
irq_set_handler_locked(d, handle_level_irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtd1625_gpio_irq_set_edge_type(struct irq_data *d, bool polarity)
|
||||
{
|
||||
struct rtd1625_gpio *data = irq_data_get_irq_chip_data(d);
|
||||
u32 val = RTD1625_GPIO_WREN(RTD1625_GPIO_EDGE_INT_DP);
|
||||
irq_hw_number_t hwirq = irqd_to_hwirq(d);
|
||||
int ret;
|
||||
|
||||
if (!(data->info->irq_type_support & IRQ_TYPE_EDGE_BOTH))
|
||||
return -EINVAL;
|
||||
|
||||
if (polarity)
|
||||
val |= RTD1625_GPIO_EDGE_INT_DP;
|
||||
|
||||
scoped_guard(raw_spinlock_irqsave, &data->lock) {
|
||||
ret = regmap_write(data->regmap, data->info->base_offset + GPIO_CONTROL(hwirq),
|
||||
val);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
irq_set_handler_locked(d, handle_edge_irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtd1625_gpio_irq_set_type(struct irq_data *d, unsigned int type)
|
||||
{
|
||||
switch (type & IRQ_TYPE_SENSE_MASK) {
|
||||
case IRQ_TYPE_EDGE_RISING:
|
||||
return rtd1625_gpio_irq_set_edge_type(d, 1);
|
||||
|
||||
case IRQ_TYPE_EDGE_FALLING:
|
||||
return rtd1625_gpio_irq_set_edge_type(d, 0);
|
||||
|
||||
case IRQ_TYPE_EDGE_BOTH:
|
||||
return rtd1625_gpio_irq_set_edge_type(d, 1);
|
||||
|
||||
case IRQ_TYPE_LEVEL_HIGH:
|
||||
return rtd1625_gpio_irq_set_level_type(d, 0);
|
||||
|
||||
case IRQ_TYPE_LEVEL_LOW:
|
||||
return rtd1625_gpio_irq_set_level_type(d, 1);
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int rtd1625_gpio_irq_request_resources(struct irq_data *d)
|
||||
{
|
||||
struct rtd1625_gpio *data = irq_data_get_irq_chip_data(d);
|
||||
|
||||
return gpio_regmap_reqres_irq(data->gpio_reg, d->hwirq);
|
||||
}
|
||||
|
||||
static void rtd1625_gpio_irq_release_resources(struct irq_data *d)
|
||||
{
|
||||
struct rtd1625_gpio *data = irq_data_get_irq_chip_data(d);
|
||||
|
||||
gpio_regmap_relres_irq(data->gpio_reg, d->hwirq);
|
||||
}
|
||||
|
||||
static struct irq_chip rtd1625_iso_gpio_irq_chip = {
|
||||
.name = "rtd1625-gpio",
|
||||
.irq_ack = rtd1625_gpio_ack_irq,
|
||||
.irq_mask = rtd1625_gpio_disable_irq,
|
||||
.irq_unmask = rtd1625_gpio_enable_irq,
|
||||
.irq_set_type = rtd1625_gpio_irq_set_type,
|
||||
.flags = IRQCHIP_IMMUTABLE | IRQCHIP_SKIP_SET_WAKE,
|
||||
.irq_request_resources = rtd1625_gpio_irq_request_resources,
|
||||
.irq_release_resources = rtd1625_gpio_irq_release_resources,
|
||||
};
|
||||
|
||||
static int rtd1625_gpio_setup_irq(struct platform_device *pdev, struct rtd1625_gpio *data)
|
||||
{
|
||||
unsigned int num_irqs;
|
||||
int irq;
|
||||
|
||||
/* IRQ is optional; operate as basic GPIO if absent */
|
||||
irq = platform_get_irq_optional(pdev, 0);
|
||||
if (irq == -ENXIO)
|
||||
return 0;
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
num_irqs = (data->info->irq_type_support & IRQ_TYPE_LEVEL_MASK) ? 3 : 2;
|
||||
|
||||
for (unsigned int i = 0; i < num_irqs; i++) {
|
||||
irq = platform_get_irq(pdev, i);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
data->irqs[i] = irq;
|
||||
irq_set_chained_handler_and_data(data->irqs[i], rtd1625_gpio_irq_handle, data);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtd1625_gpio_irq_map(struct irq_domain *domain, unsigned int irq,
|
||||
irq_hw_number_t hwirq)
|
||||
{
|
||||
struct rtd1625_gpio *data = domain->host_data;
|
||||
|
||||
irq_set_chip_data(irq, data);
|
||||
|
||||
irq_set_chip_and_handler(irq, &rtd1625_iso_gpio_irq_chip, handle_bad_irq);
|
||||
|
||||
irq_set_noprobe(irq);
|
||||
|
||||
irq_set_lockdep_class(irq, &rtd1625_gpio_irq_lock_class,
|
||||
&rtd1625_gpio_irq_request_class);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops rtd1625_gpio_irq_domain_ops = {
|
||||
.map = rtd1625_gpio_irq_map,
|
||||
.xlate = irq_domain_xlate_twocell,
|
||||
};
|
||||
|
||||
static const struct regmap_config rtd1625_gpio_regmap_config = {
|
||||
.reg_bits = 32,
|
||||
.reg_stride = 4,
|
||||
.val_bits = 32,
|
||||
#ifndef CONFIG_DEBUG_GPIO
|
||||
.disable_locking = true,
|
||||
#endif
|
||||
};
|
||||
|
||||
static int rtd1625_gpio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct gpio_regmap_config config = {};
|
||||
struct irq_domain_info d_info = {};
|
||||
struct device *dev = &pdev->dev;
|
||||
struct gpio_regmap *gpio_reg;
|
||||
struct rtd1625_gpio *data;
|
||||
void __iomem *irq_base;
|
||||
int ret;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->info = device_get_match_data(dev);
|
||||
if (!data->info)
|
||||
return -ENODATA;
|
||||
|
||||
irq_base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(irq_base))
|
||||
return PTR_ERR(irq_base);
|
||||
|
||||
data->regmap = devm_regmap_init_mmio(dev, irq_base, &rtd1625_gpio_regmap_config);
|
||||
if (IS_ERR(data->regmap))
|
||||
return PTR_ERR(data->regmap);
|
||||
|
||||
raw_spin_lock_init(&data->lock);
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
data->save_regs = devm_kcalloc(dev, data->info->num_gpios, sizeof(*data->save_regs),
|
||||
GFP_KERNEL);
|
||||
if (!data->save_regs)
|
||||
return -ENOMEM;
|
||||
|
||||
config.parent = dev;
|
||||
config.regmap = data->regmap;
|
||||
config.ngpio = data->info->num_gpios;
|
||||
config.reg_dat_base = data->info->base_offset;
|
||||
config.reg_set_base = data->info->base_offset;
|
||||
config.reg_dir_out_base = data->info->base_offset;
|
||||
|
||||
config.reg_mask_xlate = rtd1625_reg_mask_xlate;
|
||||
config.set_config = rtd1625_gpio_set_config;
|
||||
config.value_xlate = rtd1625_value_xlate;
|
||||
|
||||
d_info.fwnode = dev_fwnode(&pdev->dev);
|
||||
d_info.size = data->info->num_gpios;
|
||||
d_info.hwirq_max = data->info->num_gpios;
|
||||
d_info.ops = &rtd1625_gpio_irq_domain_ops;
|
||||
d_info.host_data = data;
|
||||
|
||||
data->domain = devm_irq_domain_instantiate(dev, &d_info);
|
||||
if (IS_ERR(data->domain))
|
||||
return PTR_ERR(data->domain);
|
||||
|
||||
ret = rtd1625_gpio_setup_irq(pdev, data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
config.irq_domain = data->domain;
|
||||
config.drvdata = data;
|
||||
|
||||
gpio_reg = devm_gpio_regmap_register(dev, &config);
|
||||
if (IS_ERR(gpio_reg))
|
||||
return PTR_ERR(gpio_reg);
|
||||
|
||||
data->gpio_reg = gpio_reg;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct rtd1625_gpio_info rtd1625_iso_gpio_info = {
|
||||
.num_gpios = 166,
|
||||
.irq_type_support = IRQ_TYPE_EDGE_BOTH,
|
||||
.base_offset = 0x100,
|
||||
.gpa_offset = 0x000,
|
||||
.gpda_offset = 0x020,
|
||||
.write_en_all = RTD1625_ISO_GPIO_WREN_ALL,
|
||||
};
|
||||
|
||||
static const struct rtd1625_gpio_info rtd1625_isom_gpio_info = {
|
||||
.num_gpios = 4,
|
||||
.irq_type_support = IRQ_TYPE_EDGE_BOTH | IRQ_TYPE_LEVEL_LOW |
|
||||
IRQ_TYPE_LEVEL_HIGH,
|
||||
.base_offset = 0x20,
|
||||
.gpa_offset = 0x00,
|
||||
.gpda_offset = 0x04,
|
||||
.level_offset = 0x18,
|
||||
.write_en_all = RTD1625_ISOM_GPIO_WREN_ALL,
|
||||
};
|
||||
|
||||
static int rtd1625_gpio_suspend(struct device *dev)
|
||||
{
|
||||
struct rtd1625_gpio *data = dev_get_drvdata(dev);
|
||||
const struct rtd1625_gpio_info *info = data->info;
|
||||
int ret;
|
||||
|
||||
for (unsigned int i = 0; i < info->num_gpios; i++) {
|
||||
ret = regmap_read(data->regmap, data->info->base_offset + GPIO_CONTROL(i),
|
||||
&data->save_regs[i]);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtd1625_gpio_resume(struct device *dev)
|
||||
{
|
||||
struct rtd1625_gpio *data = dev_get_drvdata(dev);
|
||||
const struct rtd1625_gpio_info *info = data->info;
|
||||
int ret;
|
||||
|
||||
for (unsigned int i = 0; i < info->num_gpios; i++) {
|
||||
ret = regmap_write(data->regmap, data->info->base_offset + GPIO_CONTROL(i),
|
||||
data->save_regs[i] | info->write_en_all);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DEFINE_NOIRQ_DEV_PM_OPS(rtd1625_gpio_pm_ops, rtd1625_gpio_suspend, rtd1625_gpio_resume);
|
||||
|
||||
static const struct of_device_id rtd1625_gpio_of_matches[] = {
|
||||
{ .compatible = "realtek,rtd1625-iso-gpio", .data = &rtd1625_iso_gpio_info },
|
||||
{ .compatible = "realtek,rtd1625-isom-gpio", .data = &rtd1625_isom_gpio_info },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rtd1625_gpio_of_matches);
|
||||
|
||||
static struct platform_driver rtd1625_gpio_platform_driver = {
|
||||
.driver = {
|
||||
.name = "gpio-rtd1625",
|
||||
.of_match_table = rtd1625_gpio_of_matches,
|
||||
.pm = pm_sleep_ptr(&rtd1625_gpio_pm_ops),
|
||||
},
|
||||
.probe = rtd1625_gpio_probe,
|
||||
};
|
||||
module_platform_driver(rtd1625_gpio_platform_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Realtek Semiconductor Corporation");
|
||||
MODULE_DESCRIPTION("Realtek DHC SoC RTD1625 gpio driver");
|
||||
@@ -139,7 +139,7 @@ static int sa1100_gpio_type(struct irq_data *d, unsigned int type)
|
||||
if (type == IRQ_TYPE_PROBE) {
|
||||
if ((sgc->irqrising | sgc->irqfalling) & mask)
|
||||
return 0;
|
||||
type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING;
|
||||
type = IRQ_TYPE_EDGE_BOTH;
|
||||
}
|
||||
|
||||
if (type & IRQ_TYPE_EDGE_RISING)
|
||||
|
||||
@@ -260,6 +260,7 @@ static const struct of_device_id sifive_gpio_match[] = {
|
||||
{ .compatible = "sifive,fu540-c000-gpio" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sifive_gpio_match);
|
||||
|
||||
static struct platform_driver sifive_gpio_driver = {
|
||||
.probe = sifive_gpio_probe,
|
||||
|
||||
@@ -244,6 +244,7 @@ static int gpio_la_poll_probe(struct platform_device *pdev)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Initially allocate a buffer. It currently is NULL */
|
||||
fops_buf_size_set(priv, GPIO_LA_DEFAULT_BUF_SIZE);
|
||||
ret = devm_add_action_or_reset(dev, fops_buf_release, priv);
|
||||
if (ret)
|
||||
|
||||
@@ -158,7 +158,7 @@ static int sdv_register_irqsupport(struct sdv_gpio_chip_data *sd,
|
||||
|
||||
sd->gc->private = sd;
|
||||
ct = sd->gc->chip_types;
|
||||
ct->type = IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW;
|
||||
ct->type = IRQ_TYPE_LEVEL_MASK;
|
||||
ct->regs.eoi = GPSTR;
|
||||
ct->regs.mask = GPIO_INT;
|
||||
ct->chip.irq_mask = irq_gc_mask_clr_bit;
|
||||
|
||||
@@ -149,7 +149,7 @@ static int stmpe_gpio_irq_set_type(struct irq_data *d, unsigned int type)
|
||||
int regoffset = offset / 8;
|
||||
int mask = BIT(offset % 8);
|
||||
|
||||
if (type & IRQ_TYPE_LEVEL_LOW || type & IRQ_TYPE_LEVEL_HIGH)
|
||||
if (type & IRQ_TYPE_LEVEL_MASK)
|
||||
return -EINVAL;
|
||||
|
||||
/* STMPE801 and STMPE 1600 don't have RE and FE registers */
|
||||
|
||||
@@ -51,7 +51,7 @@ static inline u32 tb10x_reg_read(struct tb10x_gpio *gpio, unsigned int offs)
|
||||
return ioread32(gpio->base + offs);
|
||||
}
|
||||
|
||||
static int tb10x_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
|
||||
static int tb10x_gpio_to_irq(struct gpio_chip *chip, unsigned int offset)
|
||||
{
|
||||
struct tb10x_gpio *tb10x_gpio = gpiochip_get_data(chip);
|
||||
|
||||
@@ -167,9 +167,8 @@ static int tb10x_gpio_probe(struct platform_device *pdev)
|
||||
tb10x_gpio->domain = irq_domain_create_linear(dev_fwnode(dev),
|
||||
tb10x_gpio->chip.gc.ngpio,
|
||||
&irq_generic_chip_ops, NULL);
|
||||
if (!tb10x_gpio->domain) {
|
||||
if (!tb10x_gpio->domain)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = irq_alloc_domain_generic_chips(tb10x_gpio->domain,
|
||||
tb10x_gpio->chip.gc.ngpio, 1, tb10x_gpio->chip.gc.label,
|
||||
|
||||
@@ -342,9 +342,9 @@ static int tegra_gpio_irq_set_type(struct irq_data *d, unsigned int type)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH))
|
||||
if (type & IRQ_TYPE_LEVEL_MASK)
|
||||
irq_set_handler_locked(d, handle_level_irq);
|
||||
else if (type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING))
|
||||
else if (type & IRQ_TYPE_EDGE_BOTH)
|
||||
irq_set_handler_locked(d, handle_edge_irq);
|
||||
|
||||
if (d->parent_data)
|
||||
|
||||
@@ -148,7 +148,7 @@ static int timbgpio_irq_type(struct irq_data *d, unsigned trigger)
|
||||
if (ver > 2)
|
||||
bflr = ioread32(tgpio->membase + TGPIO_BFLR);
|
||||
|
||||
if (trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) {
|
||||
if (trigger & IRQ_TYPE_LEVEL_MASK) {
|
||||
bflr &= ~(1 << offset);
|
||||
flr &= ~(1 << offset);
|
||||
if (trigger & IRQ_TYPE_LEVEL_HIGH)
|
||||
|
||||
@@ -83,6 +83,7 @@ static const struct of_device_id vf610_gpio_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx8ulp-gpio", .data = &imx8ulp_data, },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, vf610_gpio_dt_ids);
|
||||
|
||||
static inline void vf610_gpio_writel(u32 val, void __iomem *reg)
|
||||
{
|
||||
|
||||
@@ -2682,15 +2682,8 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file)
|
||||
file->private_data = cdev;
|
||||
cdev->fp = file;
|
||||
|
||||
ret = nonseekable_open(inode, file);
|
||||
if (ret)
|
||||
goto out_unregister_device_notifier;
|
||||
return nonseekable_open(inode, file);
|
||||
|
||||
return ret;
|
||||
|
||||
out_unregister_device_notifier:
|
||||
blocking_notifier_chain_unregister(&gdev->device_notifier,
|
||||
&cdev->device_unregistered_nb);
|
||||
out_unregister_line_notifier:
|
||||
scoped_guard(write_lock_irqsave, &gdev->line_state_lock)
|
||||
raw_notifier_chain_unregister(&gdev->line_state_notifier,
|
||||
|
||||
+546
-25
@@ -3,6 +3,8 @@
|
||||
* Copyright (C) Qualcomm Technologies, Inc. and/or its subsidiaries
|
||||
*/
|
||||
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/fwnode.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
@@ -11,26 +13,78 @@
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <kunit/fwnode.h>
|
||||
#include <kunit/platform_device.h>
|
||||
#include <kunit/test.h>
|
||||
|
||||
#define GPIO_TEST_PROVIDER "gpio-test-provider"
|
||||
#define GPIO_SWNODE_TEST_CONSUMER "gpio-swnode-test-consumer"
|
||||
#define GPIO_PROBE_ORDER_TEST_CONSUMER "gpio-probe-order-test-consumer"
|
||||
#define GPIO_PROBE_DEFER_TEST_CONSUMER "gpio-probe-defer-test-consumer"
|
||||
#define GPIO_UNBIND_TEST_CONSUMER "gpio-unbind-test-consumer"
|
||||
#define GPIO_CONSUMER_NAME "gpio-swnode-consumer-test-device"
|
||||
|
||||
#define GPIO_TEST_PROVIDER_NGPIO 4
|
||||
|
||||
/*
|
||||
* The test provider tracks per-line direction and value so that lines can be
|
||||
* driven as both inputs and outputs - this is needed to exercise input as well
|
||||
* as output GPIO hogs.
|
||||
*/
|
||||
struct gpio_test_provider_data {
|
||||
DECLARE_BITMAP(is_output, GPIO_TEST_PROVIDER_NGPIO);
|
||||
DECLARE_BITMAP(values, GPIO_TEST_PROVIDER_NGPIO);
|
||||
};
|
||||
|
||||
static int gpio_test_provider_get_direction(struct gpio_chip *gc, unsigned int offset)
|
||||
{
|
||||
return GPIO_LINE_DIRECTION_OUT;
|
||||
struct gpio_test_provider_data *data = gpiochip_get_data(gc);
|
||||
|
||||
return test_bit(offset, data->is_output) ?
|
||||
GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
|
||||
}
|
||||
|
||||
static int gpio_test_provider_direction_input(struct gpio_chip *gc, unsigned int offset)
|
||||
{
|
||||
struct gpio_test_provider_data *data = gpiochip_get_data(gc);
|
||||
|
||||
clear_bit(offset, data->is_output);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gpio_test_provider_direction_output(struct gpio_chip *gc, unsigned int offset,
|
||||
int value)
|
||||
{
|
||||
struct gpio_test_provider_data *data = gpiochip_get_data(gc);
|
||||
|
||||
set_bit(offset, data->is_output);
|
||||
__assign_bit(offset, data->values, value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gpio_test_provider_get(struct gpio_chip *gc, unsigned int offset)
|
||||
{
|
||||
struct gpio_test_provider_data *data = gpiochip_get_data(gc);
|
||||
|
||||
return test_bit(offset, data->values);
|
||||
}
|
||||
|
||||
static int gpio_test_provider_set(struct gpio_chip *gc, unsigned int offset, int value)
|
||||
{
|
||||
struct gpio_test_provider_data *data = gpiochip_get_data(gc);
|
||||
|
||||
__assign_bit(offset, data->values, value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gpio_test_provider_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct gpio_test_provider_data *data;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct gpio_chip *gc;
|
||||
|
||||
@@ -38,16 +92,26 @@ static int gpio_test_provider_probe(struct platform_device *pdev)
|
||||
if (!gc)
|
||||
return -ENOMEM;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Lines start as outputs to preserve the default for lookup tests. */
|
||||
bitmap_fill(data->is_output, GPIO_TEST_PROVIDER_NGPIO);
|
||||
|
||||
gc->base = -1;
|
||||
gc->ngpio = 4;
|
||||
gc->label = "gpio-swnode-consumer-test-device";
|
||||
gc->ngpio = GPIO_TEST_PROVIDER_NGPIO;
|
||||
gc->label = GPIO_CONSUMER_NAME;
|
||||
gc->parent = dev;
|
||||
gc->owner = THIS_MODULE;
|
||||
|
||||
gc->get_direction = gpio_test_provider_get_direction;
|
||||
gc->direction_input = gpio_test_provider_direction_input;
|
||||
gc->direction_output = gpio_test_provider_direction_output;
|
||||
gc->get = gpio_test_provider_get;
|
||||
gc->set = gpio_test_provider_set;
|
||||
|
||||
return devm_gpiochip_add_data(dev, gc, NULL);
|
||||
return devm_gpiochip_add_data(dev, gc, data);
|
||||
}
|
||||
|
||||
static struct platform_driver gpio_test_provider_driver = {
|
||||
@@ -63,10 +127,12 @@ static const struct software_node gpio_test_provider_swnode = {
|
||||
|
||||
struct gpio_swnode_consumer_pdata {
|
||||
bool gpio_ok;
|
||||
int errno;
|
||||
};
|
||||
|
||||
static const struct gpio_swnode_consumer_pdata gpio_swnode_pdata_template = {
|
||||
.gpio_ok = false,
|
||||
.errno = 0,
|
||||
};
|
||||
|
||||
static int gpio_swnode_consumer_probe(struct platform_device *pdev)
|
||||
@@ -76,8 +142,10 @@ static int gpio_swnode_consumer_probe(struct platform_device *pdev)
|
||||
struct gpio_desc *desc;
|
||||
|
||||
desc = devm_gpiod_get(dev, "foo", GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(desc))
|
||||
if (IS_ERR(desc)) {
|
||||
pdata->errno = PTR_ERR(desc);
|
||||
return PTR_ERR(desc);
|
||||
}
|
||||
|
||||
pdata->gpio_ok = true;
|
||||
|
||||
@@ -91,13 +159,8 @@ static struct platform_driver gpio_swnode_consumer_driver = {
|
||||
},
|
||||
};
|
||||
|
||||
static void gpio_swnode_lookup_by_primary(struct kunit *test)
|
||||
static int gpio_swnode_register_drivers(struct kunit *test)
|
||||
{
|
||||
struct gpio_swnode_consumer_pdata *pdata;
|
||||
struct platform_device_info pdevinfo;
|
||||
struct property_entry properties[2];
|
||||
struct platform_device *pdev;
|
||||
bool bound = false;
|
||||
int ret;
|
||||
|
||||
ret = kunit_platform_driver_register(test, &gpio_test_provider_driver);
|
||||
@@ -106,6 +169,17 @@ static void gpio_swnode_lookup_by_primary(struct kunit *test)
|
||||
ret = kunit_platform_driver_register(test, &gpio_swnode_consumer_driver);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gpio_swnode_lookup_by_primary(struct kunit *test)
|
||||
{
|
||||
struct gpio_swnode_consumer_pdata *pdata;
|
||||
struct platform_device_info pdevinfo;
|
||||
struct property_entry properties[2];
|
||||
struct platform_device *pdev;
|
||||
bool bound = false;
|
||||
|
||||
pdevinfo = (struct platform_device_info){
|
||||
.name = GPIO_TEST_PROVIDER,
|
||||
.id = PLATFORM_DEVID_NONE,
|
||||
@@ -149,7 +223,6 @@ static void gpio_swnode_lookup_by_secondary(struct kunit *test)
|
||||
struct fwnode_handle *primary;
|
||||
struct platform_device *pdev;
|
||||
bool bound = false;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Can't live on the stack as it will still get referenced in cleanup
|
||||
@@ -158,12 +231,6 @@ static void gpio_swnode_lookup_by_secondary(struct kunit *test)
|
||||
primary = kunit_kzalloc(test, sizeof(*primary), GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, primary);
|
||||
|
||||
ret = kunit_platform_driver_register(test, &gpio_test_provider_driver);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
ret = kunit_platform_driver_register(test, &gpio_swnode_consumer_driver);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
fwnode_init(primary, NULL);
|
||||
|
||||
pdevinfo = (struct platform_device_info){
|
||||
@@ -211,6 +278,266 @@ static struct kunit_case gpio_swnode_lookup_tests[] = {
|
||||
static struct kunit_suite gpio_swnode_lookup_test_suite = {
|
||||
.name = "gpio-swnode-lookup",
|
||||
.test_cases = gpio_swnode_lookup_tests,
|
||||
.init = gpio_swnode_register_drivers,
|
||||
};
|
||||
|
||||
static void gpio_swnode_unregister_swnode(void *data)
|
||||
{
|
||||
software_node_unregister(data);
|
||||
}
|
||||
|
||||
struct gpio_probe_order_pdata {
|
||||
unsigned int probe_count;
|
||||
bool gpio_ok;
|
||||
};
|
||||
|
||||
static const struct gpio_probe_order_pdata gpio_probe_order_pdata_template = {
|
||||
.probe_count = 0,
|
||||
.gpio_ok = false,
|
||||
};
|
||||
|
||||
static int gpio_probe_order_consumer_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct gpio_probe_order_pdata *pdata = dev_get_platdata(dev);
|
||||
struct gpio_desc *desc;
|
||||
|
||||
pdata->probe_count++;
|
||||
|
||||
desc = devm_gpiod_get(dev, "foo", GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(desc))
|
||||
return PTR_ERR(desc);
|
||||
|
||||
pdata->gpio_ok = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver gpio_probe_order_consumer_driver = {
|
||||
.probe = gpio_probe_order_consumer_probe,
|
||||
.driver = {
|
||||
.name = GPIO_PROBE_ORDER_TEST_CONSUMER,
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
* Verify that fw_devlink orders the probe of a GPIO consumer after its
|
||||
* provider. The consumer references the provider through a software node and
|
||||
* is registered first. fw_devlink must defer it before its driver's probe()
|
||||
* is ever entered, so the consumer probes exactly once - only after the
|
||||
* provider is added and bound.
|
||||
*/
|
||||
static void gpio_swnode_probe_order(struct kunit *test)
|
||||
{
|
||||
struct property_entry properties[2] = { };
|
||||
struct gpio_probe_order_pdata *pdata;
|
||||
struct platform_device_info pdevinfo;
|
||||
struct platform_device *prvd, *cons;
|
||||
bool bound = false;
|
||||
int ret;
|
||||
|
||||
ret = kunit_platform_driver_register(test, &gpio_test_provider_driver);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
ret = kunit_platform_driver_register(test, &gpio_probe_order_consumer_driver);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
ret = software_node_register(&gpio_test_provider_swnode);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
ret = kunit_add_action_or_reset(test, gpio_swnode_unregister_swnode,
|
||||
(void *)&gpio_test_provider_swnode);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
properties[0] = PROPERTY_ENTRY_GPIO("foo-gpios",
|
||||
&gpio_test_provider_swnode,
|
||||
0, GPIO_ACTIVE_HIGH);
|
||||
|
||||
pdevinfo = (struct platform_device_info){
|
||||
.name = GPIO_PROBE_ORDER_TEST_CONSUMER,
|
||||
.id = PLATFORM_DEVID_NONE,
|
||||
.data = &gpio_probe_order_pdata_template,
|
||||
.size_data = sizeof(gpio_probe_order_pdata_template),
|
||||
.properties = properties,
|
||||
};
|
||||
|
||||
cons = kunit_platform_device_register_full(test, &pdevinfo);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons);
|
||||
|
||||
wait_for_device_probe();
|
||||
scoped_guard(device, &cons->dev)
|
||||
bound = device_is_bound(&cons->dev);
|
||||
|
||||
KUNIT_ASSERT_FALSE(test, bound);
|
||||
|
||||
pdata = dev_get_platdata(&cons->dev);
|
||||
KUNIT_ASSERT_EQ(test, pdata->probe_count, 0);
|
||||
KUNIT_ASSERT_FALSE(test, pdata->gpio_ok);
|
||||
|
||||
pdevinfo = (struct platform_device_info){
|
||||
.name = GPIO_TEST_PROVIDER,
|
||||
.id = PLATFORM_DEVID_NONE,
|
||||
.swnode = &gpio_test_provider_swnode,
|
||||
};
|
||||
|
||||
prvd = kunit_platform_device_register_full(test, &pdevinfo);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, prvd);
|
||||
|
||||
wait_for_device_probe();
|
||||
|
||||
scoped_guard(device, &prvd->dev)
|
||||
bound = device_is_bound(&prvd->dev);
|
||||
KUNIT_ASSERT_TRUE(test, bound);
|
||||
|
||||
scoped_guard(device, &cons->dev)
|
||||
bound = device_is_bound(&cons->dev);
|
||||
KUNIT_ASSERT_TRUE(test, bound);
|
||||
|
||||
pdata = dev_get_platdata(&cons->dev);
|
||||
KUNIT_ASSERT_EQ(test, pdata->probe_count, 1);
|
||||
KUNIT_ASSERT_TRUE(test, pdata->gpio_ok);
|
||||
}
|
||||
|
||||
struct gpio_probe_defer_pdata {
|
||||
unsigned int probe_count;
|
||||
int gpio_err;
|
||||
};
|
||||
|
||||
static const struct gpio_probe_defer_pdata gpio_probe_defer_pdata_template = {
|
||||
.probe_count = 0,
|
||||
.gpio_err = 0,
|
||||
};
|
||||
|
||||
static int gpio_probe_defer_consumer_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct gpio_probe_defer_pdata *pdata = dev_get_platdata(dev);
|
||||
struct gpio_desc *desc;
|
||||
|
||||
pdata->probe_count++;
|
||||
|
||||
desc = devm_gpiod_get(dev, "foo", GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(desc)) {
|
||||
pdata->gpio_err = PTR_ERR(desc);
|
||||
return pdata->gpio_err;
|
||||
}
|
||||
|
||||
pdata->gpio_err = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver gpio_probe_defer_consumer_driver = {
|
||||
.probe = gpio_probe_defer_consumer_probe,
|
||||
.driver = {
|
||||
.name = GPIO_PROBE_DEFER_TEST_CONSUMER,
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
* Verify that a GPIO consumer referencing a provider whose software node is
|
||||
* not registered yet, defers its probe instead of failing.
|
||||
*
|
||||
* The provider software node is deliberately left unregistered when the
|
||||
* consumer is added. fw_devlink cannot resolve the reference, so it creates no
|
||||
* supplier link and does not order the consumer - the consumer's probe() runs
|
||||
* and reaches devm_gpiod_get(). The swnode GPIO lookup returns -ENOTCONN for a
|
||||
* reference to an unregistered node, which gpiolib maps to -EPROBE_DEFER. Once
|
||||
* the provider software node and device appear, the deferred consumer probes
|
||||
* again and binds.
|
||||
*/
|
||||
static void gpio_swnode_probe_defer_on_unregistered(struct kunit *test)
|
||||
{
|
||||
struct property_entry properties[2] = { };
|
||||
struct gpio_probe_defer_pdata *pdata;
|
||||
struct platform_device_info pdevinfo;
|
||||
struct platform_device *prvd, *cons;
|
||||
struct fwnode_handle *fwnode;
|
||||
bool bound = false;
|
||||
int ret;
|
||||
|
||||
ret = kunit_platform_driver_register(test, &gpio_test_provider_driver);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
ret = kunit_platform_driver_register(test, &gpio_probe_defer_consumer_driver);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
properties[0] = PROPERTY_ENTRY_GPIO("foo-gpios",
|
||||
&gpio_test_provider_swnode,
|
||||
0, GPIO_ACTIVE_HIGH);
|
||||
|
||||
pdevinfo = (struct platform_device_info){
|
||||
.name = GPIO_PROBE_DEFER_TEST_CONSUMER,
|
||||
.id = PLATFORM_DEVID_NONE,
|
||||
.data = &gpio_probe_defer_pdata_template,
|
||||
.size_data = sizeof(gpio_probe_defer_pdata_template),
|
||||
.properties = properties,
|
||||
};
|
||||
|
||||
cons = kunit_platform_device_register_full(test, &pdevinfo);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons);
|
||||
|
||||
wait_for_device_probe();
|
||||
scoped_guard(device, &cons->dev)
|
||||
bound = device_is_bound(&cons->dev);
|
||||
|
||||
KUNIT_ASSERT_FALSE(test, bound);
|
||||
|
||||
pdata = dev_get_platdata(&cons->dev);
|
||||
KUNIT_ASSERT_GT(test, pdata->probe_count, 0);
|
||||
KUNIT_ASSERT_EQ(test, pdata->gpio_err, -EPROBE_DEFER);
|
||||
|
||||
fwnode = kunit_software_node_register(test, &gpio_test_provider_swnode);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode);
|
||||
|
||||
pdevinfo = (struct platform_device_info){
|
||||
.name = GPIO_TEST_PROVIDER,
|
||||
.id = PLATFORM_DEVID_NONE,
|
||||
.swnode = &gpio_test_provider_swnode,
|
||||
};
|
||||
|
||||
prvd = kunit_platform_device_register_full(test, &pdevinfo);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, prvd);
|
||||
|
||||
wait_for_device_probe();
|
||||
|
||||
scoped_guard(device, &prvd->dev)
|
||||
bound = device_is_bound(&prvd->dev);
|
||||
KUNIT_ASSERT_TRUE(test, bound);
|
||||
|
||||
scoped_guard(device, &cons->dev)
|
||||
bound = device_is_bound(&cons->dev);
|
||||
KUNIT_ASSERT_TRUE(test, bound);
|
||||
|
||||
pdata = dev_get_platdata(&cons->dev);
|
||||
KUNIT_ASSERT_EQ(test, pdata->gpio_err, 0);
|
||||
|
||||
/* Tear down the consumer before the provider to free the GPIO. */
|
||||
kunit_platform_device_unregister(test, cons);
|
||||
}
|
||||
|
||||
static int gpio_swnode_probe_order_test_init(struct kunit *test)
|
||||
{
|
||||
/*
|
||||
* A prior test may have left a managed device link teardown queued on
|
||||
* the device_link_mq. Flush it so that software_node_register()
|
||||
* doesn't spuriously see the node as registered and fail with -EEXIST.
|
||||
*/
|
||||
device_link_wait_removal();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct kunit_case gpio_swnode_probe_order_tests[] = {
|
||||
KUNIT_CASE(gpio_swnode_probe_order),
|
||||
KUNIT_CASE(gpio_swnode_probe_defer_on_unregistered),
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct kunit_suite gpio_swnode_probe_order_test_suite = {
|
||||
.name = "gpio-swnode-probe-order",
|
||||
.test_cases = gpio_swnode_probe_order_tests,
|
||||
.init = gpio_swnode_probe_order_test_init,
|
||||
};
|
||||
|
||||
static BLOCKING_NOTIFIER_HEAD(gpio_unbind_notifier);
|
||||
@@ -310,15 +637,24 @@ static void gpio_unbind_with_consumers(struct kunit *test)
|
||||
0, GPIO_ACTIVE_HIGH);
|
||||
properties[1] = (struct property_entry){ };
|
||||
|
||||
pdevinfo = (struct platform_device_info){
|
||||
.name = GPIO_UNBIND_TEST_CONSUMER,
|
||||
.id = PLATFORM_DEVID_NONE,
|
||||
.properties = properties,
|
||||
};
|
||||
|
||||
cons = kunit_platform_device_register_full(test, &pdevinfo);
|
||||
/*
|
||||
* This test deliberately keeps the consumer bound while the provider
|
||||
* is unregistered. fw_devlink would force-unbind the consumer before
|
||||
* the provider so use the FWNODE_FLAG_LINKS_ADDED flag to opt out of
|
||||
* it as a workaround.
|
||||
*/
|
||||
cons = kunit_platform_device_alloc(test, GPIO_UNBIND_TEST_CONSUMER,
|
||||
PLATFORM_DEVID_NONE);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons);
|
||||
|
||||
ret = device_create_managed_software_node(&cons->dev, properties, NULL);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
fwnode_set_flag(dev_fwnode(&cons->dev), FWNODE_FLAG_LINKS_ADDED);
|
||||
|
||||
ret = kunit_platform_device_add(test, cons);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
wait_for_device_probe();
|
||||
scoped_guard(device, &cons->dev)
|
||||
bound = device_is_bound(&cons->dev);
|
||||
@@ -346,11 +682,196 @@ static struct kunit_case gpio_unbind_with_consumers_tests[] = {
|
||||
static struct kunit_suite gpio_unbind_with_consumers_test_suite = {
|
||||
.name = "gpio-unbind-with-consumers",
|
||||
.test_cases = gpio_unbind_with_consumers_tests,
|
||||
/* We need this here too to clean any left over links. */
|
||||
.init = gpio_swnode_probe_order_test_init,
|
||||
};
|
||||
|
||||
/*
|
||||
* GPIO line hogs are described by child software nodes of the provider
|
||||
* carrying the "gpio-hog" property. They are picked up automatically when the
|
||||
* gpiochip is registered. Each hog below sits on a distinct line of the
|
||||
* provider.
|
||||
*/
|
||||
#define GPIO_HOG_OUTPUT_HIGH_OFFSET 0
|
||||
#define GPIO_HOG_OUTPUT_LOW_OFFSET 1
|
||||
#define GPIO_HOG_INPUT_OFFSET 2
|
||||
|
||||
static const u32 gpio_hog_output_high_gpios[] = {
|
||||
GPIO_HOG_OUTPUT_HIGH_OFFSET, GPIO_ACTIVE_HIGH,
|
||||
};
|
||||
|
||||
static const struct property_entry gpio_hog_output_high_properties[] = {
|
||||
PROPERTY_ENTRY_U32_ARRAY("gpios", gpio_hog_output_high_gpios),
|
||||
PROPERTY_ENTRY_STRING("line-name", "hog-output-high"),
|
||||
PROPERTY_ENTRY_BOOL("output-high"),
|
||||
PROPERTY_ENTRY_BOOL("gpio-hog"),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node gpio_hog_output_high_swnode =
|
||||
SOFTWARE_NODE("hog-output-high", gpio_hog_output_high_properties,
|
||||
&gpio_test_provider_swnode);
|
||||
|
||||
static const u32 gpio_hog_output_low_gpios[] = {
|
||||
GPIO_HOG_OUTPUT_LOW_OFFSET, GPIO_ACTIVE_HIGH,
|
||||
};
|
||||
|
||||
static const struct property_entry gpio_hog_output_low_properties[] = {
|
||||
PROPERTY_ENTRY_U32_ARRAY("gpios", gpio_hog_output_low_gpios),
|
||||
PROPERTY_ENTRY_STRING("line-name", "hog-output-low"),
|
||||
PROPERTY_ENTRY_BOOL("output-low"),
|
||||
PROPERTY_ENTRY_BOOL("gpio-hog"),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node gpio_hog_output_low_swnode =
|
||||
SOFTWARE_NODE("hog-output-low", gpio_hog_output_low_properties,
|
||||
&gpio_test_provider_swnode);
|
||||
|
||||
static const u32 gpio_hog_input_gpios[] = {
|
||||
GPIO_HOG_INPUT_OFFSET, GPIO_ACTIVE_HIGH,
|
||||
};
|
||||
|
||||
static const struct property_entry gpio_hog_input_properties[] = {
|
||||
PROPERTY_ENTRY_U32_ARRAY("gpios", gpio_hog_input_gpios),
|
||||
PROPERTY_ENTRY_STRING("line-name", "hog-input"),
|
||||
PROPERTY_ENTRY_BOOL("input"),
|
||||
PROPERTY_ENTRY_BOOL("gpio-hog"),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node gpio_hog_input_swnode =
|
||||
SOFTWARE_NODE("hog-input", gpio_hog_input_properties,
|
||||
&gpio_test_provider_swnode);
|
||||
|
||||
static const struct software_node *const gpio_hog_swnodes[] = {
|
||||
&gpio_test_provider_swnode,
|
||||
&gpio_hog_output_high_swnode,
|
||||
&gpio_hog_output_low_swnode,
|
||||
&gpio_hog_input_swnode,
|
||||
NULL
|
||||
};
|
||||
|
||||
/*
|
||||
* Bring up the provider with a single hog child registered and verify both
|
||||
* that the line was configured with the expected direction and that it is now
|
||||
* exclusively owned (a consumer asking for the same line fails to bind).
|
||||
*
|
||||
* The provider node is referenced by the device through its fwnode rather than
|
||||
* being handed to .swnode, so the device takes no software node reference of
|
||||
* its own. Both the provider and the hog child are therefore test-managed and
|
||||
* torn down (child first) once the test case completes.
|
||||
*/
|
||||
static void gpio_hog_assert(struct kunit *test, unsigned int offset,
|
||||
int expected_direction)
|
||||
{
|
||||
struct gpio_swnode_consumer_pdata *pdata;
|
||||
struct platform_device_info pdevinfo;
|
||||
struct property_entry properties[2];
|
||||
struct platform_device *pdev;
|
||||
struct fwnode_handle *fwnode;
|
||||
struct gpio_desc *desc;
|
||||
bool bound = true;
|
||||
int ret;
|
||||
|
||||
fwnode = software_node_fwnode(&gpio_test_provider_swnode);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode);
|
||||
|
||||
pdevinfo = (struct platform_device_info){
|
||||
.name = GPIO_TEST_PROVIDER,
|
||||
.id = PLATFORM_DEVID_NONE,
|
||||
.fwnode = fwnode,
|
||||
};
|
||||
|
||||
pdev = kunit_platform_device_register_full(test, &pdevinfo);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
|
||||
|
||||
wait_for_device_probe();
|
||||
|
||||
/* The hog must have configured the line with the expected direction. */
|
||||
struct gpio_device *gdev __free(gpio_device_put) =
|
||||
gpio_device_find_by_label(GPIO_CONSUMER_NAME);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, gdev);
|
||||
|
||||
desc = gpio_device_get_desc(gdev, offset);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc);
|
||||
|
||||
ret = gpiod_get_direction(desc);
|
||||
KUNIT_ASSERT_EQ(test, ret, expected_direction);
|
||||
|
||||
/* A hogged line is owned exclusively, so a consumer must fail to bind. */
|
||||
properties[0] = PROPERTY_ENTRY_GPIO("foo-gpios",
|
||||
&gpio_test_provider_swnode,
|
||||
offset, GPIO_ACTIVE_HIGH);
|
||||
properties[1] = (struct property_entry){ };
|
||||
|
||||
pdevinfo = (struct platform_device_info){
|
||||
.name = GPIO_SWNODE_TEST_CONSUMER,
|
||||
.id = PLATFORM_DEVID_NONE,
|
||||
.data = &gpio_swnode_pdata_template,
|
||||
.size_data = sizeof(gpio_swnode_pdata_template),
|
||||
.properties = properties,
|
||||
};
|
||||
|
||||
pdev = kunit_platform_device_register_full(test, &pdevinfo);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
|
||||
|
||||
wait_for_device_probe();
|
||||
scoped_guard(device, &pdev->dev)
|
||||
bound = device_is_bound(&pdev->dev);
|
||||
|
||||
KUNIT_ASSERT_FALSE(test, bound);
|
||||
|
||||
pdata = dev_get_platdata(&pdev->dev);
|
||||
KUNIT_ASSERT_FALSE(test, pdata->gpio_ok);
|
||||
KUNIT_ASSERT_EQ(test, pdata->errno, -EBUSY);
|
||||
}
|
||||
|
||||
static void gpio_hog_output_high(struct kunit *test)
|
||||
{
|
||||
gpio_hog_assert(test, GPIO_HOG_OUTPUT_HIGH_OFFSET, GPIO_LINE_DIRECTION_OUT);
|
||||
}
|
||||
|
||||
static void gpio_hog_output_low(struct kunit *test)
|
||||
{
|
||||
gpio_hog_assert(test, GPIO_HOG_OUTPUT_LOW_OFFSET, GPIO_LINE_DIRECTION_OUT);
|
||||
}
|
||||
|
||||
static void gpio_hog_input(struct kunit *test)
|
||||
{
|
||||
gpio_hog_assert(test, GPIO_HOG_INPUT_OFFSET, GPIO_LINE_DIRECTION_IN);
|
||||
}
|
||||
|
||||
static int gpio_hog_suite_init(struct kunit_suite *suite)
|
||||
{
|
||||
return software_node_register_node_group(gpio_hog_swnodes);
|
||||
}
|
||||
|
||||
static void gpio_hog_suite_exit(struct kunit_suite *suite)
|
||||
{
|
||||
software_node_unregister_node_group(gpio_hog_swnodes);
|
||||
}
|
||||
|
||||
static struct kunit_case gpio_swnode_hog_tests[] = {
|
||||
KUNIT_CASE(gpio_hog_output_high),
|
||||
KUNIT_CASE(gpio_hog_output_low),
|
||||
KUNIT_CASE(gpio_hog_input),
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct kunit_suite gpio_swnode_hog_test_suite = {
|
||||
.name = "gpio-swnode-hog",
|
||||
.test_cases = gpio_swnode_hog_tests,
|
||||
.suite_init = gpio_hog_suite_init,
|
||||
.suite_exit = gpio_hog_suite_exit,
|
||||
.init = gpio_swnode_register_drivers,
|
||||
};
|
||||
|
||||
kunit_test_suites(
|
||||
&gpio_swnode_lookup_test_suite,
|
||||
&gpio_swnode_probe_order_test_suite,
|
||||
&gpio_unbind_with_consumers_test_suite,
|
||||
&gpio_swnode_hog_test_suite,
|
||||
);
|
||||
|
||||
MODULE_DESCRIPTION("Test module for the GPIO subsystem");
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
static struct gpio_device *swnode_get_gpio_device(struct fwnode_handle *fwnode)
|
||||
{
|
||||
const struct software_node *gdev_node;
|
||||
struct gpio_device *gdev;
|
||||
|
||||
gdev_node = to_software_node(fwnode);
|
||||
if (!gdev_node)
|
||||
@@ -41,27 +40,7 @@ static struct gpio_device *swnode_get_gpio_device(struct fwnode_handle *fwnode)
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
fwnode_lookup:
|
||||
gdev = gpio_device_find_by_fwnode(fwnode);
|
||||
if (!gdev && gdev_node && gdev_node->name)
|
||||
/*
|
||||
* FIXME: We shouldn't need to compare the GPIO controller's
|
||||
* label against the software node that is supposedly attached
|
||||
* to it. However there are currently GPIO users that - knowing
|
||||
* the expected label of the GPIO chip whose pins they want to
|
||||
* control - set up dummy software nodes named after those GPIO
|
||||
* controllers, which aren't actually attached to them. In this
|
||||
* case gpio_device_find_by_fwnode() will fail as no device on
|
||||
* the GPIO bus is actually associated with the fwnode we're
|
||||
* looking for.
|
||||
*
|
||||
* As a fallback: continue checking the label if we have no
|
||||
* match. However, the situation described above is an abuse
|
||||
* of the software node API and should be phased out and the
|
||||
* following line - eventually removed.
|
||||
*/
|
||||
gdev = gpio_device_find_by_label(gdev_node->name);
|
||||
|
||||
return gdev ?: ERR_PTR(-EPROBE_DEFER);
|
||||
return gpio_device_find_by_fwnode(fwnode) ?: ERR_PTR(-EPROBE_DEFER);
|
||||
}
|
||||
|
||||
static int swnode_gpio_get_reference(const struct fwnode_handle *fwnode,
|
||||
|
||||
@@ -560,8 +560,8 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ad7173_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
|
||||
unsigned int offset, unsigned int *reg,
|
||||
static int ad7173_mask_xlate(struct gpio_regmap *gpio, enum gpio_regmap_operation op,
|
||||
unsigned int base, unsigned int offset, unsigned int *reg,
|
||||
unsigned int *mask)
|
||||
{
|
||||
*mask = AD7173_GPO_DATA(offset);
|
||||
@@ -569,8 +569,8 @@ static int ad7173_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ad4111_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
|
||||
unsigned int offset, unsigned int *reg,
|
||||
static int ad4111_mask_xlate(struct gpio_regmap *gpio, enum gpio_regmap_operation op,
|
||||
unsigned int base, unsigned int offset, unsigned int *reg,
|
||||
unsigned int *mask)
|
||||
{
|
||||
*mask = AD4111_GPO01_DATA(offset);
|
||||
|
||||
@@ -349,9 +349,9 @@ static const struct iio_chan_spec stx104_channels_diff[] = {
|
||||
STX104_IN_CHAN(6, 1), STX104_IN_CHAN(7, 1)
|
||||
};
|
||||
|
||||
static int stx104_reg_mask_xlate(struct gpio_regmap *const gpio, const unsigned int base,
|
||||
unsigned int offset, unsigned int *const reg,
|
||||
unsigned int *const mask)
|
||||
static int stx104_reg_mask_xlate(struct gpio_regmap *const gpio, enum gpio_regmap_operation op,
|
||||
const unsigned int base, unsigned int offset,
|
||||
unsigned int *const reg, unsigned int *const mask)
|
||||
{
|
||||
/* Output lines are located at same register bit offsets as input lines */
|
||||
if (offset >= 4)
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
#include <linux/input.h>
|
||||
#include <linux/gpio_keys.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/spinlock.h>
|
||||
@@ -528,6 +528,7 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
|
||||
*/
|
||||
bdata->gpiod = NULL;
|
||||
}
|
||||
#ifdef CONFIG_GPIOLIB_LEGACY
|
||||
} else if (gpio_is_valid(button->gpio)) {
|
||||
/*
|
||||
* Legacy GPIO number, so request the GPIO here and
|
||||
@@ -546,6 +547,7 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
|
||||
|
||||
if (button->active_low ^ gpiod_is_active_low(bdata->gpiod))
|
||||
gpiod_toggle_active_low(bdata->gpiod);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (bdata->gpiod) {
|
||||
@@ -583,8 +585,7 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
|
||||
if (irq < 0) {
|
||||
error = irq;
|
||||
dev_err_probe(dev, error,
|
||||
"Unable to get irq number for GPIO %d\n",
|
||||
button->gpio);
|
||||
"Unable to get irq number for GPIO\n");
|
||||
return error;
|
||||
}
|
||||
bdata->irq = irq;
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
#include <linux/input.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/gpio_keys.h>
|
||||
#include <linux/property.h>
|
||||
|
||||
@@ -301,6 +301,7 @@ static int gpio_keys_polled_probe(struct platform_device *pdev)
|
||||
return dev_err_probe(dev, PTR_ERR(bdata->gpiod),
|
||||
"failed to get gpio\n");
|
||||
}
|
||||
#ifdef CONFIG_GPIOLIB_LEGACY
|
||||
} else if (gpio_is_valid(button->gpio)) {
|
||||
/*
|
||||
* Legacy GPIO number so request the GPIO here and
|
||||
@@ -323,6 +324,7 @@ static int gpio_keys_polled_probe(struct platform_device *pdev)
|
||||
|
||||
if (button->active_low ^ gpiod_is_active_low(bdata->gpiod))
|
||||
gpiod_toggle_active_low(bdata->gpiod);
|
||||
#endif
|
||||
}
|
||||
|
||||
bdata->last_state = -1;
|
||||
|
||||
@@ -904,6 +904,9 @@ config INPUT_IDEAPAD_SLIDEBAR
|
||||
config INPUT_SOC_BUTTON_ARRAY
|
||||
tristate "Windows-compatible SoC Button Array"
|
||||
depends on KEYBOARD_GPIO && ACPI
|
||||
depends on X86
|
||||
depends on GPIOLIB
|
||||
select GPIOLIB_LEGACY
|
||||
help
|
||||
Say Y here if you have a SoC-based tablet that originally runs
|
||||
Windows 8 or a Microsoft Surface Book 2, Pro 5, Laptop 1 or later.
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio_keys.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
static bool use_low_level_irq;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/aat2870.h>
|
||||
#include <linux/regulator/machine.h>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/lp3943.h>
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ struct sm501_device {
|
||||
struct sm501_gpio;
|
||||
|
||||
#ifdef CONFIG_MFD_SM501_GPIO
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
|
||||
struct sm501_gpio_chip {
|
||||
struct gpio_chip gpio;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#include <linux/init.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <linux/err.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/mfd/tps65910.h>
|
||||
|
||||
#define COMP1 0
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/mfd/core.h>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
|
||||
#include <bcm63xx_regs.h>
|
||||
#include <bcm63xx_io.h>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
|
||||
#include <mach/hardware.h>
|
||||
#include <asm/irq.h>
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
|
||||
|
||||
#include <linux/cpufreq.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/ioport.h>
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/extcon-provider.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <linux/bits.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#define BCM63XX_DATA_REG 0x0c
|
||||
|
||||
static int bcm63xx_reg_mask_xlate(struct gpio_regmap *gpio,
|
||||
enum gpio_regmap_operation op,
|
||||
unsigned int base, unsigned int offset,
|
||||
unsigned int *reg, unsigned int *mask)
|
||||
{
|
||||
|
||||
@@ -346,6 +346,7 @@ static struct tps6594_pinctrl tps6594_template_pinctrl = {
|
||||
};
|
||||
|
||||
static int tps6594_gpio_regmap_xlate(struct gpio_regmap *gpio,
|
||||
enum gpio_regmap_operation op,
|
||||
unsigned int base, unsigned int offset,
|
||||
unsigned int *reg, unsigned int *mask)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* KUnit resource management helpers for firmware nodes.
|
||||
*
|
||||
* Copyright (C) Qualcomm Technologies, Inc. and/or its subsidiaries
|
||||
*/
|
||||
|
||||
#ifndef _KUNIT_FWNODE_H
|
||||
#define _KUNIT_FWNODE_H
|
||||
|
||||
struct device;
|
||||
struct fwnode_handle;
|
||||
struct kunit;
|
||||
struct property_entry;
|
||||
struct software_node;
|
||||
|
||||
struct fwnode_handle *
|
||||
kunit_fwnode_create_software_node(struct kunit *test,
|
||||
const struct property_entry *properties,
|
||||
const struct fwnode_handle *parent);
|
||||
struct fwnode_handle *
|
||||
kunit_software_node_register(struct kunit *test,
|
||||
const struct software_node *node);
|
||||
int kunit_software_node_register_node_group(struct kunit *test,
|
||||
const struct software_node *const *nodes);
|
||||
int kunit_device_add_software_node(struct kunit *test, struct device *dev,
|
||||
const struct software_node *node);
|
||||
|
||||
#endif /* _KUNIT_FWNODE_H */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user