mirror of
https://github.com/torvalds/linux.git
synced 2024-11-21 11:35:45 +00:00
gpio updates for v6.13-rc1
GPIOLIB core: - use the new mem_is_zero() instead of memchr_inv(s, 0, n) - don't store debounce period twice needlessly - clean-up debugfs handling - remove leftover comments referring to no longer used spinlocks - unduplicate some operations like SRCU locks and initializing GPIO descriptors - constify the sysfs class struct - use lock guards in GPIO sysfs code - update GPIO uAPI internal flags all at once atomically for consistency with other places - modify the behavior of the sysfs interface by no longer exporting lines that are named inside the driver code or board files with the sysfs links bearing the line names as this has for many years been largely unused due to the prevalence of DT, ACPI and firmware nodes over board files and made the API inconsistent - for GPIO interrupt providers: free irqs that are still requested by users when removing the chip GPIO uAPI: - notify user-space about changes to GPIO lines' state (requested, released, reconfigured) triggered from the kernel as well (until now we'd only do this for changes triggered from user-space) - to that end: modify the internal workings of the notification mechanism by switching to an atomic notifier which allows us to send events from atomic context - also to that end store the debounce period in the GPIO descriptor struct and not in the character device context struct - while at it, also cover the corner-case of users introducing changes over sysfs while others watch them via the character device - don't report GPIO lines requested as interrupts as "used" to user-space as it can still request them as GPIOs New drivers: - add a driver for the GPIO functionality of the MFD Congatec Board Controller - add a driver for the PolarFire GPIO controller - add a driver supporting the GPIOs on FTDI FT2232H Driver improvements: - use generic device property accessors instead of OF-specific ones across many GPIO drivers (mpc8xxx, vf610, eic-sprd, davinci, ts4900, xilinx, mvebu) - use devres helpers to simplify error paths and either shrink or entirely remove the driver's remove() callback (grgpio, amdpt, menz127, max730x, ftgpio010, 74x164, ljca) - use helper variables to store the address of pdev->dev and avoid some line-breaks - use device_for_each_child_node_scoped() to avoid having to put the fwnode on breaks or errors (gpio-sim, gpio-dwapb, gpiolib-acpi) - use a scoped bitmap to simplify the code and drop goto labels in gpio-aggregator - drop unneeded Kconfig dependencies on OF_GPIO (grgpio, mveby, xilinx) - add support for new models to gpio-aspeed, gpio-rockchip and gpio-dwapb - clean-up ACPI handling and some other bits in gpio-xgene-sb - replace deprecated PCI functions in pcie-idio-24 and pci-idio-16 - allow to build davinci and mvebu drivers with COMPILE_TEST=y - remove dead code in gpio-mb86s7x - switch back to using platform_driver::remove() (after the conversion to remove_new()) across the GPIO drivers - remove remaining uses of GPIOF_ACTIVE_LOW across the tree and drop this deprecated symbol - convert the gpio-altera driver to no longer pull in the deprecated legacy-of-mm-gpiochip.h header - use of_property_present() instead of of_property_read_bool() in gpiolib-of and gpio-rockchip - allow to build the tegra186 driver on Tegra234 platforms in Kconfig Late fixes: - add a missing return value check after devm_kasprintf() to gpio-grgpio DT bindings: - document the ngpios property of gpio-mmio - add support for a new aspeed model - fix the example for st,nomadik-gpio Other: - kernel doc and comments tweaks - fix typos in TODO - reorder headers alphabetically in some drivers - fix incorrect format specifiers in gpio tools -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEFp3rbAvDxGAT0sefEacuoBRx13IFAmc7INQACgkQEacuoBRx 13Kotg/8DDkhZ01Qc2uR817DjCdbalS0TDr4FYn/XusyeBEgNgQDm/TcfmIMiU4M Dazb7pOXfEc4VJqPTSpvqsTwTyMLN1pi58ZXZZA760rf8O8O+8c/cogVk36QK+IB 3LX/w7JFFME6cC3kZ+mV3a6Cdxb4UIQdv1rWtLX8MjCiJ5+ax33VS0pe67n8sOft A6LeYwFr1ngk9NDg+OrFzExMTCGqk4aUYWiLd8ki3bw5ZMYzLwuenWLONQo3HOP5 QuWQV+wF913mjB53omiZ8heJ7hN3ez071W4rXXPGXn5sAGipqJKWk6Nvx1wRZapD d+6XozBLGNEIcPvWJAWDfxnHmEjVpFyhavc9Id569DcQ0WVLm+CFPkNmp4mgOoOB k8f6R2CA2lheIKTRUk/Lt+Cu2+Za/07bM/WsxL6x/oCXZwL6NA5P+8VoNcBCDKrF PVUz7Jad6FVHkxySqwX4clHLW173pqyG47RJ7KxVv7g6g+YNW0WCM7crS+czRWIq KUT9sumw1dEFlYZ9IGZTuP/Pvur8f17powkS8VuYJ02N1yq3OhQZb52NX8stsmkW P6CsdZe9KRoGQUJp9nYVE5j09oz1gqqdbLdn4Byqg6G50w2U1mLNaWE1rtWLtcTx h7npUNOaX44JafnClFJvv1R9ovkuzgTaymEL4qo3QB6COHdvjlA= =ON7q -----END PGP SIGNATURE----- Merge tag 'gpio-updates-for-v6.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux Pull gpio updates from Bartosz Golaszewski: "Three new drivers, support for some new models in existing ones and lots of various tweaks and improvements across the board (switching to using recommended APIs, code shrink and simplification, etc.). Also a new feature in the character device uAPI where we now notify the user-space about changes triggered by in-kernel users as well, not only when they were done by other user-space agents. Summary: GPIOLIB core: - use the new mem_is_zero() instead of memchr_inv(s, 0, n) - don't store debounce period twice needlessly - clean-up debugfs handling - remove leftover comments referring to no longer used spinlocks - unduplicate some operations like SRCU locks and initializing GPIO descriptors - constify the sysfs class struct - use lock guards in GPIO sysfs code - update GPIO uAPI internal flags all at once atomically for consistency with other places - modify the behavior of the sysfs interface by no longer exporting lines that are named inside the driver code or board files with the sysfs links bearing the line names as this has for many years been largely unused due to the prevalence of DT, ACPI and firmware nodes over board files and made the API inconsistent - for GPIO interrupt providers: free irqs that are still requested by users when removing the chip GPIO uAPI: - notify user-space about changes to GPIO lines' state (requested, released, reconfigured) triggered from the kernel as well (until now we'd only do this for changes triggered from user-space) - to that end: modify the internal workings of the notification mechanism by switching to an atomic notifier which allows us to send events from atomic context - also to that end store the debounce period in the GPIO descriptor struct and not in the character device context struct - while at it, also cover the corner-case of users introducing changes over sysfs while others watch them via the character device - don't report GPIO lines requested as interrupts as "used" to user-space as it can still request them as GPIOs New drivers: - GPIO part of the MFD Congatec Board Controller - PolarFire GPIO controller - GPIOs on FTDI FT2232H Driver improvements: - use generic device property accessors instead of OF-specific ones across many GPIO drivers (mpc8xxx, vf610, eic-sprd, davinci, ts4900, xilinx, mvebu) - use devres helpers to simplify error paths and either shrink or entirely remove the driver's remove() callback (grgpio, amdpt, menz127, max730x, ftgpio010, 74x164, ljca) - use helper variables to store the address of pdev->dev and avoid some line-breaks - use device_for_each_child_node_scoped() to avoid having to put the fwnode on breaks or errors (gpio-sim, gpio-dwapb, gpiolib-acpi) - use a scoped bitmap to simplify the code and drop goto labels in gpio-aggregator - drop unneeded Kconfig dependencies on OF_GPIO (grgpio, mveby, xilinx) - add support for new models to gpio-aspeed, gpio-rockchip and gpio-dwapb - clean-up ACPI handling and some other bits in gpio-xgene-sb - replace deprecated PCI functions in pcie-idio-24 and pci-idio-16 - allow to build davinci and mvebu drivers with COMPILE_TEST=y - remove dead code in gpio-mb86s7x - switch back to using platform_driver::remove() (after the conversion to remove_new()) across the GPIO drivers - remove remaining uses of GPIOF_ACTIVE_LOW across the tree and drop this deprecated symbol - convert the gpio-altera driver to no longer pull in the deprecated legacy-of-mm-gpiochip.h header - use of_property_present() instead of of_property_read_bool() in gpiolib-of and gpio-rockchip - allow to build the tegra186 driver on Tegra234 platforms in Kconfig Late fixes: - add a missing return value check after devm_kasprintf() to gpio-grgpio DT bindings: - document the ngpios property of gpio-mmio - add support for a new aspeed model - fix the example for st,nomadik-gpio Other: - kernel doc and comments tweaks - fix typos in TODO - reorder headers alphabetically in some drivers - fix incorrect format specifiers in gpio tools" * tag 'gpio-updates-for-v6.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux: (98 commits) gpio: tegra186: Allow to enable driver on Tegra234 gpio: grgpio: Add NULL check in grgpio_probe tools: gpio: Fix several incorrect format specifiers gpio: mpfs: add CoreGPIO support gpio: rockchip: support new version GPIO gpio: rockchip: change the GPIO version judgment logic gpio: rockchip: explan the format of the GPIO version ID gpiolib: cdev: use !mem_is_zero() instead of memchr_inv(s, 0, n) MAINTAINERS: add gpio driver to PolarFire entry gpio: Get rid of GPIOF_ACTIVE_LOW USB: gadget: pxa27x_udc: Avoid using GPIOF_ACTIVE_LOW pcmcia: soc_common: Avoid using GPIOF_ACTIVE_LOW leds: gpio: Avoid using GPIOF_ACTIVE_LOW Input: gpio_keys_polled - avoid using GPIOF_ACTIVE_LOW Input: gpio_keys - avoid using GPIOF_ACTIVE_LOW gpio: Use of_property_present() for non-boolean properties gpio: mpfs: add polarfire soc gpio support gpio: altera: Drop legacy-of-mm-gpiochip.h header gpio: pcie-idio-24: Replace deprecated PCI functions gpio: pci-idio-16: Replace deprecated PCI functions ...
This commit is contained in:
commit
131561f2ca
@ -15,6 +15,7 @@ properties:
|
||||
- aspeed,ast2400-gpio
|
||||
- aspeed,ast2500-gpio
|
||||
- aspeed,ast2600-gpio
|
||||
- aspeed,ast2700-gpio
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
@ -25,7 +26,7 @@ properties:
|
||||
|
||||
gpio-controller: true
|
||||
gpio-line-names:
|
||||
minItems: 36
|
||||
minItems: 12
|
||||
maxItems: 232
|
||||
|
||||
gpio-ranges: true
|
||||
@ -42,7 +43,7 @@ properties:
|
||||
const: 2
|
||||
|
||||
ngpios:
|
||||
minimum: 36
|
||||
minimum: 12
|
||||
maximum: 232
|
||||
|
||||
required:
|
||||
@ -93,6 +94,20 @@ allOf:
|
||||
enum: [ 36, 208 ]
|
||||
required:
|
||||
- ngpios
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: aspeed,ast2700-gpio
|
||||
then:
|
||||
properties:
|
||||
gpio-line-names:
|
||||
minItems: 12
|
||||
maxItems: 216
|
||||
ngpios:
|
||||
enum: [ 12, 216 ]
|
||||
required:
|
||||
- ngpios
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
|
@ -37,7 +37,8 @@ properties:
|
||||
description:
|
||||
A list of registers in the controller. The width of each register is
|
||||
determined by its size. All registers must have the same width. The number
|
||||
of GPIOs is set by the width, with bit 0 corresponding to GPIO 0.
|
||||
of GPIOs is set by the width, with bit 0 corresponding to GPIO 0, unless
|
||||
the ngpios property further restricts the number of used lines.
|
||||
items:
|
||||
- description:
|
||||
Register to READ the value of the GPIO lines. If GPIO line is high,
|
||||
@ -74,6 +75,15 @@ properties:
|
||||
|
||||
native-endian: true
|
||||
|
||||
ngpios:
|
||||
minimum: 1
|
||||
maximum: 63
|
||||
description:
|
||||
If this property is present the number of usable GPIO lines are restricted
|
||||
to the first 0 .. ngpios lines. This is useful when the GPIO MMIO register
|
||||
has 32 bits for GPIO but only the first 12 are actually connected to
|
||||
real electronics, and then we set ngpios to 12.
|
||||
|
||||
no-output:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
description:
|
||||
@ -111,6 +121,7 @@ examples:
|
||||
compatible = "brcm,bcm6345-gpio";
|
||||
reg-names = "dirout", "dat";
|
||||
reg = <0xfffe0406 2>, <0xfffe040a 2>;
|
||||
ngpios = <15>;
|
||||
native-endian;
|
||||
gpio-controller;
|
||||
#gpio-cells = <2>;
|
||||
|
@ -89,6 +89,7 @@ examples:
|
||||
interrupts = <0 120 0x4>;
|
||||
#gpio-cells = <2>;
|
||||
gpio-controller;
|
||||
#interrupt-cells = <2>;
|
||||
interrupt-controller;
|
||||
st,supports-sleepmode;
|
||||
gpio-bank = <1>;
|
||||
|
10
MAINTAINERS
10
MAINTAINERS
@ -5741,6 +5741,15 @@ F: fs/configfs/
|
||||
F: include/linux/configfs.h
|
||||
F: samples/configfs/
|
||||
|
||||
CONGATEC BOARD CONTROLLER MFD DRIVER
|
||||
M: Thomas Richard <thomas.richard@bootlin.com>
|
||||
S: Maintained
|
||||
F: drivers/gpio/gpio-cgbc.c
|
||||
F: drivers/i2c/busses/i2c-cgbc.c
|
||||
F: drivers/mfd/cgbc-core.c
|
||||
F: drivers/watchdog/cgbc_wdt.c
|
||||
F: include/linux/mfd/cgbc.h
|
||||
|
||||
CONSOLE SUBSYSTEM
|
||||
M: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
||||
S: Supported
|
||||
@ -19879,6 +19888,7 @@ F: arch/riscv/boot/dts/microchip/
|
||||
F: drivers/char/hw_random/mpfs-rng.c
|
||||
F: drivers/clk/microchip/clk-mpfs*.c
|
||||
F: drivers/firmware/microchip/mpfs-auto-update.c
|
||||
F: drivers/gpio/gpio-mpfs.c
|
||||
F: drivers/i2c/busses/i2c-microchip-corei2c.c
|
||||
F: drivers/mailbox/mailbox-mpfs.c
|
||||
F: drivers/pci/controller/plda/pcie-microchip-host.c
|
||||
|
@ -70,8 +70,7 @@ config GPIO_SYSFS
|
||||
ioctl() operations instead.
|
||||
|
||||
config GPIO_CDEV
|
||||
bool
|
||||
prompt "Character device (/dev/gpiochipN) support" if EXPERT
|
||||
bool "Character device (/dev/gpiochipN) support" if EXPERT
|
||||
default y
|
||||
help
|
||||
Say Y here to add the character device /dev/gpiochipN interface
|
||||
@ -149,9 +148,7 @@ config GPIO_74XX_MMIO
|
||||
|
||||
config GPIO_ALTERA
|
||||
tristate "Altera GPIO"
|
||||
depends on OF_GPIO
|
||||
select GPIOLIB_IRQCHIP
|
||||
select OF_GPIO_MM_GPIOCHIP
|
||||
help
|
||||
Say Y or M here to build support for the Altera PIO device.
|
||||
|
||||
@ -243,7 +240,7 @@ config GPIO_CLPS711X
|
||||
config GPIO_DAVINCI
|
||||
tristate "TI Davinci/Keystone GPIO support"
|
||||
default y if ARCH_DAVINCI
|
||||
depends on (ARM || ARM64) && (ARCH_DAVINCI || ARCH_KEYSTONE || ARCH_K3)
|
||||
depends on ((ARM || ARM64) && (ARCH_DAVINCI || ARCH_KEYSTONE || ARCH_K3)) || COMPILE_TEST
|
||||
help
|
||||
Say yes here to enable GPIO support for TI Davinci/Keystone SoCs.
|
||||
|
||||
@ -341,7 +338,6 @@ config GPIO_GRANITERAPIDS
|
||||
|
||||
config GPIO_GRGPIO
|
||||
tristate "Aeroflex Gaisler GRGPIO support"
|
||||
depends on OF_GPIO
|
||||
select GPIO_GENERIC
|
||||
select IRQ_DOMAIN
|
||||
help
|
||||
@ -487,8 +483,7 @@ config GPIO_MT7621
|
||||
|
||||
config GPIO_MVEBU
|
||||
def_bool y
|
||||
depends on PLAT_ORION || ARCH_MVEBU
|
||||
depends on OF_GPIO
|
||||
depends on PLAT_ORION || ARCH_MVEBU || COMPILE_TEST
|
||||
select GENERIC_IRQ_CHIP
|
||||
select REGMAP_MMIO
|
||||
|
||||
@ -549,6 +544,12 @@ config GPIO_PL061
|
||||
help
|
||||
Say yes here to support the PrimeCell PL061 GPIO device.
|
||||
|
||||
config GPIO_POLARFIRE_SOC
|
||||
bool "Microchip FPGA GPIO support"
|
||||
select REGMAP_MMIO
|
||||
help
|
||||
Say yes here to support the GPIO controllers on Microchip FPGAs.
|
||||
|
||||
config GPIO_PXA
|
||||
bool "PXA GPIO support"
|
||||
depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST
|
||||
@ -714,13 +715,13 @@ config GPIO_TEGRA
|
||||
|
||||
config GPIO_TEGRA186
|
||||
tristate "NVIDIA Tegra186 GPIO support"
|
||||
default ARCH_TEGRA_186_SOC || ARCH_TEGRA_194_SOC
|
||||
depends on ARCH_TEGRA_186_SOC || ARCH_TEGRA_194_SOC || COMPILE_TEST
|
||||
default ARCH_TEGRA_186_SOC || ARCH_TEGRA_194_SOC || ARCH_TEGRA_234_SOC
|
||||
depends on ARCH_TEGRA_186_SOC || ARCH_TEGRA_194_SOC || ARCH_TEGRA_234_SOC || COMPILE_TEST
|
||||
depends on OF_GPIO
|
||||
select GPIOLIB_IRQCHIP
|
||||
select IRQ_DOMAIN_HIERARCHY
|
||||
help
|
||||
Say yes here to support GPIO pins on NVIDIA Tegra186 SoCs.
|
||||
Say yes here to support GPIO pins on NVIDIA Tegra186, 194 and 234 SoCs.
|
||||
|
||||
config GPIO_TS4800
|
||||
tristate "TS-4800 DIO blocks and compatibles"
|
||||
@ -796,7 +797,6 @@ config GPIO_XGENE_SB
|
||||
config GPIO_XILINX
|
||||
tristate "Xilinx GPIO support"
|
||||
select GPIOLIB_IRQCHIP
|
||||
depends on OF_GPIO
|
||||
help
|
||||
Say yes here to support the Xilinx FPGA GPIO device.
|
||||
|
||||
@ -1287,6 +1287,16 @@ config GPIO_BD9571MWV
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called gpio-bd9571mwv.
|
||||
|
||||
config GPIO_CGBC
|
||||
tristate "Congatec Board Controller GPIO support"
|
||||
depends on MFD_CGBC
|
||||
help
|
||||
Select this option to enable GPIO support for the Congatec Board
|
||||
Controller.
|
||||
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called gpio-cgbc.
|
||||
|
||||
config GPIO_CROS_EC
|
||||
tristate "ChromeOS EC GPIO support"
|
||||
depends on CROS_EC
|
||||
@ -1844,6 +1854,13 @@ config GPIO_VIPERBOARD
|
||||
River Tech's viperboard.h for detailed meaning
|
||||
of the module parameters.
|
||||
|
||||
config GPIO_MPSSE
|
||||
tristate "FTDI MPSSE GPIO support"
|
||||
select GPIOLIB_IRQCHIP
|
||||
help
|
||||
GPIO driver for FTDI's MPSSE interface. These can do input and
|
||||
output. Each MPSSE provides 16 IO pins.
|
||||
|
||||
endmenu
|
||||
|
||||
menu "Virtual GPIO drivers"
|
||||
|
@ -45,6 +45,7 @@ obj-$(CONFIG_GPIO_BD9571MWV) += gpio-bd9571mwv.o
|
||||
obj-$(CONFIG_GPIO_BRCMSTB) += gpio-brcmstb.o
|
||||
obj-$(CONFIG_GPIO_BT8XX) += gpio-bt8xx.o
|
||||
obj-$(CONFIG_GPIO_CADENCE) += gpio-cadence.o
|
||||
obj-$(CONFIG_GPIO_CGBC) += gpio-cgbc.o
|
||||
obj-$(CONFIG_GPIO_CLPS711X) += gpio-clps711x.o
|
||||
obj-$(CONFIG_GPIO_SNPS_CREG) += gpio-creg-snps.o
|
||||
obj-$(CONFIG_GPIO_CROS_EC) += gpio-cros-ec.o
|
||||
@ -114,6 +115,7 @@ obj-$(CONFIG_GPIO_MOCKUP) += gpio-mockup.o
|
||||
obj-$(CONFIG_GPIO_MOXTET) += gpio-moxtet.o
|
||||
obj-$(CONFIG_GPIO_MPC5200) += gpio-mpc5200.o
|
||||
obj-$(CONFIG_GPIO_MPC8XXX) += gpio-mpc8xxx.o
|
||||
obj-$(CONFIG_GPIO_MPSSE) += gpio-mpsse.o
|
||||
obj-$(CONFIG_GPIO_MSC313) += gpio-msc313.o
|
||||
obj-$(CONFIG_GPIO_MT7621) += gpio-mt7621.o
|
||||
obj-$(CONFIG_GPIO_MVEBU) += gpio-mvebu.o
|
||||
@ -133,6 +135,7 @@ obj-$(CONFIG_GPIO_PCI_IDIO_16) += gpio-pci-idio-16.o
|
||||
obj-$(CONFIG_GPIO_PISOSR) += gpio-pisosr.o
|
||||
obj-$(CONFIG_GPIO_PL061) += gpio-pl061.o
|
||||
obj-$(CONFIG_GPIO_PMIC_EIC_SPRD) += gpio-pmic-eic-sprd.o
|
||||
obj-$(CONFIG_GPIO_POLARFIRE_SOC) += gpio-mpfs.o
|
||||
obj-$(CONFIG_GPIO_PXA) += gpio-pxa.o
|
||||
obj-$(CONFIG_GPIO_RASPBERRYPI_EXP) += gpio-raspberrypi-exp.o
|
||||
obj-$(CONFIG_GPIO_RC5T583) += gpio-rc5t583.o
|
||||
|
@ -61,8 +61,8 @@ Work items:
|
||||
|
||||
- Change all consumer drivers that #include <linux/of_gpio.h> to
|
||||
#include <linux/gpio/consumer.h> and stop doing custom parsing of the
|
||||
GPIO lines from the device tree. This can be tricky and often ivolves
|
||||
changing boardfiles, etc.
|
||||
GPIO lines from the device tree. This can be tricky and often involves
|
||||
changing board files, etc.
|
||||
|
||||
- Pull semantics for legacy device tree (OF) GPIO lookups into
|
||||
gpiolib-of.c: in some cases subsystems are doing custom flags and
|
||||
|
@ -143,24 +143,17 @@ static int gen_74x164_probe(struct spi_device *spi)
|
||||
chip->gpio_chip.parent = &spi->dev;
|
||||
chip->gpio_chip.owner = THIS_MODULE;
|
||||
|
||||
mutex_init(&chip->lock);
|
||||
ret = devm_mutex_init(&spi->dev, &chip->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = __gen_74x164_write_config(chip);
|
||||
if (ret) {
|
||||
dev_err(&spi->dev, "Failed writing: %d\n", ret);
|
||||
goto exit_destroy;
|
||||
}
|
||||
if (ret)
|
||||
return dev_err_probe(&spi->dev, ret, "Config write failed\n");
|
||||
|
||||
gpiod_set_value_cansleep(chip->gpiod_oe, 1);
|
||||
|
||||
ret = gpiochip_add_data(&chip->gpio_chip, chip);
|
||||
if (!ret)
|
||||
return 0;
|
||||
|
||||
exit_destroy:
|
||||
mutex_destroy(&chip->lock);
|
||||
|
||||
return ret;
|
||||
return devm_gpiochip_add_data(&spi->dev, &chip->gpio_chip, chip);
|
||||
}
|
||||
|
||||
static void gen_74x164_remove(struct spi_device *spi)
|
||||
@ -168,8 +161,6 @@ static void gen_74x164_remove(struct spi_device *spi)
|
||||
struct gen_74x164_chip *chip = spi_get_drvdata(spi);
|
||||
|
||||
gpiod_set_value_cansleep(chip->gpiod_oe, 0);
|
||||
gpiochip_remove(&chip->gpio_chip);
|
||||
mutex_destroy(&chip->lock);
|
||||
}
|
||||
|
||||
static const struct spi_device_id gen_74x164_spi_ids[] = {
|
||||
|
@ -65,11 +65,11 @@ static int aggr_parse(struct gpio_aggregator *aggr)
|
||||
{
|
||||
char *args = skip_spaces(aggr->args);
|
||||
char *name, *offsets, *p;
|
||||
unsigned long *bitmap;
|
||||
unsigned int i, n = 0;
|
||||
int error = 0;
|
||||
|
||||
bitmap = bitmap_alloc(AGGREGATOR_MAX_GPIOS, GFP_KERNEL);
|
||||
unsigned long *bitmap __free(bitmap) =
|
||||
bitmap_alloc(AGGREGATOR_MAX_GPIOS, GFP_KERNEL);
|
||||
if (!bitmap)
|
||||
return -ENOMEM;
|
||||
|
||||
@ -82,7 +82,7 @@ static int aggr_parse(struct gpio_aggregator *aggr)
|
||||
/* Named GPIO line */
|
||||
error = aggr_add_gpio(aggr, name, U16_MAX, &n);
|
||||
if (error)
|
||||
goto free_bitmap;
|
||||
return error;
|
||||
|
||||
name = offsets;
|
||||
continue;
|
||||
@ -92,13 +92,13 @@ static int aggr_parse(struct gpio_aggregator *aggr)
|
||||
error = bitmap_parselist(offsets, bitmap, AGGREGATOR_MAX_GPIOS);
|
||||
if (error) {
|
||||
pr_err("Cannot parse %s: %d\n", offsets, error);
|
||||
goto free_bitmap;
|
||||
return error;
|
||||
}
|
||||
|
||||
for_each_set_bit(i, bitmap, AGGREGATOR_MAX_GPIOS) {
|
||||
error = aggr_add_gpio(aggr, name, i, &n);
|
||||
if (error)
|
||||
goto free_bitmap;
|
||||
return error;
|
||||
}
|
||||
|
||||
args = next_arg(args, &name, &p);
|
||||
@ -106,12 +106,10 @@ static int aggr_parse(struct gpio_aggregator *aggr)
|
||||
|
||||
if (!n) {
|
||||
pr_err("No GPIOs specified\n");
|
||||
error = -EINVAL;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
free_bitmap:
|
||||
bitmap_free(bitmap);
|
||||
return error;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t new_device_store(struct device_driver *driver, const char *buf,
|
||||
|
@ -4,11 +4,19 @@
|
||||
* Based on gpio-mpc8xxx.c
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/gpio/legacy-of-mm-gpiochip.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <linux/gpio/driver.h>
|
||||
|
||||
#define ALTERA_GPIO_MAX_NGPIO 32
|
||||
#define ALTERA_GPIO_DATA 0x0
|
||||
@ -18,7 +26,8 @@
|
||||
|
||||
/**
|
||||
* struct altera_gpio_chip
|
||||
* @mmchip : memory mapped chip structure.
|
||||
* @gc : GPIO chip structure.
|
||||
* @regs : memory mapped IO address for the controller registers.
|
||||
* @gpio_lock : synchronization lock so that new irq/set/get requests
|
||||
* will be blocked until the current one completes.
|
||||
* @interrupt_trigger : specifies the hardware configured IRQ trigger type
|
||||
@ -26,7 +35,8 @@
|
||||
* @mapped_irq : kernel mapped irq number.
|
||||
*/
|
||||
struct altera_gpio_chip {
|
||||
struct of_mm_gpio_chip mmchip;
|
||||
struct gpio_chip gc;
|
||||
void __iomem *regs;
|
||||
raw_spinlock_t gpio_lock;
|
||||
int interrupt_trigger;
|
||||
int mapped_irq;
|
||||
@ -34,40 +44,36 @@ struct altera_gpio_chip {
|
||||
|
||||
static void altera_gpio_irq_unmask(struct irq_data *d)
|
||||
{
|
||||
struct altera_gpio_chip *altera_gc;
|
||||
struct of_mm_gpio_chip *mm_gc;
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct altera_gpio_chip *altera_gc = gpiochip_get_data(gc);
|
||||
unsigned long flags;
|
||||
u32 intmask;
|
||||
|
||||
altera_gc = gpiochip_get_data(irq_data_get_irq_chip_data(d));
|
||||
mm_gc = &altera_gc->mmchip;
|
||||
gpiochip_enable_irq(&mm_gc->gc, irqd_to_hwirq(d));
|
||||
gpiochip_enable_irq(gc, irqd_to_hwirq(d));
|
||||
|
||||
raw_spin_lock_irqsave(&altera_gc->gpio_lock, flags);
|
||||
intmask = readl(mm_gc->regs + ALTERA_GPIO_IRQ_MASK);
|
||||
intmask = readl(altera_gc->regs + ALTERA_GPIO_IRQ_MASK);
|
||||
/* Set ALTERA_GPIO_IRQ_MASK bit to unmask */
|
||||
intmask |= BIT(irqd_to_hwirq(d));
|
||||
writel(intmask, mm_gc->regs + ALTERA_GPIO_IRQ_MASK);
|
||||
writel(intmask, altera_gc->regs + ALTERA_GPIO_IRQ_MASK);
|
||||
raw_spin_unlock_irqrestore(&altera_gc->gpio_lock, flags);
|
||||
}
|
||||
|
||||
static void altera_gpio_irq_mask(struct irq_data *d)
|
||||
{
|
||||
struct altera_gpio_chip *altera_gc;
|
||||
struct of_mm_gpio_chip *mm_gc;
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct altera_gpio_chip *altera_gc = gpiochip_get_data(gc);
|
||||
unsigned long flags;
|
||||
u32 intmask;
|
||||
|
||||
altera_gc = gpiochip_get_data(irq_data_get_irq_chip_data(d));
|
||||
mm_gc = &altera_gc->mmchip;
|
||||
|
||||
raw_spin_lock_irqsave(&altera_gc->gpio_lock, flags);
|
||||
intmask = readl(mm_gc->regs + ALTERA_GPIO_IRQ_MASK);
|
||||
intmask = readl(altera_gc->regs + ALTERA_GPIO_IRQ_MASK);
|
||||
/* Clear ALTERA_GPIO_IRQ_MASK bit to mask */
|
||||
intmask &= ~BIT(irqd_to_hwirq(d));
|
||||
writel(intmask, mm_gc->regs + ALTERA_GPIO_IRQ_MASK);
|
||||
writel(intmask, altera_gc->regs + ALTERA_GPIO_IRQ_MASK);
|
||||
raw_spin_unlock_irqrestore(&altera_gc->gpio_lock, flags);
|
||||
gpiochip_disable_irq(&mm_gc->gc, irqd_to_hwirq(d));
|
||||
|
||||
gpiochip_disable_irq(gc, irqd_to_hwirq(d));
|
||||
}
|
||||
|
||||
/*
|
||||
@ -77,9 +83,8 @@ static void altera_gpio_irq_mask(struct irq_data *d)
|
||||
static int altera_gpio_irq_set_type(struct irq_data *d,
|
||||
unsigned int type)
|
||||
{
|
||||
struct altera_gpio_chip *altera_gc;
|
||||
|
||||
altera_gc = gpiochip_get_data(irq_data_get_irq_chip_data(d));
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct altera_gpio_chip *altera_gc = gpiochip_get_data(gc);
|
||||
|
||||
if (type == IRQ_TYPE_NONE) {
|
||||
irq_set_handler_locked(d, handle_bad_irq);
|
||||
@ -105,49 +110,39 @@ static unsigned int altera_gpio_irq_startup(struct irq_data *d)
|
||||
|
||||
static int altera_gpio_get(struct gpio_chip *gc, unsigned offset)
|
||||
{
|
||||
struct of_mm_gpio_chip *mm_gc;
|
||||
struct altera_gpio_chip *altera_gc = gpiochip_get_data(gc);
|
||||
|
||||
mm_gc = to_of_mm_gpio_chip(gc);
|
||||
|
||||
return !!(readl(mm_gc->regs + ALTERA_GPIO_DATA) & BIT(offset));
|
||||
return !!(readl(altera_gc->regs + ALTERA_GPIO_DATA) & BIT(offset));
|
||||
}
|
||||
|
||||
static void altera_gpio_set(struct gpio_chip *gc, unsigned offset, int value)
|
||||
{
|
||||
struct of_mm_gpio_chip *mm_gc;
|
||||
struct altera_gpio_chip *chip;
|
||||
struct altera_gpio_chip *altera_gc = gpiochip_get_data(gc);
|
||||
unsigned long flags;
|
||||
unsigned int data_reg;
|
||||
|
||||
mm_gc = to_of_mm_gpio_chip(gc);
|
||||
chip = gpiochip_get_data(gc);
|
||||
|
||||
raw_spin_lock_irqsave(&chip->gpio_lock, flags);
|
||||
data_reg = readl(mm_gc->regs + ALTERA_GPIO_DATA);
|
||||
raw_spin_lock_irqsave(&altera_gc->gpio_lock, flags);
|
||||
data_reg = readl(altera_gc->regs + ALTERA_GPIO_DATA);
|
||||
if (value)
|
||||
data_reg |= BIT(offset);
|
||||
else
|
||||
data_reg &= ~BIT(offset);
|
||||
writel(data_reg, mm_gc->regs + ALTERA_GPIO_DATA);
|
||||
raw_spin_unlock_irqrestore(&chip->gpio_lock, flags);
|
||||
writel(data_reg, altera_gc->regs + ALTERA_GPIO_DATA);
|
||||
raw_spin_unlock_irqrestore(&altera_gc->gpio_lock, flags);
|
||||
}
|
||||
|
||||
static int altera_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
|
||||
{
|
||||
struct of_mm_gpio_chip *mm_gc;
|
||||
struct altera_gpio_chip *chip;
|
||||
struct altera_gpio_chip *altera_gc = gpiochip_get_data(gc);
|
||||
unsigned long flags;
|
||||
unsigned int gpio_ddr;
|
||||
|
||||
mm_gc = to_of_mm_gpio_chip(gc);
|
||||
chip = gpiochip_get_data(gc);
|
||||
|
||||
raw_spin_lock_irqsave(&chip->gpio_lock, flags);
|
||||
raw_spin_lock_irqsave(&altera_gc->gpio_lock, flags);
|
||||
/* Set pin as input, assumes software controlled IP */
|
||||
gpio_ddr = readl(mm_gc->regs + ALTERA_GPIO_DIR);
|
||||
gpio_ddr = readl(altera_gc->regs + ALTERA_GPIO_DIR);
|
||||
gpio_ddr &= ~BIT(offset);
|
||||
writel(gpio_ddr, mm_gc->regs + ALTERA_GPIO_DIR);
|
||||
raw_spin_unlock_irqrestore(&chip->gpio_lock, flags);
|
||||
writel(gpio_ddr, altera_gc->regs + ALTERA_GPIO_DIR);
|
||||
raw_spin_unlock_irqrestore(&altera_gc->gpio_lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -155,53 +150,46 @@ static int altera_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
|
||||
static int altera_gpio_direction_output(struct gpio_chip *gc,
|
||||
unsigned offset, int value)
|
||||
{
|
||||
struct of_mm_gpio_chip *mm_gc;
|
||||
struct altera_gpio_chip *chip;
|
||||
struct altera_gpio_chip *altera_gc = gpiochip_get_data(gc);
|
||||
unsigned long flags;
|
||||
unsigned int data_reg, gpio_ddr;
|
||||
|
||||
mm_gc = to_of_mm_gpio_chip(gc);
|
||||
chip = gpiochip_get_data(gc);
|
||||
|
||||
raw_spin_lock_irqsave(&chip->gpio_lock, flags);
|
||||
raw_spin_lock_irqsave(&altera_gc->gpio_lock, flags);
|
||||
/* Sets the GPIO value */
|
||||
data_reg = readl(mm_gc->regs + ALTERA_GPIO_DATA);
|
||||
data_reg = readl(altera_gc->regs + ALTERA_GPIO_DATA);
|
||||
if (value)
|
||||
data_reg |= BIT(offset);
|
||||
else
|
||||
data_reg &= ~BIT(offset);
|
||||
writel(data_reg, mm_gc->regs + ALTERA_GPIO_DATA);
|
||||
writel(data_reg, altera_gc->regs + ALTERA_GPIO_DATA);
|
||||
|
||||
/* Set pin as output, assumes software controlled IP */
|
||||
gpio_ddr = readl(mm_gc->regs + ALTERA_GPIO_DIR);
|
||||
gpio_ddr = readl(altera_gc->regs + ALTERA_GPIO_DIR);
|
||||
gpio_ddr |= BIT(offset);
|
||||
writel(gpio_ddr, mm_gc->regs + ALTERA_GPIO_DIR);
|
||||
raw_spin_unlock_irqrestore(&chip->gpio_lock, flags);
|
||||
writel(gpio_ddr, altera_gc->regs + ALTERA_GPIO_DIR);
|
||||
raw_spin_unlock_irqrestore(&altera_gc->gpio_lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void altera_gpio_irq_edge_handler(struct irq_desc *desc)
|
||||
{
|
||||
struct altera_gpio_chip *altera_gc;
|
||||
struct gpio_chip *gc = irq_desc_get_handler_data(desc);
|
||||
struct altera_gpio_chip *altera_gc = gpiochip_get_data(gc);
|
||||
struct irq_domain *irqdomain = gc->irq.domain;
|
||||
struct irq_chip *chip;
|
||||
struct of_mm_gpio_chip *mm_gc;
|
||||
struct irq_domain *irqdomain;
|
||||
unsigned long status;
|
||||
int i;
|
||||
|
||||
altera_gc = gpiochip_get_data(irq_desc_get_handler_data(desc));
|
||||
chip = irq_desc_get_chip(desc);
|
||||
mm_gc = &altera_gc->mmchip;
|
||||
irqdomain = altera_gc->mmchip.gc.irq.domain;
|
||||
|
||||
chained_irq_enter(chip, desc);
|
||||
|
||||
while ((status =
|
||||
(readl(mm_gc->regs + ALTERA_GPIO_EDGE_CAP) &
|
||||
readl(mm_gc->regs + ALTERA_GPIO_IRQ_MASK)))) {
|
||||
writel(status, mm_gc->regs + ALTERA_GPIO_EDGE_CAP);
|
||||
for_each_set_bit(i, &status, mm_gc->gc.ngpio)
|
||||
(readl(altera_gc->regs + ALTERA_GPIO_EDGE_CAP) &
|
||||
readl(altera_gc->regs + ALTERA_GPIO_IRQ_MASK)))) {
|
||||
writel(status, altera_gc->regs + ALTERA_GPIO_EDGE_CAP);
|
||||
for_each_set_bit(i, &status, gc->ngpio)
|
||||
generic_handle_domain_irq(irqdomain, i);
|
||||
}
|
||||
|
||||
@ -210,24 +198,21 @@ static void altera_gpio_irq_edge_handler(struct irq_desc *desc)
|
||||
|
||||
static void altera_gpio_irq_leveL_high_handler(struct irq_desc *desc)
|
||||
{
|
||||
struct altera_gpio_chip *altera_gc;
|
||||
struct gpio_chip *gc = irq_desc_get_handler_data(desc);
|
||||
struct altera_gpio_chip *altera_gc = gpiochip_get_data(gc);
|
||||
struct irq_domain *irqdomain = gc->irq.domain;
|
||||
struct irq_chip *chip;
|
||||
struct of_mm_gpio_chip *mm_gc;
|
||||
struct irq_domain *irqdomain;
|
||||
unsigned long status;
|
||||
int i;
|
||||
|
||||
altera_gc = gpiochip_get_data(irq_desc_get_handler_data(desc));
|
||||
chip = irq_desc_get_chip(desc);
|
||||
mm_gc = &altera_gc->mmchip;
|
||||
irqdomain = altera_gc->mmchip.gc.irq.domain;
|
||||
|
||||
chained_irq_enter(chip, desc);
|
||||
|
||||
status = readl(mm_gc->regs + ALTERA_GPIO_DATA);
|
||||
status &= readl(mm_gc->regs + ALTERA_GPIO_IRQ_MASK);
|
||||
status = readl(altera_gc->regs + ALTERA_GPIO_DATA);
|
||||
status &= readl(altera_gc->regs + ALTERA_GPIO_IRQ_MASK);
|
||||
|
||||
for_each_set_bit(i, &status, mm_gc->gc.ngpio)
|
||||
for_each_set_bit(i, &status, gc->ngpio)
|
||||
generic_handle_domain_irq(irqdomain, i);
|
||||
|
||||
chained_irq_exit(chip, desc);
|
||||
@ -246,7 +231,7 @@ static const struct irq_chip altera_gpio_irq_chip = {
|
||||
|
||||
static int altera_gpio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct device *dev = &pdev->dev;
|
||||
int reg, ret;
|
||||
struct altera_gpio_chip *altera_gc;
|
||||
struct gpio_irq_chip *girq;
|
||||
@ -257,39 +242,42 @@ static int altera_gpio_probe(struct platform_device *pdev)
|
||||
|
||||
raw_spin_lock_init(&altera_gc->gpio_lock);
|
||||
|
||||
if (of_property_read_u32(node, "altr,ngpio", ®))
|
||||
if (device_property_read_u32(dev, "altr,ngpio", ®))
|
||||
/* By default assume maximum ngpio */
|
||||
altera_gc->mmchip.gc.ngpio = ALTERA_GPIO_MAX_NGPIO;
|
||||
altera_gc->gc.ngpio = ALTERA_GPIO_MAX_NGPIO;
|
||||
else
|
||||
altera_gc->mmchip.gc.ngpio = reg;
|
||||
altera_gc->gc.ngpio = reg;
|
||||
|
||||
if (altera_gc->mmchip.gc.ngpio > ALTERA_GPIO_MAX_NGPIO) {
|
||||
if (altera_gc->gc.ngpio > ALTERA_GPIO_MAX_NGPIO) {
|
||||
dev_warn(&pdev->dev,
|
||||
"ngpio is greater than %d, defaulting to %d\n",
|
||||
ALTERA_GPIO_MAX_NGPIO, ALTERA_GPIO_MAX_NGPIO);
|
||||
altera_gc->mmchip.gc.ngpio = ALTERA_GPIO_MAX_NGPIO;
|
||||
altera_gc->gc.ngpio = ALTERA_GPIO_MAX_NGPIO;
|
||||
}
|
||||
|
||||
altera_gc->mmchip.gc.direction_input = altera_gpio_direction_input;
|
||||
altera_gc->mmchip.gc.direction_output = altera_gpio_direction_output;
|
||||
altera_gc->mmchip.gc.get = altera_gpio_get;
|
||||
altera_gc->mmchip.gc.set = altera_gpio_set;
|
||||
altera_gc->mmchip.gc.owner = THIS_MODULE;
|
||||
altera_gc->mmchip.gc.parent = &pdev->dev;
|
||||
altera_gc->gc.direction_input = altera_gpio_direction_input;
|
||||
altera_gc->gc.direction_output = altera_gpio_direction_output;
|
||||
altera_gc->gc.get = altera_gpio_get;
|
||||
altera_gc->gc.set = altera_gpio_set;
|
||||
altera_gc->gc.owner = THIS_MODULE;
|
||||
altera_gc->gc.parent = &pdev->dev;
|
||||
|
||||
altera_gc->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(altera_gc->regs))
|
||||
return dev_err_probe(dev, PTR_ERR(altera_gc->regs), "failed to ioremap memory resource\n");
|
||||
|
||||
altera_gc->mapped_irq = platform_get_irq_optional(pdev, 0);
|
||||
|
||||
if (altera_gc->mapped_irq < 0)
|
||||
goto skip_irq;
|
||||
|
||||
if (of_property_read_u32(node, "altr,interrupt-type", ®)) {
|
||||
if (device_property_read_u32(dev, "altr,interrupt-type", ®)) {
|
||||
dev_err(&pdev->dev,
|
||||
"altr,interrupt-type value not set in device tree\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
altera_gc->interrupt_trigger = reg;
|
||||
|
||||
girq = &altera_gc->mmchip.gc.irq;
|
||||
girq = &altera_gc->gc.irq;
|
||||
gpio_irq_chip_set_chip(girq, &altera_gpio_irq_chip);
|
||||
|
||||
if (altera_gc->interrupt_trigger == IRQ_TYPE_LEVEL_HIGH)
|
||||
@ -306,24 +294,15 @@ static int altera_gpio_probe(struct platform_device *pdev)
|
||||
girq->parents[0] = altera_gc->mapped_irq;
|
||||
|
||||
skip_irq:
|
||||
ret = of_mm_gpiochip_add_data(node, &altera_gc->mmchip, altera_gc);
|
||||
ret = devm_gpiochip_add_data(dev, &altera_gc->gc, altera_gc);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed adding memory mapped gpiochip\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, altera_gc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void altera_gpio_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct altera_gpio_chip *altera_gc = platform_get_drvdata(pdev);
|
||||
|
||||
of_mm_gpiochip_remove(&altera_gc->mmchip);
|
||||
}
|
||||
|
||||
static const struct of_device_id altera_gpio_of_match[] = {
|
||||
{ .compatible = "altr,pio-1.0", },
|
||||
{},
|
||||
@ -336,7 +315,6 @@ static struct platform_driver altera_gpio_driver = {
|
||||
.of_match_table = altera_gpio_of_match,
|
||||
},
|
||||
.probe = altera_gpio_probe,
|
||||
.remove_new = altera_gpio_remove,
|
||||
};
|
||||
|
||||
static int __init altera_gpio_init(void)
|
||||
|
@ -106,7 +106,7 @@ static int pt_gpio_probe(struct platform_device *pdev)
|
||||
pt_gpio->gc.free = pt_gpio_free;
|
||||
pt_gpio->gc.ngpio = (uintptr_t)device_get_match_data(dev);
|
||||
|
||||
ret = gpiochip_add_data(&pt_gpio->gc, pt_gpio);
|
||||
ret = devm_gpiochip_add_data(dev, &pt_gpio->gc, pt_gpio);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to register GPIO lib\n");
|
||||
return ret;
|
||||
@ -122,13 +122,6 @@ static int pt_gpio_probe(struct platform_device *pdev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void pt_gpio_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct pt_gpio_chip *pt_gpio = platform_get_drvdata(pdev);
|
||||
|
||||
gpiochip_remove(&pt_gpio->gc);
|
||||
}
|
||||
|
||||
static const struct acpi_device_id pt_gpio_acpi_match[] = {
|
||||
{ "AMDF030", PT_TOTAL_GPIO },
|
||||
{ "AMDIF030", PT_TOTAL_GPIO },
|
||||
@ -143,7 +136,6 @@ static struct platform_driver pt_gpio_driver = {
|
||||
.acpi_match_table = ACPI_PTR(pt_gpio_acpi_match),
|
||||
},
|
||||
.probe = pt_gpio_probe,
|
||||
.remove_new = pt_gpio_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(pt_gpio_driver);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -751,7 +751,7 @@ static struct platform_driver brcmstb_gpio_driver = {
|
||||
.pm = &brcmstb_gpio_pm_ops,
|
||||
},
|
||||
.probe = brcmstb_gpio_probe,
|
||||
.remove_new = brcmstb_gpio_remove,
|
||||
.remove = brcmstb_gpio_remove,
|
||||
.shutdown = brcmstb_gpio_shutdown,
|
||||
};
|
||||
module_platform_driver(brcmstb_gpio_driver);
|
||||
|
@ -277,7 +277,7 @@ static struct platform_driver cdns_gpio_driver = {
|
||||
.of_match_table = cdns_of_ids,
|
||||
},
|
||||
.probe = cdns_gpio_probe,
|
||||
.remove_new = cdns_gpio_remove,
|
||||
.remove = cdns_gpio_remove,
|
||||
};
|
||||
module_platform_driver(cdns_gpio_driver);
|
||||
|
||||
|
196
drivers/gpio/gpio-cgbc.c
Normal file
196
drivers/gpio/gpio-cgbc.c
Normal file
@ -0,0 +1,196 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Congatec Board Controller GPIO driver
|
||||
*
|
||||
* Copyright (C) 2024 Bootlin
|
||||
* Author: Thomas Richard <thomas.richard@bootlin.com>
|
||||
*/
|
||||
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/mfd/cgbc.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#define CGBC_GPIO_NGPIO 14
|
||||
|
||||
#define CGBC_GPIO_CMD_GET 0x64
|
||||
#define CGBC_GPIO_CMD_SET 0x65
|
||||
#define CGBC_GPIO_CMD_DIR_GET 0x66
|
||||
#define CGBC_GPIO_CMD_DIR_SET 0x67
|
||||
|
||||
struct cgbc_gpio_data {
|
||||
struct gpio_chip chip;
|
||||
struct cgbc_device_data *cgbc;
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
static int cgbc_gpio_cmd(struct cgbc_device_data *cgbc,
|
||||
u8 cmd0, u8 cmd1, u8 cmd2, u8 *value)
|
||||
{
|
||||
u8 cmd[3] = {cmd0, cmd1, cmd2};
|
||||
|
||||
return cgbc_command(cgbc, cmd, sizeof(cmd), value, 1, NULL);
|
||||
}
|
||||
|
||||
static int cgbc_gpio_get(struct gpio_chip *chip, unsigned int offset)
|
||||
{
|
||||
struct cgbc_gpio_data *gpio = gpiochip_get_data(chip);
|
||||
struct cgbc_device_data *cgbc = gpio->cgbc;
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
scoped_guard(mutex, &gpio->lock)
|
||||
ret = cgbc_gpio_cmd(cgbc, CGBC_GPIO_CMD_GET, (offset > 7) ? 1 : 0, 0, &val);
|
||||
|
||||
offset %= 8;
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
else
|
||||
return (int)(val & (u8)BIT(offset));
|
||||
}
|
||||
|
||||
static void __cgbc_gpio_set(struct gpio_chip *chip,
|
||||
unsigned int offset, int value)
|
||||
{
|
||||
struct cgbc_gpio_data *gpio = gpiochip_get_data(chip);
|
||||
struct cgbc_device_data *cgbc = gpio->cgbc;
|
||||
u8 val;
|
||||
int ret;
|
||||
|
||||
ret = cgbc_gpio_cmd(cgbc, CGBC_GPIO_CMD_GET, (offset > 7) ? 1 : 0, 0, &val);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
if (value)
|
||||
val |= BIT(offset % 8);
|
||||
else
|
||||
val &= ~(BIT(offset % 8));
|
||||
|
||||
cgbc_gpio_cmd(cgbc, CGBC_GPIO_CMD_SET, (offset > 7) ? 1 : 0, val, &val);
|
||||
}
|
||||
|
||||
static void cgbc_gpio_set(struct gpio_chip *chip,
|
||||
unsigned int offset, int value)
|
||||
{
|
||||
struct cgbc_gpio_data *gpio = gpiochip_get_data(chip);
|
||||
|
||||
scoped_guard(mutex, &gpio->lock)
|
||||
__cgbc_gpio_set(chip, offset, value);
|
||||
}
|
||||
|
||||
static int cgbc_gpio_direction_set(struct gpio_chip *chip,
|
||||
unsigned int offset, int direction)
|
||||
{
|
||||
struct cgbc_gpio_data *gpio = gpiochip_get_data(chip);
|
||||
struct cgbc_device_data *cgbc = gpio->cgbc;
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
ret = cgbc_gpio_cmd(cgbc, CGBC_GPIO_CMD_DIR_GET, (offset > 7) ? 1 : 0, 0, &val);
|
||||
if (ret)
|
||||
goto end;
|
||||
|
||||
if (direction == GPIO_LINE_DIRECTION_IN)
|
||||
val &= ~(BIT(offset % 8));
|
||||
else
|
||||
val |= BIT(offset % 8);
|
||||
|
||||
ret = cgbc_gpio_cmd(cgbc, CGBC_GPIO_CMD_DIR_SET, (offset > 7) ? 1 : 0, val, &val);
|
||||
|
||||
end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cgbc_gpio_direction_input(struct gpio_chip *chip,
|
||||
unsigned int offset)
|
||||
{
|
||||
struct cgbc_gpio_data *gpio = gpiochip_get_data(chip);
|
||||
|
||||
guard(mutex)(&gpio->lock);
|
||||
return cgbc_gpio_direction_set(chip, offset, GPIO_LINE_DIRECTION_IN);
|
||||
}
|
||||
|
||||
static int cgbc_gpio_direction_output(struct gpio_chip *chip,
|
||||
unsigned int offset, int value)
|
||||
{
|
||||
struct cgbc_gpio_data *gpio = gpiochip_get_data(chip);
|
||||
|
||||
guard(mutex)(&gpio->lock);
|
||||
|
||||
__cgbc_gpio_set(chip, offset, value);
|
||||
return cgbc_gpio_direction_set(chip, offset, GPIO_LINE_DIRECTION_OUT);
|
||||
}
|
||||
|
||||
static int cgbc_gpio_get_direction(struct gpio_chip *chip, unsigned int offset)
|
||||
{
|
||||
struct cgbc_gpio_data *gpio = gpiochip_get_data(chip);
|
||||
struct cgbc_device_data *cgbc = gpio->cgbc;
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
scoped_guard(mutex, &gpio->lock)
|
||||
ret = cgbc_gpio_cmd(cgbc, CGBC_GPIO_CMD_DIR_GET, (offset > 7) ? 1 : 0, 0, &val);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (val & BIT(offset % 8))
|
||||
return GPIO_LINE_DIRECTION_OUT;
|
||||
else
|
||||
return GPIO_LINE_DIRECTION_IN;
|
||||
}
|
||||
|
||||
static int cgbc_gpio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct cgbc_device_data *cgbc = dev_get_drvdata(dev->parent);
|
||||
struct cgbc_gpio_data *gpio;
|
||||
struct gpio_chip *chip;
|
||||
int ret;
|
||||
|
||||
gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL);
|
||||
if (!gpio)
|
||||
return -ENOMEM;
|
||||
|
||||
gpio->cgbc = cgbc;
|
||||
|
||||
platform_set_drvdata(pdev, gpio);
|
||||
|
||||
chip = &gpio->chip;
|
||||
chip->label = dev_name(&pdev->dev);
|
||||
chip->owner = THIS_MODULE;
|
||||
chip->parent = dev;
|
||||
chip->base = -1;
|
||||
chip->direction_input = cgbc_gpio_direction_input;
|
||||
chip->direction_output = cgbc_gpio_direction_output;
|
||||
chip->get_direction = cgbc_gpio_get_direction;
|
||||
chip->get = cgbc_gpio_get;
|
||||
chip->set = cgbc_gpio_set;
|
||||
chip->ngpio = CGBC_GPIO_NGPIO;
|
||||
|
||||
ret = devm_mutex_init(dev, &gpio->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_gpiochip_add_data(dev, chip, gpio);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Could not register GPIO chip\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver cgbc_gpio_driver = {
|
||||
.driver = {
|
||||
.name = "cgbc-gpio",
|
||||
},
|
||||
.probe = cgbc_gpio_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(cgbc_gpio_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Congatec Board Controller GPIO Driver");
|
||||
MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:cgbc-gpio");
|
@ -15,7 +15,6 @@
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
@ -159,14 +158,13 @@ static int davinci_gpio_probe(struct platform_device *pdev)
|
||||
unsigned int ngpio, nbank, nirq, gpio_unbanked;
|
||||
struct davinci_gpio_controller *chips;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *dn = dev_of_node(dev);
|
||||
|
||||
/*
|
||||
* The gpio banks conceptually expose a segmented bitmap,
|
||||
* and "ngpio" is one more than the largest zero-based
|
||||
* bit index that's valid.
|
||||
*/
|
||||
ret = of_property_read_u32(dn, "ti,ngpio", &ngpio);
|
||||
ret = device_property_read_u32(dev, "ti,ngpio", &ngpio);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to get the number of GPIOs\n");
|
||||
if (ngpio == 0)
|
||||
@ -177,8 +175,8 @@ static int davinci_gpio_probe(struct platform_device *pdev)
|
||||
* interrupts is equal to number of gpios else all are banked so
|
||||
* number of interrupts is equal to number of banks(each with 16 gpios)
|
||||
*/
|
||||
ret = of_property_read_u32(dn, "ti,davinci-gpio-unbanked",
|
||||
&gpio_unbanked);
|
||||
ret = device_property_read_u32(dev, "ti,davinci-gpio-unbanked",
|
||||
&gpio_unbanked);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to get the unbanked GPIOs property\n");
|
||||
|
||||
@ -662,7 +660,7 @@ static struct platform_driver davinci_gpio_driver = {
|
||||
.driver = {
|
||||
.name = "davinci_gpio",
|
||||
.pm = pm_sleep_ptr(&davinci_gpio_dev_pm_ops),
|
||||
.of_match_table = of_match_ptr(davinci_gpio_ids),
|
||||
.of_match_table = davinci_gpio_ids,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -512,7 +512,7 @@ static void dln2_gpio_remove(struct platform_device *pdev)
|
||||
static struct platform_driver dln2_gpio_driver = {
|
||||
.driver.name = "dln2-gpio",
|
||||
.probe = dln2_gpio_probe,
|
||||
.remove_new = dln2_gpio_remove,
|
||||
.remove = dln2_gpio_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(dln2_gpio_driver);
|
||||
|
@ -571,7 +571,6 @@ static void dwapb_get_irq(struct device *dev, struct fwnode_handle *fwnode,
|
||||
|
||||
static struct dwapb_platform_data *dwapb_gpio_get_pdata(struct device *dev)
|
||||
{
|
||||
struct fwnode_handle *fwnode;
|
||||
struct dwapb_platform_data *pdata;
|
||||
struct dwapb_port_property *pp;
|
||||
int nports;
|
||||
@ -592,7 +591,7 @@ static struct dwapb_platform_data *dwapb_gpio_get_pdata(struct device *dev)
|
||||
pdata->nports = nports;
|
||||
|
||||
i = 0;
|
||||
device_for_each_child_node(dev, fwnode) {
|
||||
device_for_each_child_node_scoped(dev, fwnode) {
|
||||
pp = &pdata->properties[i++];
|
||||
pp->fwnode = fwnode;
|
||||
|
||||
@ -600,7 +599,6 @@ static struct dwapb_platform_data *dwapb_gpio_get_pdata(struct device *dev)
|
||||
pp->idx >= DWAPB_MAX_PORTS) {
|
||||
dev_err(dev,
|
||||
"missing/invalid port index for port%d\n", i);
|
||||
fwnode_handle_put(fwnode);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
@ -694,6 +692,7 @@ static const struct acpi_device_id dwapb_acpi_match[] = {
|
||||
{"HISI0181", GPIO_REG_OFFSET_V1},
|
||||
{"APMC0D07", GPIO_REG_OFFSET_V1},
|
||||
{"APMC0D81", GPIO_REG_OFFSET_V2},
|
||||
{"FUJI200A", GPIO_REG_OFFSET_V1},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, dwapb_acpi_match);
|
||||
|
@ -10,8 +10,8 @@
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
/* EIC registers definition */
|
||||
@ -617,7 +617,7 @@ static int sprd_eic_probe(struct platform_device *pdev)
|
||||
u16 num_banks = 0;
|
||||
int ret, i;
|
||||
|
||||
pdata = of_device_get_match_data(dev);
|
||||
pdata = device_get_match_data(dev);
|
||||
if (!pdata) {
|
||||
dev_err(dev, "No matching driver data found.\n");
|
||||
return -EINVAL;
|
||||
|
@ -253,18 +253,13 @@ static int ftgpio_gpio_probe(struct platform_device *pdev)
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
g->clk = devm_clk_get(dev, NULL);
|
||||
if (!IS_ERR(g->clk)) {
|
||||
ret = clk_prepare_enable(g->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else if (PTR_ERR(g->clk) == -EPROBE_DEFER) {
|
||||
g->clk = devm_clk_get_enabled(dev, NULL);
|
||||
if (IS_ERR(g->clk) && PTR_ERR(g->clk) == -EPROBE_DEFER)
|
||||
/*
|
||||
* Percolate deferrals, for anything else,
|
||||
* just live without the clocking.
|
||||
*/
|
||||
return PTR_ERR(g->clk);
|
||||
}
|
||||
|
||||
ret = bgpio_init(&g->gc, dev, 4,
|
||||
g->base + GPIO_DATA_IN,
|
||||
@ -273,10 +268,9 @@ static int ftgpio_gpio_probe(struct platform_device *pdev)
|
||||
g->base + GPIO_DIR,
|
||||
NULL,
|
||||
0);
|
||||
if (ret) {
|
||||
dev_err(dev, "unable to init generic GPIO\n");
|
||||
goto dis_clk;
|
||||
}
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "unable to init generic GPIO\n");
|
||||
|
||||
g->gc.label = dev_name(dev);
|
||||
g->gc.base = -1;
|
||||
g->gc.parent = dev;
|
||||
@ -293,10 +287,9 @@ static int ftgpio_gpio_probe(struct platform_device *pdev)
|
||||
girq->num_parents = 1;
|
||||
girq->parents = devm_kcalloc(dev, 1, sizeof(*girq->parents),
|
||||
GFP_KERNEL);
|
||||
if (!girq->parents) {
|
||||
ret = -ENOMEM;
|
||||
goto dis_clk;
|
||||
}
|
||||
if (!girq->parents)
|
||||
return -ENOMEM;
|
||||
|
||||
girq->default_type = IRQ_TYPE_NONE;
|
||||
girq->handler = handle_bad_irq;
|
||||
girq->parents[0] = irq;
|
||||
@ -309,26 +302,7 @@ static int ftgpio_gpio_probe(struct platform_device *pdev)
|
||||
/* Clear any use of debounce */
|
||||
writel(0x0, g->base + GPIO_DEBOUNCE_EN);
|
||||
|
||||
ret = devm_gpiochip_add_data(dev, &g->gc, g);
|
||||
if (ret)
|
||||
goto dis_clk;
|
||||
|
||||
platform_set_drvdata(pdev, g);
|
||||
dev_info(dev, "FTGPIO010 @%p registered\n", g->base);
|
||||
|
||||
return 0;
|
||||
|
||||
dis_clk:
|
||||
clk_disable_unprepare(g->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ftgpio_gpio_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct ftgpio_gpio *g = platform_get_drvdata(pdev);
|
||||
|
||||
clk_disable_unprepare(g->clk);
|
||||
return devm_gpiochip_add_data(dev, &g->gc, g);
|
||||
}
|
||||
|
||||
static const struct of_device_id ftgpio_gpio_of_match[] = {
|
||||
@ -350,6 +324,5 @@ static struct platform_driver ftgpio_gpio_driver = {
|
||||
.of_match_table = ftgpio_gpio_of_match,
|
||||
},
|
||||
.probe = ftgpio_gpio_probe,
|
||||
.remove_new = ftgpio_gpio_remove,
|
||||
};
|
||||
builtin_platform_driver(ftgpio_gpio_driver);
|
||||
|
@ -16,20 +16,20 @@
|
||||
* Contributors: Andreas Larsson <andreas@gaisler.com>
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
#define GRGPIO_MAX_NGPIO 32
|
||||
|
||||
@ -318,6 +318,13 @@ static void grgpio_irq_unmap(struct irq_domain *d, unsigned int irq)
|
||||
raw_spin_unlock_irqrestore(&priv->gc.bgpio_lock, flags);
|
||||
}
|
||||
|
||||
static void grgpio_irq_domain_remove(void *data)
|
||||
{
|
||||
struct irq_domain *domain = data;
|
||||
|
||||
irq_domain_remove(domain);
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops grgpio_irq_domain_ops = {
|
||||
.map = grgpio_irq_map,
|
||||
.unmap = grgpio_irq_unmap,
|
||||
@ -328,6 +335,7 @@ static const struct irq_domain_ops grgpio_irq_domain_ops = {
|
||||
static int grgpio_probe(struct platform_device *ofdev)
|
||||
{
|
||||
struct device_node *np = ofdev->dev.of_node;
|
||||
struct device *dev = &ofdev->dev;
|
||||
void __iomem *regs;
|
||||
struct gpio_chip *gc;
|
||||
struct grgpio_priv *priv;
|
||||
@ -337,7 +345,7 @@ static int grgpio_probe(struct platform_device *ofdev)
|
||||
int size;
|
||||
int i;
|
||||
|
||||
priv = devm_kzalloc(&ofdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
@ -346,28 +354,31 @@ static int grgpio_probe(struct platform_device *ofdev)
|
||||
return PTR_ERR(regs);
|
||||
|
||||
gc = &priv->gc;
|
||||
err = bgpio_init(gc, &ofdev->dev, 4, regs + GRGPIO_DATA,
|
||||
err = bgpio_init(gc, dev, 4, regs + GRGPIO_DATA,
|
||||
regs + GRGPIO_OUTPUT, NULL, regs + GRGPIO_DIR, NULL,
|
||||
BGPIOF_BIG_ENDIAN_BYTE_ORDER);
|
||||
if (err) {
|
||||
dev_err(&ofdev->dev, "bgpio_init() failed\n");
|
||||
dev_err(dev, "bgpio_init() failed\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
priv->regs = regs;
|
||||
priv->imask = gc->read_reg(regs + GRGPIO_IMASK);
|
||||
priv->dev = &ofdev->dev;
|
||||
priv->dev = dev;
|
||||
|
||||
gc->owner = THIS_MODULE;
|
||||
gc->to_irq = grgpio_to_irq;
|
||||
gc->label = devm_kasprintf(&ofdev->dev, GFP_KERNEL, "%pOF", np);
|
||||
gc->label = devm_kasprintf(dev, GFP_KERNEL, "%pOF", np);
|
||||
if (!gc->label)
|
||||
return -ENOMEM;
|
||||
|
||||
gc->base = -1;
|
||||
|
||||
err = of_property_read_u32(np, "nbits", &prop);
|
||||
if (err || prop <= 0 || prop > GRGPIO_MAX_NGPIO) {
|
||||
gc->ngpio = GRGPIO_MAX_NGPIO;
|
||||
dev_dbg(&ofdev->dev,
|
||||
"No or invalid nbits property: assume %d\n", gc->ngpio);
|
||||
dev_dbg(dev, "No or invalid nbits property: assume %d\n",
|
||||
gc->ngpio);
|
||||
} else {
|
||||
gc->ngpio = prop;
|
||||
}
|
||||
@ -379,7 +390,7 @@ static int grgpio_probe(struct platform_device *ofdev)
|
||||
irqmap = (s32 *)of_get_property(np, "irqmap", &size);
|
||||
if (irqmap) {
|
||||
if (size < gc->ngpio) {
|
||||
dev_err(&ofdev->dev,
|
||||
dev_err(dev,
|
||||
"irqmap shorter than ngpio (%d < %d)\n",
|
||||
size, gc->ngpio);
|
||||
return -EINVAL;
|
||||
@ -389,10 +400,15 @@ static int grgpio_probe(struct platform_device *ofdev)
|
||||
&grgpio_irq_domain_ops,
|
||||
priv);
|
||||
if (!priv->domain) {
|
||||
dev_err(&ofdev->dev, "Could not add irq domain\n");
|
||||
dev_err(dev, "Could not add irq domain\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err = devm_add_action_or_reset(dev, grgpio_irq_domain_remove,
|
||||
priv->domain);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
for (i = 0; i < gc->ngpio; i++) {
|
||||
struct grgpio_lirq *lirq;
|
||||
int ret;
|
||||
@ -415,32 +431,18 @@ static int grgpio_probe(struct platform_device *ofdev)
|
||||
}
|
||||
}
|
||||
|
||||
platform_set_drvdata(ofdev, priv);
|
||||
|
||||
err = gpiochip_add_data(gc, priv);
|
||||
err = devm_gpiochip_add_data(dev, gc, priv);
|
||||
if (err) {
|
||||
dev_err(&ofdev->dev, "Could not add gpiochip\n");
|
||||
if (priv->domain)
|
||||
irq_domain_remove(priv->domain);
|
||||
dev_err(dev, "Could not add gpiochip\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
dev_info(&ofdev->dev, "regs=0x%p, base=%d, ngpio=%d, irqs=%s\n",
|
||||
dev_info(dev, "regs=0x%p, base=%d, ngpio=%d, irqs=%s\n",
|
||||
priv->regs, gc->base, gc->ngpio, priv->domain ? "on" : "off");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void grgpio_remove(struct platform_device *ofdev)
|
||||
{
|
||||
struct grgpio_priv *priv = platform_get_drvdata(ofdev);
|
||||
|
||||
gpiochip_remove(&priv->gc);
|
||||
|
||||
if (priv->domain)
|
||||
irq_domain_remove(priv->domain);
|
||||
}
|
||||
|
||||
static const struct of_device_id grgpio_match[] = {
|
||||
{.name = "GAISLER_GPIO"},
|
||||
{.name = "01_01a"},
|
||||
@ -455,7 +457,6 @@ static struct platform_driver grgpio_driver = {
|
||||
.of_match_table = grgpio_match,
|
||||
},
|
||||
.probe = grgpio_probe,
|
||||
.remove_new = grgpio_remove,
|
||||
};
|
||||
module_platform_driver(grgpio_driver);
|
||||
|
||||
|
@ -420,8 +420,14 @@ static int ljca_gpio_probe(struct auxiliary_device *auxdev,
|
||||
if (!ljca_gpio->connect_mode)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&ljca_gpio->irq_lock);
|
||||
mutex_init(&ljca_gpio->trans_lock);
|
||||
ret = devm_mutex_init(&auxdev->dev, &ljca_gpio->irq_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_mutex_init(&auxdev->dev, &ljca_gpio->trans_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ljca_gpio->gc.direction_input = ljca_gpio_direction_input;
|
||||
ljca_gpio->gc.direction_output = ljca_gpio_direction_output;
|
||||
ljca_gpio->gc.get_direction = ljca_gpio_get_direction;
|
||||
@ -453,11 +459,8 @@ static int ljca_gpio_probe(struct auxiliary_device *auxdev,
|
||||
|
||||
INIT_WORK(&ljca_gpio->work, ljca_gpio_async);
|
||||
ret = gpiochip_add_data(&ljca_gpio->gc, ljca_gpio);
|
||||
if (ret) {
|
||||
if (ret)
|
||||
ljca_unregister_event_cb(ljca);
|
||||
mutex_destroy(&ljca_gpio->irq_lock);
|
||||
mutex_destroy(&ljca_gpio->trans_lock);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -469,8 +472,6 @@ static void ljca_gpio_remove(struct auxiliary_device *auxdev)
|
||||
gpiochip_remove(&ljca_gpio->gc);
|
||||
ljca_unregister_event_cb(ljca_gpio->ljca);
|
||||
cancel_work_sync(&ljca_gpio->work);
|
||||
mutex_destroy(&ljca_gpio->irq_lock);
|
||||
mutex_destroy(&ljca_gpio->trans_lock);
|
||||
}
|
||||
|
||||
static const struct auxiliary_device_id ljca_gpio_id_table[] = {
|
||||
|
@ -388,7 +388,7 @@ MODULE_DEVICE_TABLE(of, lpc18xx_gpio_match);
|
||||
|
||||
static struct platform_driver lpc18xx_gpio_driver = {
|
||||
.probe = lpc18xx_gpio_probe,
|
||||
.remove_new = lpc18xx_gpio_remove,
|
||||
.remove = lpc18xx_gpio_remove,
|
||||
.driver = {
|
||||
.name = "lpc18xx-gpio",
|
||||
.of_match_table = lpc18xx_gpio_match,
|
||||
|
@ -165,7 +165,10 @@ int __max730x_probe(struct max7301 *ts)
|
||||
|
||||
pdata = dev_get_platdata(dev);
|
||||
|
||||
mutex_init(&ts->lock);
|
||||
ret = devm_mutex_init(ts->dev, &ts->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dev_set_drvdata(dev, ts);
|
||||
|
||||
/* Power up the chip and disable IRQ output */
|
||||
@ -206,17 +209,11 @@ int __max730x_probe(struct max7301 *ts)
|
||||
int offset = (i - 1) * 4 + j;
|
||||
ret = max7301_direction_input(&ts->chip, offset);
|
||||
if (ret)
|
||||
goto exit_destroy;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = gpiochip_add_data(&ts->chip, ts);
|
||||
if (!ret)
|
||||
return ret;
|
||||
|
||||
exit_destroy:
|
||||
mutex_destroy(&ts->lock);
|
||||
return ret;
|
||||
return devm_gpiochip_add_data(ts->dev, &ts->chip, ts);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__max730x_probe);
|
||||
|
||||
@ -226,8 +223,6 @@ void __max730x_remove(struct device *dev)
|
||||
|
||||
/* Power down the chip and disable IRQ output */
|
||||
ts->write(dev, 0x04, 0x00);
|
||||
gpiochip_remove(&ts->chip);
|
||||
mutex_destroy(&ts->lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__max730x_remove);
|
||||
|
||||
|
@ -145,8 +145,6 @@ static int mb86s70_gpio_to_irq(struct gpio_chip *gc, unsigned int offset)
|
||||
irq = platform_get_irq(to_platform_device(gc->parent), index);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
if (irq == 0)
|
||||
break;
|
||||
if (irq_get_irq_data(irq)->hwirq == offset)
|
||||
return irq;
|
||||
}
|
||||
@ -227,7 +225,7 @@ static struct platform_driver mb86s70_gpio_driver = {
|
||||
.acpi_match_table = ACPI_PTR(mb86s70_gpio_acpi_ids),
|
||||
},
|
||||
.probe = mb86s70_gpio_probe,
|
||||
.remove_new = mb86s70_gpio_remove,
|
||||
.remove = mb86s70_gpio_remove,
|
||||
};
|
||||
module_platform_driver(mb86s70_gpio_driver);
|
||||
|
||||
|
@ -127,6 +127,13 @@ static int men_z127_set_config(struct gpio_chip *gc, unsigned offset,
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static void men_z127_release_mem(void *data)
|
||||
{
|
||||
struct resource *res = data;
|
||||
|
||||
mcb_release_mem(res);
|
||||
}
|
||||
|
||||
static int men_z127_probe(struct mcb_device *mdev,
|
||||
const struct mcb_device_id *id)
|
||||
{
|
||||
@ -140,17 +147,19 @@ static int men_z127_probe(struct mcb_device *mdev,
|
||||
return -ENOMEM;
|
||||
|
||||
men_z127_gpio->mem = mcb_request_mem(mdev, dev_name(dev));
|
||||
if (IS_ERR(men_z127_gpio->mem)) {
|
||||
dev_err(dev, "failed to request device memory");
|
||||
return PTR_ERR(men_z127_gpio->mem);
|
||||
}
|
||||
if (IS_ERR(men_z127_gpio->mem))
|
||||
return dev_err_probe(dev, PTR_ERR(men_z127_gpio->mem),
|
||||
"failed to request device memory");
|
||||
|
||||
men_z127_gpio->reg_base = ioremap(men_z127_gpio->mem->start,
|
||||
resource_size(men_z127_gpio->mem));
|
||||
if (men_z127_gpio->reg_base == NULL) {
|
||||
ret = -ENXIO;
|
||||
goto err_release;
|
||||
}
|
||||
ret = devm_add_action_or_reset(dev, men_z127_release_mem,
|
||||
men_z127_gpio->mem);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
men_z127_gpio->reg_base = devm_ioremap(dev, men_z127_gpio->mem->start,
|
||||
resource_size(men_z127_gpio->mem));
|
||||
if (men_z127_gpio->reg_base == NULL)
|
||||
return -ENXIO;
|
||||
|
||||
mcb_set_drvdata(mdev, men_z127_gpio);
|
||||
|
||||
@ -161,34 +170,16 @@ static int men_z127_probe(struct mcb_device *mdev,
|
||||
men_z127_gpio->reg_base + MEN_Z127_GPIODR,
|
||||
NULL, 0);
|
||||
if (ret)
|
||||
goto err_unmap;
|
||||
return ret;
|
||||
|
||||
men_z127_gpio->gc.set_config = men_z127_set_config;
|
||||
|
||||
ret = gpiochip_add_data(&men_z127_gpio->gc, men_z127_gpio);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to register MEN 16Z127 GPIO controller");
|
||||
goto err_unmap;
|
||||
}
|
||||
|
||||
dev_info(dev, "MEN 16Z127 GPIO driver registered");
|
||||
ret = devm_gpiochip_add_data(dev, &men_z127_gpio->gc, men_z127_gpio);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"failed to register MEN 16Z127 GPIO controller");
|
||||
|
||||
return 0;
|
||||
|
||||
err_unmap:
|
||||
iounmap(men_z127_gpio->reg_base);
|
||||
err_release:
|
||||
mcb_release_mem(men_z127_gpio->mem);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void men_z127_remove(struct mcb_device *mdev)
|
||||
{
|
||||
struct men_z127_gpio *men_z127_gpio = mcb_get_drvdata(mdev);
|
||||
|
||||
gpiochip_remove(&men_z127_gpio->gc);
|
||||
iounmap(men_z127_gpio->reg_base);
|
||||
mcb_release_mem(men_z127_gpio->mem);
|
||||
}
|
||||
|
||||
static const struct mcb_device_id men_z127_ids[] = {
|
||||
@ -202,7 +193,6 @@ static struct mcb_driver men_z127_driver = {
|
||||
.name = "z127-gpio",
|
||||
},
|
||||
.probe = men_z127_probe,
|
||||
.remove = men_z127_remove,
|
||||
.id_table = men_z127_ids,
|
||||
};
|
||||
module_mcb_driver(men_z127_driver);
|
||||
|
@ -136,7 +136,7 @@ MODULE_DEVICE_TABLE(of, ltq_mm_match);
|
||||
|
||||
static struct platform_driver ltq_mm_driver = {
|
||||
.probe = ltq_mm_probe,
|
||||
.remove_new = ltq_mm_remove,
|
||||
.remove = ltq_mm_remove,
|
||||
.driver = {
|
||||
.name = "gpio-mm-ltq",
|
||||
.of_match_table = ltq_mm_match,
|
||||
|
@ -183,7 +183,7 @@ static struct platform_driver mpc52xx_wkup_gpiochip_driver = {
|
||||
.of_match_table = mpc52xx_wkup_gpiochip_match,
|
||||
},
|
||||
.probe = mpc52xx_wkup_gpiochip_probe,
|
||||
.remove_new = mpc52xx_gpiochip_remove,
|
||||
.remove = mpc52xx_gpiochip_remove,
|
||||
};
|
||||
|
||||
/*
|
||||
@ -336,7 +336,7 @@ static struct platform_driver mpc52xx_simple_gpiochip_driver = {
|
||||
.of_match_table = mpc52xx_simple_gpiochip_match,
|
||||
},
|
||||
.probe = mpc52xx_simple_gpiochip_probe,
|
||||
.remove_new = mpc52xx_gpiochip_remove,
|
||||
.remove = mpc52xx_gpiochip_remove,
|
||||
};
|
||||
|
||||
static struct platform_driver * const drivers[] = {
|
||||
|
@ -15,7 +15,6 @@
|
||||
#include <linux/irq.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
@ -300,14 +299,14 @@ static const struct of_device_id mpc8xxx_gpio_ids[] = {
|
||||
|
||||
static int mpc8xxx_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct mpc8xxx_gpio_chip *mpc8xxx_gc;
|
||||
struct gpio_chip *gc;
|
||||
const struct mpc8xxx_gpio_devtype *devtype = NULL;
|
||||
struct mpc8xxx_gpio_chip *mpc8xxx_gc;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct fwnode_handle *fwnode;
|
||||
struct gpio_chip *gc;
|
||||
int ret;
|
||||
|
||||
mpc8xxx_gc = devm_kzalloc(&pdev->dev, sizeof(*mpc8xxx_gc), GFP_KERNEL);
|
||||
mpc8xxx_gc = devm_kzalloc(dev, sizeof(*mpc8xxx_gc), GFP_KERNEL);
|
||||
if (!mpc8xxx_gc)
|
||||
return -ENOMEM;
|
||||
|
||||
@ -320,32 +319,28 @@ static int mpc8xxx_probe(struct platform_device *pdev)
|
||||
return PTR_ERR(mpc8xxx_gc->regs);
|
||||
|
||||
gc = &mpc8xxx_gc->gc;
|
||||
gc->parent = &pdev->dev;
|
||||
gc->parent = dev;
|
||||
|
||||
if (device_property_read_bool(&pdev->dev, "little-endian")) {
|
||||
ret = bgpio_init(gc, &pdev->dev, 4,
|
||||
mpc8xxx_gc->regs + GPIO_DAT,
|
||||
NULL, NULL,
|
||||
mpc8xxx_gc->regs + GPIO_DIR, NULL,
|
||||
BGPIOF_BIG_ENDIAN);
|
||||
if (device_property_read_bool(dev, "little-endian")) {
|
||||
ret = bgpio_init(gc, dev, 4, mpc8xxx_gc->regs + GPIO_DAT,
|
||||
NULL, NULL, mpc8xxx_gc->regs + GPIO_DIR,
|
||||
NULL, BGPIOF_BIG_ENDIAN);
|
||||
if (ret)
|
||||
return ret;
|
||||
dev_dbg(&pdev->dev, "GPIO registers are LITTLE endian\n");
|
||||
dev_dbg(dev, "GPIO registers are LITTLE endian\n");
|
||||
} else {
|
||||
ret = bgpio_init(gc, &pdev->dev, 4,
|
||||
mpc8xxx_gc->regs + GPIO_DAT,
|
||||
NULL, NULL,
|
||||
mpc8xxx_gc->regs + GPIO_DIR, NULL,
|
||||
BGPIOF_BIG_ENDIAN
|
||||
ret = bgpio_init(gc, dev, 4, mpc8xxx_gc->regs + GPIO_DAT,
|
||||
NULL, NULL, mpc8xxx_gc->regs + GPIO_DIR,
|
||||
NULL, BGPIOF_BIG_ENDIAN
|
||||
| BGPIOF_BIG_ENDIAN_BYTE_ORDER);
|
||||
if (ret)
|
||||
return ret;
|
||||
dev_dbg(&pdev->dev, "GPIO registers are BIG endian\n");
|
||||
dev_dbg(dev, "GPIO registers are BIG endian\n");
|
||||
}
|
||||
|
||||
mpc8xxx_gc->direction_output = gc->direction_output;
|
||||
|
||||
devtype = device_get_match_data(&pdev->dev);
|
||||
devtype = device_get_match_data(dev);
|
||||
if (!devtype)
|
||||
devtype = &mpc8xxx_gpio_devtype_default;
|
||||
|
||||
@ -370,10 +365,10 @@ static int mpc8xxx_probe(struct platform_device *pdev)
|
||||
* associated input enable must be set (GPIOxGPIE[IEn]=1) to propagate
|
||||
* the port value to the GPIO Data Register.
|
||||
*/
|
||||
fwnode = dev_fwnode(&pdev->dev);
|
||||
if (of_device_is_compatible(np, "fsl,qoriq-gpio") ||
|
||||
of_device_is_compatible(np, "fsl,ls1028a-gpio") ||
|
||||
of_device_is_compatible(np, "fsl,ls1088a-gpio") ||
|
||||
fwnode = dev_fwnode(dev);
|
||||
if (device_is_compatible(dev, "fsl,qoriq-gpio") ||
|
||||
device_is_compatible(dev, "fsl,ls1028a-gpio") ||
|
||||
device_is_compatible(dev, "fsl,ls1088a-gpio") ||
|
||||
is_acpi_node(fwnode)) {
|
||||
gc->write_reg(mpc8xxx_gc->regs + GPIO_IBE, 0xffffffff);
|
||||
/* Also, latch state of GPIOs configured as output by bootloader. */
|
||||
@ -381,9 +376,9 @@ static int mpc8xxx_probe(struct platform_device *pdev)
|
||||
gc->read_reg(mpc8xxx_gc->regs + GPIO_DIR);
|
||||
}
|
||||
|
||||
ret = devm_gpiochip_add_data(&pdev->dev, gc, mpc8xxx_gc);
|
||||
ret = devm_gpiochip_add_data(dev, gc, mpc8xxx_gc);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
dev_err(dev,
|
||||
"GPIO chip registration failed with status %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
@ -404,18 +399,17 @@ static int mpc8xxx_probe(struct platform_device *pdev)
|
||||
gc->write_reg(mpc8xxx_gc->regs + GPIO_IER, 0xffffffff);
|
||||
gc->write_reg(mpc8xxx_gc->regs + GPIO_IMR, 0);
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, mpc8xxx_gc->irqn,
|
||||
ret = devm_request_irq(dev, mpc8xxx_gc->irqn,
|
||||
mpc8xxx_gpio_irq_cascade,
|
||||
IRQF_NO_THREAD | IRQF_SHARED, "gpio-cascade",
|
||||
mpc8xxx_gc);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"failed to devm_request_irq(%d), ret = %d\n",
|
||||
dev_err(dev, "failed to devm_request_irq(%d), ret = %d\n",
|
||||
mpc8xxx_gc->irqn, ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
device_init_wakeup(&pdev->dev, true);
|
||||
device_init_wakeup(dev, true);
|
||||
|
||||
return 0;
|
||||
err:
|
||||
@ -466,7 +460,7 @@ MODULE_DEVICE_TABLE(acpi, gpio_acpi_ids);
|
||||
|
||||
static struct platform_driver mpc8xxx_plat_driver = {
|
||||
.probe = mpc8xxx_probe,
|
||||
.remove_new = mpc8xxx_remove,
|
||||
.remove = mpc8xxx_remove,
|
||||
.driver = {
|
||||
.name = "gpio-mpc8xxx",
|
||||
.of_match_table = mpc8xxx_gpio_ids,
|
||||
|
188
drivers/gpio/gpio-mpfs.c
Normal file
188
drivers/gpio/gpio-mpfs.c
Normal file
@ -0,0 +1,188 @@
|
||||
// SPDX-License-Identifier: (GPL-2.0)
|
||||
/*
|
||||
* Microchip PolarFire SoC (MPFS) GPIO controller driver
|
||||
*
|
||||
* Copyright (c) 2018-2024 Microchip Technology Inc. and its subsidiaries
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
#define MPFS_GPIO_CTRL(i) (0x4 * (i))
|
||||
#define MPFS_MAX_NUM_GPIO 32
|
||||
#define MPFS_GPIO_EN_INT 3
|
||||
#define MPFS_GPIO_EN_OUT_BUF BIT(2)
|
||||
#define MPFS_GPIO_EN_IN BIT(1)
|
||||
#define MPFS_GPIO_EN_OUT BIT(0)
|
||||
#define MPFS_GPIO_DIR_MASK GENMASK(2, 0)
|
||||
|
||||
#define MPFS_GPIO_TYPE_INT_EDGE_BOTH 0x80
|
||||
#define MPFS_GPIO_TYPE_INT_EDGE_NEG 0x60
|
||||
#define MPFS_GPIO_TYPE_INT_EDGE_POS 0x40
|
||||
#define MPFS_GPIO_TYPE_INT_LEVEL_LOW 0x20
|
||||
#define MPFS_GPIO_TYPE_INT_LEVEL_HIGH 0x00
|
||||
#define MPFS_GPIO_TYPE_INT_MASK GENMASK(7, 5)
|
||||
#define MPFS_IRQ_REG 0x80
|
||||
|
||||
#define MPFS_INP_REG 0x84
|
||||
#define COREGPIO_INP_REG 0x90
|
||||
#define MPFS_OUTP_REG 0x88
|
||||
#define COREGPIO_OUTP_REG 0xA0
|
||||
|
||||
struct mpfs_gpio_reg_offsets {
|
||||
u8 inp;
|
||||
u8 outp;
|
||||
};
|
||||
|
||||
struct mpfs_gpio_chip {
|
||||
struct regmap *regs;
|
||||
const struct mpfs_gpio_reg_offsets *offsets;
|
||||
struct gpio_chip gc;
|
||||
};
|
||||
|
||||
static const struct regmap_config mpfs_gpio_regmap_config = {
|
||||
.reg_bits = 32,
|
||||
.reg_stride = 4,
|
||||
.val_bits = 32,
|
||||
};
|
||||
|
||||
static int mpfs_gpio_direction_input(struct gpio_chip *gc, unsigned int gpio_index)
|
||||
{
|
||||
struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc);
|
||||
|
||||
regmap_update_bits(mpfs_gpio->regs, MPFS_GPIO_CTRL(gpio_index),
|
||||
MPFS_GPIO_DIR_MASK, MPFS_GPIO_EN_IN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpfs_gpio_direction_output(struct gpio_chip *gc, unsigned int gpio_index, int value)
|
||||
{
|
||||
struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc);
|
||||
|
||||
regmap_update_bits(mpfs_gpio->regs, MPFS_GPIO_CTRL(gpio_index),
|
||||
MPFS_GPIO_DIR_MASK, MPFS_GPIO_EN_IN);
|
||||
regmap_update_bits(mpfs_gpio->regs, mpfs_gpio->offsets->outp, BIT(gpio_index),
|
||||
value << gpio_index);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpfs_gpio_get_direction(struct gpio_chip *gc,
|
||||
unsigned int gpio_index)
|
||||
{
|
||||
struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc);
|
||||
unsigned int gpio_cfg;
|
||||
|
||||
regmap_read(mpfs_gpio->regs, MPFS_GPIO_CTRL(gpio_index), &gpio_cfg);
|
||||
if (gpio_cfg & MPFS_GPIO_EN_IN)
|
||||
return GPIO_LINE_DIRECTION_IN;
|
||||
|
||||
return GPIO_LINE_DIRECTION_OUT;
|
||||
}
|
||||
|
||||
static int mpfs_gpio_get(struct gpio_chip *gc, unsigned int gpio_index)
|
||||
{
|
||||
struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc);
|
||||
|
||||
if (mpfs_gpio_get_direction(gc, gpio_index) == GPIO_LINE_DIRECTION_OUT)
|
||||
return regmap_test_bits(mpfs_gpio->regs, mpfs_gpio->offsets->outp, BIT(gpio_index));
|
||||
else
|
||||
return regmap_test_bits(mpfs_gpio->regs, mpfs_gpio->offsets->inp, BIT(gpio_index));
|
||||
}
|
||||
|
||||
static void mpfs_gpio_set(struct gpio_chip *gc, unsigned int gpio_index, int value)
|
||||
{
|
||||
struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc);
|
||||
|
||||
mpfs_gpio_get(gc, gpio_index);
|
||||
|
||||
regmap_update_bits(mpfs_gpio->regs, mpfs_gpio->offsets->outp, BIT(gpio_index),
|
||||
value << gpio_index);
|
||||
|
||||
mpfs_gpio_get(gc, gpio_index);
|
||||
}
|
||||
|
||||
static int mpfs_gpio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct mpfs_gpio_chip *mpfs_gpio;
|
||||
struct clk *clk;
|
||||
void __iomem *base;
|
||||
int ngpios;
|
||||
|
||||
mpfs_gpio = devm_kzalloc(dev, sizeof(*mpfs_gpio), GFP_KERNEL);
|
||||
if (!mpfs_gpio)
|
||||
return -ENOMEM;
|
||||
|
||||
mpfs_gpio->offsets = device_get_match_data(&pdev->dev);
|
||||
|
||||
base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(base))
|
||||
return dev_err_probe(dev, PTR_ERR(base), "failed to ioremap memory resource\n");
|
||||
|
||||
mpfs_gpio->regs = devm_regmap_init_mmio(dev, base, &mpfs_gpio_regmap_config);
|
||||
if (IS_ERR(mpfs_gpio->regs))
|
||||
return dev_err_probe(dev, PTR_ERR(mpfs_gpio->regs),
|
||||
"failed to initialise regmap\n");
|
||||
|
||||
clk = devm_clk_get_enabled(dev, NULL);
|
||||
if (IS_ERR(clk))
|
||||
return dev_err_probe(dev, PTR_ERR(clk), "failed to get and enable clock\n");
|
||||
|
||||
ngpios = MPFS_MAX_NUM_GPIO;
|
||||
device_property_read_u32(dev, "ngpios", &ngpios);
|
||||
if (ngpios > MPFS_MAX_NUM_GPIO)
|
||||
ngpios = MPFS_MAX_NUM_GPIO;
|
||||
|
||||
mpfs_gpio->gc.direction_input = mpfs_gpio_direction_input;
|
||||
mpfs_gpio->gc.direction_output = mpfs_gpio_direction_output;
|
||||
mpfs_gpio->gc.get_direction = mpfs_gpio_get_direction;
|
||||
mpfs_gpio->gc.get = mpfs_gpio_get;
|
||||
mpfs_gpio->gc.set = mpfs_gpio_set;
|
||||
mpfs_gpio->gc.base = -1;
|
||||
mpfs_gpio->gc.ngpio = ngpios;
|
||||
mpfs_gpio->gc.label = dev_name(dev);
|
||||
mpfs_gpio->gc.parent = dev;
|
||||
mpfs_gpio->gc.owner = THIS_MODULE;
|
||||
|
||||
return devm_gpiochip_add_data(dev, &mpfs_gpio->gc, mpfs_gpio);
|
||||
}
|
||||
|
||||
static const struct mpfs_gpio_reg_offsets mpfs_reg_offsets = {
|
||||
.inp = MPFS_INP_REG,
|
||||
.outp = MPFS_OUTP_REG,
|
||||
};
|
||||
|
||||
static const struct mpfs_gpio_reg_offsets coregpio_reg_offsets = {
|
||||
.inp = COREGPIO_INP_REG,
|
||||
.outp = COREGPIO_OUTP_REG,
|
||||
};
|
||||
|
||||
static const struct of_device_id mpfs_gpio_of_ids[] = {
|
||||
{
|
||||
.compatible = "microchip,mpfs-gpio",
|
||||
.data = &mpfs_reg_offsets,
|
||||
}, {
|
||||
.compatible = "microchip,coregpio-rtl-v3",
|
||||
.data = &coregpio_reg_offsets,
|
||||
},
|
||||
{ /* end of list */ }
|
||||
};
|
||||
|
||||
static struct platform_driver mpfs_gpio_driver = {
|
||||
.probe = mpfs_gpio_probe,
|
||||
.driver = {
|
||||
.name = "microchip,mpfs-gpio",
|
||||
.of_match_table = mpfs_gpio_of_ids,
|
||||
},
|
||||
};
|
||||
builtin_platform_driver(mpfs_gpio_driver);
|
527
drivers/gpio/gpio-mpsse.c
Normal file
527
drivers/gpio/gpio-mpsse.c
Normal file
@ -0,0 +1,527 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* FTDI MPSSE GPIO support
|
||||
*
|
||||
* Based on code by Anatolij Gustschin
|
||||
*
|
||||
* Copyright (C) 2024 Mary Strodl <mstrodl@csh.rit.edu>
|
||||
*/
|
||||
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/usb.h>
|
||||
|
||||
struct mpsse_priv {
|
||||
struct gpio_chip gpio;
|
||||
struct usb_device *udev; /* USB device encompassing all MPSSEs */
|
||||
struct usb_interface *intf; /* USB interface for this MPSSE */
|
||||
u8 intf_id; /* USB interface number for this MPSSE */
|
||||
struct work_struct irq_work; /* polling work thread */
|
||||
struct mutex irq_mutex; /* lock over irq_data */
|
||||
atomic_t irq_type[16]; /* pin -> edge detection type */
|
||||
atomic_t irq_enabled;
|
||||
int id;
|
||||
|
||||
u8 gpio_outputs[2]; /* Output states for GPIOs [L, H] */
|
||||
u8 gpio_dir[2]; /* Directions for GPIOs [L, H] */
|
||||
|
||||
u8 *bulk_in_buf; /* Extra recv buffer to grab status bytes */
|
||||
|
||||
struct usb_endpoint_descriptor *bulk_in;
|
||||
struct usb_endpoint_descriptor *bulk_out;
|
||||
|
||||
struct mutex io_mutex; /* sync I/O with disconnect */
|
||||
};
|
||||
|
||||
struct bulk_desc {
|
||||
bool tx; /* direction of bulk transfer */
|
||||
u8 *data; /* input (tx) or output (rx) */
|
||||
int len; /* Length of `data` if tx, or length of */
|
||||
/* Data to read if rx */
|
||||
int len_actual; /* Length successfully transferred */
|
||||
int timeout;
|
||||
};
|
||||
|
||||
static const struct usb_device_id gpio_mpsse_table[] = {
|
||||
{ USB_DEVICE(0x0c52, 0xa064) }, /* SeaLevel Systems, Inc. */
|
||||
{ } /* Terminating entry */
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(usb, gpio_mpsse_table);
|
||||
|
||||
static DEFINE_IDA(gpio_mpsse_ida);
|
||||
|
||||
/* MPSSE commands */
|
||||
#define SET_BITS_CMD 0x80
|
||||
#define GET_BITS_CMD 0x81
|
||||
|
||||
#define SET_BITMODE_REQUEST 0x0B
|
||||
#define MODE_MPSSE (2 << 8)
|
||||
#define MODE_RESET 0
|
||||
|
||||
/* Arbitrarily decided. This could probably be much less */
|
||||
#define MPSSE_WRITE_TIMEOUT 5000
|
||||
#define MPSSE_READ_TIMEOUT 5000
|
||||
|
||||
/* 1 millisecond, also pretty arbitrary */
|
||||
#define MPSSE_POLL_INTERVAL 1000
|
||||
|
||||
static int mpsse_bulk_xfer(struct usb_interface *intf, struct bulk_desc *desc)
|
||||
{
|
||||
struct mpsse_priv *priv = usb_get_intfdata(intf);
|
||||
struct usb_device *udev = priv->udev;
|
||||
unsigned int pipe;
|
||||
int ret;
|
||||
|
||||
if (desc->tx)
|
||||
pipe = usb_sndbulkpipe(udev, priv->bulk_out->bEndpointAddress);
|
||||
else
|
||||
pipe = usb_rcvbulkpipe(udev, priv->bulk_in->bEndpointAddress);
|
||||
|
||||
ret = usb_bulk_msg(udev, pipe, desc->data, desc->len,
|
||||
&desc->len_actual, desc->timeout);
|
||||
if (ret)
|
||||
dev_dbg(&udev->dev, "mpsse: bulk transfer failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mpsse_write(struct usb_interface *intf,
|
||||
u8 *buf, size_t len)
|
||||
{
|
||||
int ret;
|
||||
struct bulk_desc desc;
|
||||
|
||||
desc.len_actual = 0;
|
||||
desc.tx = true;
|
||||
desc.data = buf;
|
||||
desc.len = len;
|
||||
desc.timeout = MPSSE_WRITE_TIMEOUT;
|
||||
|
||||
ret = mpsse_bulk_xfer(intf, &desc);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mpsse_read(struct usb_interface *intf, u8 *buf, size_t len)
|
||||
{
|
||||
int ret;
|
||||
struct bulk_desc desc;
|
||||
struct mpsse_priv *priv = usb_get_intfdata(intf);
|
||||
|
||||
desc.len_actual = 0;
|
||||
desc.tx = false;
|
||||
desc.data = priv->bulk_in_buf;
|
||||
/* Device sends 2 additional status bytes, read len + 2 */
|
||||
desc.len = min_t(size_t, len + 2, usb_endpoint_maxp(priv->bulk_in));
|
||||
desc.timeout = MPSSE_READ_TIMEOUT;
|
||||
|
||||
ret = mpsse_bulk_xfer(intf, &desc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Did we get enough data? */
|
||||
if (desc.len_actual < desc.len)
|
||||
return -EIO;
|
||||
|
||||
memcpy(buf, desc.data + 2, desc.len_actual - 2);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int gpio_mpsse_set_bank(struct mpsse_priv *priv, u8 bank)
|
||||
{
|
||||
int ret;
|
||||
u8 tx_buf[3] = {
|
||||
SET_BITS_CMD | (bank << 1),
|
||||
priv->gpio_outputs[bank],
|
||||
priv->gpio_dir[bank],
|
||||
};
|
||||
|
||||
ret = mpsse_write(priv->intf, tx_buf, 3);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int gpio_mpsse_get_bank(struct mpsse_priv *priv, u8 bank)
|
||||
{
|
||||
int ret;
|
||||
u8 buf = GET_BITS_CMD | (bank << 1);
|
||||
|
||||
ret = mpsse_write(priv->intf, &buf, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = mpsse_read(priv->intf, &buf, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void gpio_mpsse_set_multiple(struct gpio_chip *chip, unsigned long *mask,
|
||||
unsigned long *bits)
|
||||
{
|
||||
unsigned long i, bank, bank_mask, bank_bits;
|
||||
int ret;
|
||||
struct mpsse_priv *priv = gpiochip_get_data(chip);
|
||||
|
||||
guard(mutex)(&priv->io_mutex);
|
||||
for_each_set_clump8(i, bank_mask, mask, chip->ngpio) {
|
||||
bank = i / 8;
|
||||
|
||||
if (bank_mask) {
|
||||
bank_bits = bitmap_get_value8(bits, i);
|
||||
/* Zero out pins we want to change */
|
||||
priv->gpio_outputs[bank] &= ~bank_mask;
|
||||
/* Set pins we care about */
|
||||
priv->gpio_outputs[bank] |= bank_bits & bank_mask;
|
||||
|
||||
ret = gpio_mpsse_set_bank(priv, bank);
|
||||
if (ret)
|
||||
dev_err(&priv->intf->dev,
|
||||
"Couldn't set values for bank %ld!",
|
||||
bank);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int gpio_mpsse_get_multiple(struct gpio_chip *chip, unsigned long *mask,
|
||||
unsigned long *bits)
|
||||
{
|
||||
unsigned long i, bank, bank_mask;
|
||||
int ret;
|
||||
struct mpsse_priv *priv = gpiochip_get_data(chip);
|
||||
|
||||
guard(mutex)(&priv->io_mutex);
|
||||
for_each_set_clump8(i, bank_mask, mask, chip->ngpio) {
|
||||
bank = i / 8;
|
||||
|
||||
if (bank_mask) {
|
||||
ret = gpio_mpsse_get_bank(priv, bank);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
bitmap_set_value8(bits, ret & bank_mask, i);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gpio_mpsse_gpio_get(struct gpio_chip *chip, unsigned int offset)
|
||||
{
|
||||
int err;
|
||||
unsigned long mask = 0, bits = 0;
|
||||
|
||||
__set_bit(offset, &mask);
|
||||
err = gpio_mpsse_get_multiple(chip, &mask, &bits);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* == is not guaranteed to give 1 if true */
|
||||
if (bits)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gpio_mpsse_gpio_set(struct gpio_chip *chip, unsigned int offset,
|
||||
int value)
|
||||
{
|
||||
unsigned long mask = 0, bits = 0;
|
||||
|
||||
__set_bit(offset, &mask);
|
||||
if (value)
|
||||
__set_bit(offset, &bits);
|
||||
|
||||
gpio_mpsse_set_multiple(chip, &mask, &bits);
|
||||
}
|
||||
|
||||
static int gpio_mpsse_direction_output(struct gpio_chip *chip,
|
||||
unsigned int offset, int value)
|
||||
{
|
||||
struct mpsse_priv *priv = gpiochip_get_data(chip);
|
||||
int bank = (offset & 8) >> 3;
|
||||
int bank_offset = offset & 7;
|
||||
|
||||
scoped_guard(mutex, &priv->io_mutex)
|
||||
priv->gpio_dir[bank] |= BIT(bank_offset);
|
||||
|
||||
gpio_mpsse_gpio_set(chip, offset, value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gpio_mpsse_direction_input(struct gpio_chip *chip,
|
||||
unsigned int offset)
|
||||
{
|
||||
struct mpsse_priv *priv = gpiochip_get_data(chip);
|
||||
int bank = (offset & 8) >> 3;
|
||||
int bank_offset = offset & 7;
|
||||
|
||||
guard(mutex)(&priv->io_mutex);
|
||||
priv->gpio_dir[bank] &= ~BIT(bank_offset);
|
||||
gpio_mpsse_set_bank(priv, bank);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gpio_mpsse_get_direction(struct gpio_chip *chip,
|
||||
unsigned int offset)
|
||||
{
|
||||
int ret;
|
||||
int bank = (offset & 8) >> 3;
|
||||
int bank_offset = offset & 7;
|
||||
struct mpsse_priv *priv = gpiochip_get_data(chip);
|
||||
|
||||
guard(mutex)(&priv->io_mutex);
|
||||
/* MPSSE directions are inverted */
|
||||
if (priv->gpio_dir[bank] & BIT(bank_offset))
|
||||
ret = GPIO_LINE_DIRECTION_OUT;
|
||||
else
|
||||
ret = GPIO_LINE_DIRECTION_IN;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void gpio_mpsse_poll(struct work_struct *work)
|
||||
{
|
||||
unsigned long pin_mask, pin_states, flags;
|
||||
int irq_enabled, offset, err, value, fire_irq,
|
||||
irq, old_value[16], irq_type[16];
|
||||
struct mpsse_priv *priv = container_of(work, struct mpsse_priv,
|
||||
irq_work);
|
||||
|
||||
for (offset = 0; offset < priv->gpio.ngpio; ++offset)
|
||||
old_value[offset] = -1;
|
||||
|
||||
while ((irq_enabled = atomic_read(&priv->irq_enabled))) {
|
||||
usleep_range(MPSSE_POLL_INTERVAL, MPSSE_POLL_INTERVAL + 1000);
|
||||
/* Cleanup will trigger at the end of the loop */
|
||||
guard(mutex)(&priv->irq_mutex);
|
||||
|
||||
pin_mask = 0;
|
||||
pin_states = 0;
|
||||
for (offset = 0; offset < priv->gpio.ngpio; ++offset) {
|
||||
irq_type[offset] = atomic_read(&priv->irq_type[offset]);
|
||||
if (irq_type[offset] != IRQ_TYPE_NONE &&
|
||||
irq_enabled & BIT(offset))
|
||||
pin_mask |= BIT(offset);
|
||||
else
|
||||
old_value[offset] = -1;
|
||||
}
|
||||
|
||||
err = gpio_mpsse_get_multiple(&priv->gpio, &pin_mask,
|
||||
&pin_states);
|
||||
if (err) {
|
||||
dev_err_ratelimited(&priv->intf->dev,
|
||||
"Error polling!\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check each value */
|
||||
for (offset = 0; offset < priv->gpio.ngpio; ++offset) {
|
||||
if (old_value[offset] == -1)
|
||||
continue;
|
||||
|
||||
fire_irq = 0;
|
||||
value = pin_states & BIT(offset);
|
||||
|
||||
switch (irq_type[offset]) {
|
||||
case IRQ_TYPE_EDGE_RISING:
|
||||
fire_irq = value > old_value[offset];
|
||||
break;
|
||||
case IRQ_TYPE_EDGE_FALLING:
|
||||
fire_irq = value < old_value[offset];
|
||||
break;
|
||||
case IRQ_TYPE_EDGE_BOTH:
|
||||
fire_irq = value != old_value[offset];
|
||||
break;
|
||||
}
|
||||
if (!fire_irq)
|
||||
continue;
|
||||
|
||||
irq = irq_find_mapping(priv->gpio.irq.domain,
|
||||
offset);
|
||||
local_irq_save(flags);
|
||||
generic_handle_irq(irq);
|
||||
local_irq_disable();
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
/* Sync back values so we can refer to them next tick */
|
||||
for (offset = 0; offset < priv->gpio.ngpio; ++offset)
|
||||
if (irq_type[offset] != IRQ_TYPE_NONE &&
|
||||
irq_enabled & BIT(offset))
|
||||
old_value[offset] = pin_states & BIT(offset);
|
||||
}
|
||||
}
|
||||
|
||||
static int gpio_mpsse_set_irq_type(struct irq_data *irqd, unsigned int type)
|
||||
{
|
||||
int offset;
|
||||
struct mpsse_priv *priv = irq_data_get_irq_chip_data(irqd);
|
||||
|
||||
offset = irqd->hwirq;
|
||||
atomic_set(&priv->irq_type[offset], type & IRQ_TYPE_EDGE_BOTH);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gpio_mpsse_irq_disable(struct irq_data *irqd)
|
||||
{
|
||||
struct mpsse_priv *priv = irq_data_get_irq_chip_data(irqd);
|
||||
|
||||
atomic_and(~BIT(irqd->hwirq), &priv->irq_enabled);
|
||||
gpiochip_disable_irq(&priv->gpio, irqd->hwirq);
|
||||
}
|
||||
|
||||
static void gpio_mpsse_irq_enable(struct irq_data *irqd)
|
||||
{
|
||||
struct mpsse_priv *priv = irq_data_get_irq_chip_data(irqd);
|
||||
|
||||
gpiochip_enable_irq(&priv->gpio, irqd->hwirq);
|
||||
/* If no-one else was using the IRQ, enable it */
|
||||
if (!atomic_fetch_or(BIT(irqd->hwirq), &priv->irq_enabled)) {
|
||||
INIT_WORK(&priv->irq_work, gpio_mpsse_poll);
|
||||
schedule_work(&priv->irq_work);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct irq_chip gpio_mpsse_irq_chip = {
|
||||
.name = "gpio-mpsse-irq",
|
||||
.irq_enable = gpio_mpsse_irq_enable,
|
||||
.irq_disable = gpio_mpsse_irq_disable,
|
||||
.irq_set_type = gpio_mpsse_set_irq_type,
|
||||
.flags = IRQCHIP_IMMUTABLE,
|
||||
GPIOCHIP_IRQ_RESOURCE_HELPERS,
|
||||
};
|
||||
|
||||
static void gpio_mpsse_ida_remove(void *data)
|
||||
{
|
||||
struct mpsse_priv *priv = data;
|
||||
|
||||
ida_simple_remove(&gpio_mpsse_ida, priv->id);
|
||||
}
|
||||
|
||||
static int gpio_mpsse_probe(struct usb_interface *interface,
|
||||
const struct usb_device_id *id)
|
||||
{
|
||||
struct mpsse_priv *priv;
|
||||
struct device *dev;
|
||||
int err;
|
||||
|
||||
dev = &interface->dev;
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->udev = usb_get_dev(interface_to_usbdev(interface));
|
||||
priv->intf = interface;
|
||||
priv->intf_id = interface->cur_altsetting->desc.bInterfaceNumber;
|
||||
|
||||
priv->id = ida_simple_get(&gpio_mpsse_ida, 0, 0, GFP_KERNEL);
|
||||
if (priv->id < 0)
|
||||
return priv->id;
|
||||
|
||||
err = devm_add_action_or_reset(dev, gpio_mpsse_ida_remove, priv);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = devm_mutex_init(dev, &priv->io_mutex);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = devm_mutex_init(dev, &priv->irq_mutex);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
priv->gpio.label = devm_kasprintf(dev, GFP_KERNEL,
|
||||
"gpio-mpsse.%d.%d",
|
||||
priv->id, priv->intf_id);
|
||||
if (!priv->gpio.label)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->gpio.owner = THIS_MODULE;
|
||||
priv->gpio.parent = interface->usb_dev;
|
||||
priv->gpio.get_direction = gpio_mpsse_get_direction;
|
||||
priv->gpio.direction_input = gpio_mpsse_direction_input;
|
||||
priv->gpio.direction_output = gpio_mpsse_direction_output;
|
||||
priv->gpio.get = gpio_mpsse_gpio_get;
|
||||
priv->gpio.set = gpio_mpsse_gpio_set;
|
||||
priv->gpio.get_multiple = gpio_mpsse_get_multiple;
|
||||
priv->gpio.set_multiple = gpio_mpsse_set_multiple;
|
||||
priv->gpio.base = -1;
|
||||
priv->gpio.ngpio = 16;
|
||||
priv->gpio.offset = priv->intf_id * priv->gpio.ngpio;
|
||||
priv->gpio.can_sleep = 1;
|
||||
|
||||
err = usb_find_common_endpoints(interface->cur_altsetting,
|
||||
&priv->bulk_in, &priv->bulk_out,
|
||||
NULL, NULL);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
priv->bulk_in_buf = devm_kmalloc(dev, usb_endpoint_maxp(priv->bulk_in),
|
||||
GFP_KERNEL);
|
||||
if (!priv->bulk_in_buf)
|
||||
return -ENOMEM;
|
||||
|
||||
usb_set_intfdata(interface, priv);
|
||||
|
||||
/* Reset mode, needed to correctly enter MPSSE mode */
|
||||
err = usb_control_msg(priv->udev, usb_sndctrlpipe(priv->udev, 0),
|
||||
SET_BITMODE_REQUEST,
|
||||
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
|
||||
MODE_RESET, priv->intf_id + 1, NULL, 0,
|
||||
USB_CTRL_SET_TIMEOUT);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Enter MPSSE mode */
|
||||
err = usb_control_msg(priv->udev, usb_sndctrlpipe(priv->udev, 0),
|
||||
SET_BITMODE_REQUEST,
|
||||
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
|
||||
MODE_MPSSE, priv->intf_id + 1, NULL, 0,
|
||||
USB_CTRL_SET_TIMEOUT);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
gpio_irq_chip_set_chip(&priv->gpio.irq, &gpio_mpsse_irq_chip);
|
||||
|
||||
priv->gpio.irq.parent_handler = NULL;
|
||||
priv->gpio.irq.num_parents = 0;
|
||||
priv->gpio.irq.parents = NULL;
|
||||
priv->gpio.irq.default_type = IRQ_TYPE_NONE;
|
||||
priv->gpio.irq.handler = handle_simple_irq;
|
||||
|
||||
err = devm_gpiochip_add_data(dev, &priv->gpio, priv);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gpio_mpsse_disconnect(struct usb_interface *intf)
|
||||
{
|
||||
struct mpsse_priv *priv = usb_get_intfdata(intf);
|
||||
|
||||
priv->intf = NULL;
|
||||
usb_set_intfdata(intf, NULL);
|
||||
usb_put_dev(priv->udev);
|
||||
}
|
||||
|
||||
static struct usb_driver gpio_mpsse_driver = {
|
||||
.name = "gpio-mpsse",
|
||||
.probe = gpio_mpsse_probe,
|
||||
.disconnect = gpio_mpsse_disconnect,
|
||||
.id_table = gpio_mpsse_table,
|
||||
};
|
||||
|
||||
module_usb_driver(gpio_mpsse_driver);
|
||||
|
||||
MODULE_AUTHOR("Mary Strodl <mstrodl@csh.rit.edu>");
|
||||
MODULE_DESCRIPTION("MPSSE GPIO driver");
|
||||
MODULE_LICENSE("GPL");
|
@ -794,8 +794,8 @@ static int mvebu_pwm_probe(struct platform_device *pdev,
|
||||
u32 set;
|
||||
|
||||
if (mvchip->soc_variant == MVEBU_GPIO_SOC_VARIANT_A8K) {
|
||||
int ret = of_property_read_u32(dev->of_node,
|
||||
"marvell,pwm-offset", &offset);
|
||||
int ret = device_property_read_u32(dev, "marvell,pwm-offset",
|
||||
&offset);
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
} else {
|
||||
@ -1106,7 +1106,7 @@ static int mvebu_gpio_probe_syscon(struct platform_device *pdev,
|
||||
if (IS_ERR(mvchip->regs))
|
||||
return PTR_ERR(mvchip->regs);
|
||||
|
||||
if (of_property_read_u32(pdev->dev.of_node, "offset", &mvchip->offset))
|
||||
if (device_property_read_u32(&pdev->dev, "offset", &mvchip->offset))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
@ -1147,7 +1147,7 @@ static int mvebu_gpio_probe(struct platform_device *pdev)
|
||||
|
||||
platform_set_drvdata(pdev, mvchip);
|
||||
|
||||
if (of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpios)) {
|
||||
if (device_property_read_u32(&pdev->dev, "ngpios", &ngpios)) {
|
||||
dev_err(&pdev->dev, "Missing ngpios OF property\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
@ -1557,7 +1557,7 @@ static const struct dev_pm_ops gpio_pm_ops = {
|
||||
|
||||
static struct platform_driver omap_gpio_driver = {
|
||||
.probe = omap_gpio_probe,
|
||||
.remove_new = omap_gpio_remove,
|
||||
.remove = omap_gpio_remove,
|
||||
.driver = {
|
||||
.name = "omap_gpio",
|
||||
.pm = &gpio_pm_ops,
|
||||
|
@ -70,24 +70,17 @@ static int idio_16_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||||
struct device *const dev = &pdev->dev;
|
||||
int err;
|
||||
const size_t pci_bar_index = 2;
|
||||
const char *const name = pci_name(pdev);
|
||||
struct idio_16_regmap_config config = {};
|
||||
void __iomem *regs;
|
||||
struct regmap *map;
|
||||
|
||||
err = pcim_enable_device(pdev);
|
||||
if (err) {
|
||||
dev_err(dev, "Failed to enable PCI device (%d)\n", err);
|
||||
return err;
|
||||
}
|
||||
if (err)
|
||||
return dev_err_probe(dev, err, "Failed to enable PCI device\n");
|
||||
|
||||
err = pcim_iomap_regions(pdev, BIT(pci_bar_index), name);
|
||||
if (err) {
|
||||
dev_err(dev, "Unable to map PCI I/O addresses (%d)\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
regs = pcim_iomap_table(pdev)[pci_bar_index];
|
||||
regs = pcim_iomap_region(pdev, pci_bar_index, pci_name(pdev));
|
||||
if (IS_ERR(regs))
|
||||
return dev_err_probe(dev, PTR_ERR(regs), "Unable to map PCI I/O addresses\n");
|
||||
|
||||
map = devm_regmap_init_mmio(dev, regs, &idio_16_regmap_config);
|
||||
if (IS_ERR(map))
|
||||
|
@ -305,19 +305,16 @@ static int idio_24_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||||
struct regmap_irq_chip_data *chip_data;
|
||||
|
||||
err = pcim_enable_device(pdev);
|
||||
if (err) {
|
||||
dev_err(dev, "Failed to enable PCI device (%d)\n", err);
|
||||
return err;
|
||||
}
|
||||
if (err)
|
||||
return dev_err_probe(dev, err, "Failed to enable PCI device\n");
|
||||
|
||||
err = pcim_iomap_regions(pdev, BIT(pci_plx_bar_index) | BIT(pci_bar_index), name);
|
||||
if (err) {
|
||||
dev_err(dev, "Unable to map PCI I/O addresses (%d)\n", err);
|
||||
return err;
|
||||
}
|
||||
pex8311_regs = pcim_iomap_region(pdev, pci_plx_bar_index, "pex8311");
|
||||
if (IS_ERR(pex8311_regs))
|
||||
return dev_err_probe(dev, PTR_ERR(pex8311_regs), "Unable to map PEX 8311 I/O addresses\n");
|
||||
|
||||
pex8311_regs = pcim_iomap_table(pdev)[pci_plx_bar_index];
|
||||
idio_24_regs = pcim_iomap_table(pdev)[pci_bar_index];
|
||||
idio_24_regs = pcim_iomap_region(pdev, pci_bar_index, name);
|
||||
if (IS_ERR(idio_24_regs))
|
||||
return dev_err_probe(dev, PTR_ERR(idio_24_regs), "Unable to map PCIe-IDIO-24 I/O addresses\n");
|
||||
|
||||
intcsr_map = devm_regmap_init_mmio(dev, pex8311_regs, &pex8311_intcsr_regmap_config);
|
||||
if (IS_ERR(intcsr_map))
|
||||
|
@ -657,7 +657,7 @@ static SIMPLE_DEV_PM_OPS(gpio_rcar_pm_ops, gpio_rcar_suspend, gpio_rcar_resume);
|
||||
|
||||
static struct platform_driver gpio_rcar_device_driver = {
|
||||
.probe = gpio_rcar_probe,
|
||||
.remove_new = gpio_rcar_remove,
|
||||
.remove = gpio_rcar_remove,
|
||||
.driver = {
|
||||
.name = "gpio_rcar",
|
||||
.pm = &gpio_rcar_pm_ops,
|
||||
|
@ -26,9 +26,16 @@
|
||||
#include "../pinctrl/core.h"
|
||||
#include "../pinctrl/pinctrl-rockchip.h"
|
||||
|
||||
/*
|
||||
* Version ID Register
|
||||
* Bits [31:24] - Major Version
|
||||
* Bits [23:16] - Minor Version
|
||||
* Bits [15:0] - Revision Number
|
||||
*/
|
||||
#define GPIO_TYPE_V1 (0) /* GPIO Version ID reserved */
|
||||
#define GPIO_TYPE_V2 (0x01000C2B) /* GPIO Version ID 0x01000C2B */
|
||||
#define GPIO_TYPE_V2_1 (0x0101157C) /* GPIO Version ID 0x0101157C */
|
||||
#define GPIO_TYPE_V2 (0x01000C2B)
|
||||
#define GPIO_TYPE_V2_1 (0x0101157C)
|
||||
#define GPIO_TYPE_V2_2 (0x010219C8)
|
||||
|
||||
static const struct rockchip_gpio_regs gpio_regs_v1 = {
|
||||
.port_dr = 0x00,
|
||||
@ -602,7 +609,7 @@ static int rockchip_gpiolib_register(struct rockchip_pin_bank *bank)
|
||||
* files which don't set the "gpio-ranges" property or systems that
|
||||
* utilize ACPI the driver has to call gpiochip_add_pin_range().
|
||||
*/
|
||||
if (!of_property_read_bool(bank->of_node, "gpio-ranges")) {
|
||||
if (!of_property_present(bank->of_node, "gpio-ranges")) {
|
||||
struct device_node *pctlnp = of_get_parent(bank->of_node);
|
||||
struct pinctrl_dev *pctldev = NULL;
|
||||
|
||||
@ -661,8 +668,10 @@ static int rockchip_get_bank_data(struct rockchip_pin_bank *bank)
|
||||
clk_prepare_enable(bank->clk);
|
||||
id = readl(bank->reg_base + gpio_regs_v2.version_id);
|
||||
|
||||
/* If not gpio v2, that is default to v1. */
|
||||
if (id == GPIO_TYPE_V2 || id == GPIO_TYPE_V2_1) {
|
||||
switch (id) {
|
||||
case GPIO_TYPE_V2:
|
||||
case GPIO_TYPE_V2_1:
|
||||
case GPIO_TYPE_V2_2:
|
||||
bank->gpio_regs = &gpio_regs_v2;
|
||||
bank->gpio_type = GPIO_TYPE_V2;
|
||||
bank->db_clk = of_clk_get(bank->of_node, 1);
|
||||
@ -671,9 +680,14 @@ static int rockchip_get_bank_data(struct rockchip_pin_bank *bank)
|
||||
clk_disable_unprepare(bank->clk);
|
||||
return -EINVAL;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
case GPIO_TYPE_V1:
|
||||
bank->gpio_regs = &gpio_regs_v1;
|
||||
bank->gpio_type = GPIO_TYPE_V1;
|
||||
break;
|
||||
default:
|
||||
dev_err(bank->dev, "unsupported version ID: 0x%08x\n", id);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -795,7 +809,7 @@ static const struct of_device_id rockchip_gpio_match[] = {
|
||||
|
||||
static struct platform_driver rockchip_gpio_driver = {
|
||||
.probe = rockchip_gpio_probe,
|
||||
.remove_new = rockchip_gpio_remove,
|
||||
.remove = rockchip_gpio_remove,
|
||||
.driver = {
|
||||
.name = "rockchip-gpio",
|
||||
.of_match_table = rockchip_gpio_match,
|
||||
|
@ -520,15 +520,12 @@ static int gpio_sim_add_bank(struct fwnode_handle *swnode, struct device *dev)
|
||||
static int gpio_sim_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct fwnode_handle *swnode;
|
||||
int ret;
|
||||
|
||||
device_for_each_child_node(dev, swnode) {
|
||||
device_for_each_child_node_scoped(dev, swnode) {
|
||||
ret = gpio_sim_add_bank(swnode, dev);
|
||||
if (ret) {
|
||||
fwnode_handle_put(swnode);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -313,7 +313,7 @@ MODULE_DEVICE_TABLE(of, gpio_la_poll_of_match);
|
||||
|
||||
static struct platform_driver gpio_la_poll_device_driver = {
|
||||
.probe = gpio_la_poll_probe,
|
||||
.remove_new = gpio_la_poll_remove,
|
||||
.remove = gpio_la_poll_remove,
|
||||
.driver = {
|
||||
.name = GPIO_LA_NAME,
|
||||
.of_match_table = gpio_la_poll_of_match,
|
||||
|
@ -235,7 +235,7 @@ MODULE_DEVICE_TABLE(of, tb10x_gpio_dt_ids);
|
||||
|
||||
static struct platform_driver tb10x_gpio_driver = {
|
||||
.probe = tb10x_gpio_probe,
|
||||
.remove_new = tb10x_gpio_remove,
|
||||
.remove = tb10x_gpio_remove,
|
||||
.driver = {
|
||||
.name = "tb10x-gpio",
|
||||
.of_match_table = tb10x_gpio_dt_ids,
|
||||
|
@ -8,8 +8,8 @@
|
||||
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#define DEFAULT_PIN_NUMBER 32
|
||||
@ -142,7 +142,7 @@ static int ts4900_gpio_probe(struct i2c_client *client)
|
||||
u32 ngpio;
|
||||
int ret;
|
||||
|
||||
if (of_property_read_u32(client->dev.of_node, "ngpios", &ngpio))
|
||||
if (device_property_read_u32(&client->dev, "ngpios", &ngpio))
|
||||
ngpio = DEFAULT_PIN_NUMBER;
|
||||
|
||||
priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
|
||||
@ -153,7 +153,7 @@ static int ts4900_gpio_probe(struct i2c_client *client)
|
||||
priv->gpio_chip.label = "ts4900-gpio";
|
||||
priv->gpio_chip.ngpio = ngpio;
|
||||
priv->gpio_chip.parent = &client->dev;
|
||||
priv->input_bit = (uintptr_t)of_device_get_match_data(&client->dev);
|
||||
priv->input_bit = (uintptr_t)device_get_match_data(&client->dev);
|
||||
|
||||
priv->regmap = devm_regmap_init_i2c(client, &ts4900_regmap_config);
|
||||
if (IS_ERR(priv->regmap)) {
|
||||
|
@ -433,7 +433,7 @@ static struct platform_driver ts5500_dio_driver = {
|
||||
.name = "ts5500-dio",
|
||||
},
|
||||
.probe = ts5500_dio_probe,
|
||||
.remove_new = ts5500_dio_remove,
|
||||
.remove = ts5500_dio_remove,
|
||||
.id_table = ts5500_dio_ids,
|
||||
};
|
||||
|
||||
|
@ -481,7 +481,7 @@ MODULE_DEVICE_TABLE(of, uniphier_gpio_match);
|
||||
|
||||
static struct platform_driver uniphier_gpio_driver = {
|
||||
.probe = uniphier_gpio_probe,
|
||||
.remove_new = uniphier_gpio_remove,
|
||||
.remove = uniphier_gpio_remove,
|
||||
.driver = {
|
||||
.name = "uniphier-gpio",
|
||||
.of_match_table = uniphier_gpio_match,
|
||||
|
@ -15,10 +15,9 @@
|
||||
#include <linux/io.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
|
||||
#define VF610_GPIO_PER_PORT 32
|
||||
|
||||
@ -297,7 +296,7 @@ static int vf610_gpio_probe(struct platform_device *pdev)
|
||||
if (!port)
|
||||
return -ENOMEM;
|
||||
|
||||
port->sdata = of_device_get_match_data(dev);
|
||||
port->sdata = device_get_match_data(dev);
|
||||
|
||||
dual_base = port->sdata->have_dual_base;
|
||||
|
||||
|
@ -8,20 +8,22 @@
|
||||
* Quan Nguyen <qnguyen@apm.com>.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/acpi.h>
|
||||
|
||||
#include "gpiolib-acpi.h"
|
||||
|
||||
/* Common property names */
|
||||
#define XGENE_NIRQ_PROPERTY "apm,nr-irqs"
|
||||
#define XGENE_NGPIO_PROPERTY "apm,nr-gpios"
|
||||
#define XGENE_IRQ_START_PROPERTY "apm,irq-start"
|
||||
|
||||
#define XGENE_DFLT_MAX_NGPIO 22
|
||||
#define XGENE_DFLT_MAX_NIRQ 6
|
||||
#define XGENE_DFLT_IRQ_START_PIN 8
|
||||
@ -252,18 +254,17 @@ static int xgene_gpio_sb_probe(struct platform_device *pdev)
|
||||
|
||||
/* Retrieve start irq pin, use default if property not found */
|
||||
priv->irq_start = XGENE_DFLT_IRQ_START_PIN;
|
||||
if (!device_property_read_u32(&pdev->dev,
|
||||
XGENE_IRQ_START_PROPERTY, &val32))
|
||||
if (!device_property_read_u32(&pdev->dev, "apm,irq-start", &val32))
|
||||
priv->irq_start = val32;
|
||||
|
||||
/* Retrieve number irqs, use default if property not found */
|
||||
priv->nirq = XGENE_DFLT_MAX_NIRQ;
|
||||
if (!device_property_read_u32(&pdev->dev, XGENE_NIRQ_PROPERTY, &val32))
|
||||
if (!device_property_read_u32(&pdev->dev, "apm,nr-irqs", &val32))
|
||||
priv->nirq = val32;
|
||||
|
||||
/* Retrieve number gpio, use default if property not found */
|
||||
priv->gc.ngpio = XGENE_DFLT_MAX_NGPIO;
|
||||
if (!device_property_read_u32(&pdev->dev, XGENE_NGPIO_PROPERTY, &val32))
|
||||
if (!device_property_read_u32(&pdev->dev, "apm,nr-gpios", &val32))
|
||||
priv->gc.ngpio = val32;
|
||||
|
||||
dev_info(&pdev->dev, "Support %d gpios, %d irqs start from pin %d\n",
|
||||
@ -305,27 +306,25 @@ static void xgene_gpio_sb_remove(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
static const struct of_device_id xgene_gpio_sb_of_match[] = {
|
||||
{.compatible = "apm,xgene-gpio-sb", },
|
||||
{},
|
||||
{ .compatible = "apm,xgene-gpio-sb" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, xgene_gpio_sb_of_match);
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
static const struct acpi_device_id xgene_gpio_sb_acpi_match[] = {
|
||||
{"APMC0D15", 0},
|
||||
{},
|
||||
{ "APMC0D15" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, xgene_gpio_sb_acpi_match);
|
||||
#endif
|
||||
|
||||
static struct platform_driver xgene_gpio_sb_driver = {
|
||||
.driver = {
|
||||
.name = "xgene-gpio-sb",
|
||||
.of_match_table = xgene_gpio_sb_of_match,
|
||||
.acpi_match_table = ACPI_PTR(xgene_gpio_sb_acpi_match),
|
||||
},
|
||||
.acpi_match_table = xgene_gpio_sb_acpi_match,
|
||||
},
|
||||
.probe = xgene_gpio_sb_probe,
|
||||
.remove_new = xgene_gpio_sb_remove,
|
||||
.remove = xgene_gpio_sb_remove,
|
||||
};
|
||||
module_platform_driver(xgene_gpio_sb_driver);
|
||||
|
||||
|
@ -316,7 +316,7 @@ static struct platform_driver bcm_iproc_gpio_driver = {
|
||||
.of_match_table = bcm_iproc_gpio_of_match,
|
||||
},
|
||||
.probe = iproc_gpio_probe,
|
||||
.remove_new = iproc_gpio_remove,
|
||||
.remove = iproc_gpio_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(bcm_iproc_gpio_driver);
|
||||
|
@ -15,9 +15,9 @@
|
||||
#include <linux/io.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/* Register Offset Definitions */
|
||||
@ -561,9 +561,9 @@ static const struct irq_chip xgpio_irq_chip = {
|
||||
*/
|
||||
static int xgpio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct xgpio_instance *chip;
|
||||
int status = 0;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
u32 is_dual = 0;
|
||||
u32 width[2];
|
||||
u32 state[2];
|
||||
@ -571,14 +571,14 @@ static int xgpio_probe(struct platform_device *pdev)
|
||||
struct gpio_irq_chip *girq;
|
||||
u32 temp;
|
||||
|
||||
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
|
||||
chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
|
||||
if (!chip)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, chip);
|
||||
|
||||
/* First, check if the device is dual-channel */
|
||||
of_property_read_u32(np, "xlnx,is-dual", &is_dual);
|
||||
device_property_read_u32(dev, "xlnx,is-dual", &is_dual);
|
||||
|
||||
/* Setup defaults */
|
||||
memset32(width, 0, ARRAY_SIZE(width));
|
||||
@ -586,14 +586,14 @@ static int xgpio_probe(struct platform_device *pdev)
|
||||
memset32(dir, 0xFFFFFFFF, ARRAY_SIZE(dir));
|
||||
|
||||
/* Update GPIO state shadow register with default value */
|
||||
of_property_read_u32(np, "xlnx,dout-default", &state[0]);
|
||||
of_property_read_u32(np, "xlnx,dout-default-2", &state[1]);
|
||||
device_property_read_u32(dev, "xlnx,dout-default", &state[0]);
|
||||
device_property_read_u32(dev, "xlnx,dout-default-2", &state[1]);
|
||||
|
||||
bitmap_from_arr32(chip->state, state, 64);
|
||||
|
||||
/* Update GPIO direction shadow register with default value */
|
||||
of_property_read_u32(np, "xlnx,tri-default", &dir[0]);
|
||||
of_property_read_u32(np, "xlnx,tri-default-2", &dir[1]);
|
||||
device_property_read_u32(dev, "xlnx,tri-default", &dir[0]);
|
||||
device_property_read_u32(dev, "xlnx,tri-default-2", &dir[1]);
|
||||
|
||||
bitmap_from_arr32(chip->dir, dir, 64);
|
||||
|
||||
@ -601,13 +601,13 @@ static int xgpio_probe(struct platform_device *pdev)
|
||||
* Check device node and parent device node for device width
|
||||
* and assume default width of 32
|
||||
*/
|
||||
if (of_property_read_u32(np, "xlnx,gpio-width", &width[0]))
|
||||
if (device_property_read_u32(dev, "xlnx,gpio-width", &width[0]))
|
||||
width[0] = 32;
|
||||
|
||||
if (width[0] > 32)
|
||||
return -EINVAL;
|
||||
|
||||
if (is_dual && of_property_read_u32(np, "xlnx,gpio2-width", &width[1]))
|
||||
if (is_dual && device_property_read_u32(dev, "xlnx,gpio2-width", &width[1]))
|
||||
width[1] = 32;
|
||||
|
||||
if (width[1] > 32)
|
||||
@ -624,7 +624,7 @@ static int xgpio_probe(struct platform_device *pdev)
|
||||
|
||||
chip->gc.base = -1;
|
||||
chip->gc.ngpio = bitmap_weight(chip->hw_map, 64);
|
||||
chip->gc.parent = &pdev->dev;
|
||||
chip->gc.parent = dev;
|
||||
chip->gc.direction_input = xgpio_dir_in;
|
||||
chip->gc.direction_output = xgpio_dir_out;
|
||||
chip->gc.get = xgpio_get;
|
||||
@ -633,21 +633,21 @@ static int xgpio_probe(struct platform_device *pdev)
|
||||
chip->gc.free = xgpio_free;
|
||||
chip->gc.set_multiple = xgpio_set_multiple;
|
||||
|
||||
chip->gc.label = dev_name(&pdev->dev);
|
||||
chip->gc.label = dev_name(dev);
|
||||
|
||||
chip->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(chip->regs)) {
|
||||
dev_err(&pdev->dev, "failed to ioremap memory resource\n");
|
||||
dev_err(dev, "failed to ioremap memory resource\n");
|
||||
return PTR_ERR(chip->regs);
|
||||
}
|
||||
|
||||
chip->clk = devm_clk_get_optional_enabled(&pdev->dev, NULL);
|
||||
chip->clk = devm_clk_get_optional_enabled(dev, NULL);
|
||||
if (IS_ERR(chip->clk))
|
||||
return dev_err_probe(&pdev->dev, PTR_ERR(chip->clk), "input clock not found.\n");
|
||||
return dev_err_probe(dev, PTR_ERR(chip->clk), "input clock not found.\n");
|
||||
|
||||
pm_runtime_get_noresume(&pdev->dev);
|
||||
pm_runtime_set_active(&pdev->dev);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
pm_runtime_get_noresume(dev);
|
||||
pm_runtime_set_active(dev);
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
xgpio_save_regs(chip);
|
||||
|
||||
@ -667,8 +667,7 @@ static int xgpio_probe(struct platform_device *pdev)
|
||||
gpio_irq_chip_set_chip(girq, &xgpio_irq_chip);
|
||||
girq->parent_handler = xgpio_irqhandler;
|
||||
girq->num_parents = 1;
|
||||
girq->parents = devm_kcalloc(&pdev->dev, 1,
|
||||
sizeof(*girq->parents),
|
||||
girq->parents = devm_kcalloc(dev, 1, sizeof(*girq->parents),
|
||||
GFP_KERNEL);
|
||||
if (!girq->parents) {
|
||||
status = -ENOMEM;
|
||||
@ -679,18 +678,18 @@ static int xgpio_probe(struct platform_device *pdev)
|
||||
girq->handler = handle_bad_irq;
|
||||
|
||||
skip_irq:
|
||||
status = devm_gpiochip_add_data(&pdev->dev, &chip->gc, chip);
|
||||
status = devm_gpiochip_add_data(dev, &chip->gc, chip);
|
||||
if (status) {
|
||||
dev_err(&pdev->dev, "failed to add GPIO chip\n");
|
||||
dev_err(dev, "failed to add GPIO chip\n");
|
||||
goto err_pm_put;
|
||||
}
|
||||
|
||||
pm_runtime_put(&pdev->dev);
|
||||
pm_runtime_put(dev);
|
||||
return 0;
|
||||
|
||||
err_pm_put:
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
pm_runtime_put_noidle(&pdev->dev);
|
||||
pm_runtime_disable(dev);
|
||||
pm_runtime_put_noidle(dev);
|
||||
return status;
|
||||
}
|
||||
|
||||
@ -703,7 +702,7 @@ MODULE_DEVICE_TABLE(of, xgpio_of_match);
|
||||
|
||||
static struct platform_driver xgpio_plat_driver = {
|
||||
.probe = xgpio_probe,
|
||||
.remove_new = xgpio_remove,
|
||||
.remove = xgpio_remove,
|
||||
.driver = {
|
||||
.name = "gpio-xilinx",
|
||||
.of_match_table = xgpio_of_match,
|
||||
|
@ -1023,7 +1023,7 @@ static struct platform_driver zynq_gpio_driver = {
|
||||
.of_match_table = zynq_gpio_of_match,
|
||||
},
|
||||
.probe = zynq_gpio_probe,
|
||||
.remove_new = zynq_gpio_remove,
|
||||
.remove = zynq_gpio_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(zynq_gpio_driver);
|
||||
|
@ -1315,9 +1315,8 @@ acpi_gpiochip_parse_own_gpio(struct acpi_gpio_chip *achip,
|
||||
static void acpi_gpiochip_scan_gpios(struct acpi_gpio_chip *achip)
|
||||
{
|
||||
struct gpio_chip *chip = achip->chip;
|
||||
struct fwnode_handle *fwnode;
|
||||
|
||||
device_for_each_child_node(chip->parent, fwnode) {
|
||||
device_for_each_child_node_scoped(chip->parent, fwnode) {
|
||||
unsigned long lflags;
|
||||
enum gpiod_flags dflags;
|
||||
struct gpio_desc *desc;
|
||||
@ -1335,7 +1334,6 @@ static void acpi_gpiochip_scan_gpios(struct acpi_gpio_chip *achip)
|
||||
ret = gpiod_hog(desc, name, lflags, dflags);
|
||||
if (ret) {
|
||||
dev_err(chip->parent, "Failed to hog GPIO\n");
|
||||
fwnode_handle_put(fwnode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -16,16 +16,15 @@
|
||||
#include <linux/hte.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irqreturn.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kfifo.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/rbtree.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/timekeeping.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/workqueue.h>
|
||||
@ -143,18 +142,22 @@ static int linehandle_validate_flags(u32 flags)
|
||||
|
||||
static void linehandle_flags_to_desc_flags(u32 lflags, unsigned long *flagsp)
|
||||
{
|
||||
assign_bit(FLAG_ACTIVE_LOW, flagsp,
|
||||
unsigned long flags = READ_ONCE(*flagsp);
|
||||
|
||||
assign_bit(FLAG_ACTIVE_LOW, &flags,
|
||||
lflags & GPIOHANDLE_REQUEST_ACTIVE_LOW);
|
||||
assign_bit(FLAG_OPEN_DRAIN, flagsp,
|
||||
assign_bit(FLAG_OPEN_DRAIN, &flags,
|
||||
lflags & GPIOHANDLE_REQUEST_OPEN_DRAIN);
|
||||
assign_bit(FLAG_OPEN_SOURCE, flagsp,
|
||||
assign_bit(FLAG_OPEN_SOURCE, &flags,
|
||||
lflags & GPIOHANDLE_REQUEST_OPEN_SOURCE);
|
||||
assign_bit(FLAG_PULL_UP, flagsp,
|
||||
assign_bit(FLAG_PULL_UP, &flags,
|
||||
lflags & GPIOHANDLE_REQUEST_BIAS_PULL_UP);
|
||||
assign_bit(FLAG_PULL_DOWN, flagsp,
|
||||
assign_bit(FLAG_PULL_DOWN, &flags,
|
||||
lflags & GPIOHANDLE_REQUEST_BIAS_PULL_DOWN);
|
||||
assign_bit(FLAG_BIAS_DISABLE, flagsp,
|
||||
assign_bit(FLAG_BIAS_DISABLE, &flags,
|
||||
lflags & GPIOHANDLE_REQUEST_BIAS_DISABLE);
|
||||
|
||||
WRITE_ONCE(*flagsp, flags);
|
||||
}
|
||||
|
||||
static long linehandle_set_config(struct linehandle_state *lh,
|
||||
@ -184,11 +187,11 @@ static long linehandle_set_config(struct linehandle_state *lh,
|
||||
if (lflags & GPIOHANDLE_REQUEST_OUTPUT) {
|
||||
int val = !!gcnf.default_values[i];
|
||||
|
||||
ret = gpiod_direction_output(desc, val);
|
||||
ret = gpiod_direction_output_nonotify(desc, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else {
|
||||
ret = gpiod_direction_input(desc);
|
||||
ret = gpiod_direction_input_nonotify(desc);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@ -359,11 +362,11 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip)
|
||||
if (lflags & GPIOHANDLE_REQUEST_OUTPUT) {
|
||||
int val = !!handlereq.default_values[i];
|
||||
|
||||
ret = gpiod_direction_output(desc, val);
|
||||
ret = gpiod_direction_output_nonotify(desc, val);
|
||||
if (ret)
|
||||
goto out_free_lh;
|
||||
} else if (lflags & GPIOHANDLE_REQUEST_INPUT) {
|
||||
ret = gpiod_direction_input(desc);
|
||||
ret = gpiod_direction_input_nonotify(desc);
|
||||
if (ret)
|
||||
goto out_free_lh;
|
||||
}
|
||||
@ -417,7 +420,6 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip)
|
||||
|
||||
/**
|
||||
* struct line - contains the state of a requested line
|
||||
* @node: to store the object in supinfo_tree if supplemental
|
||||
* @desc: the GPIO descriptor for this line.
|
||||
* @req: the corresponding line request
|
||||
* @irq: the interrupt triggered in response to events on this GPIO
|
||||
@ -430,7 +432,6 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip)
|
||||
* @line_seqno: the seqno for the current edge event in the sequence of
|
||||
* events for this line.
|
||||
* @work: the worker that implements software debouncing
|
||||
* @debounce_period_us: the debounce period in microseconds
|
||||
* @sw_debounced: flag indicating if the software debouncer is active
|
||||
* @level: the current debounced physical level of the line
|
||||
* @hdesc: the Hardware Timestamp Engine (HTE) descriptor
|
||||
@ -439,7 +440,6 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip)
|
||||
* @last_seqno: the last sequence number before debounce period expires
|
||||
*/
|
||||
struct line {
|
||||
struct rb_node node;
|
||||
struct gpio_desc *desc;
|
||||
/*
|
||||
* -- edge detector specific fields --
|
||||
@ -450,7 +450,7 @@ struct line {
|
||||
* The flags for the active edge detector configuration.
|
||||
*
|
||||
* edflags is set by linereq_create(), linereq_free(), and
|
||||
* linereq_set_config_unlocked(), which are themselves mutually
|
||||
* linereq_set_config(), which are themselves mutually
|
||||
* exclusive, and is accessed by edge_irq_thread(),
|
||||
* process_hw_ts_thread() and debounce_work_func(),
|
||||
* which can all live with a slightly stale value.
|
||||
@ -473,15 +473,6 @@ struct line {
|
||||
* -- debouncer specific fields --
|
||||
*/
|
||||
struct delayed_work work;
|
||||
/*
|
||||
* debounce_period_us is accessed by debounce_irq_handler() and
|
||||
* process_hw_ts() which are disabled when modified by
|
||||
* debounce_setup(), edge_detector_setup() or edge_detector_stop()
|
||||
* or can live with a stale version when updated by
|
||||
* edge_detector_update().
|
||||
* The modifying functions are themselves mutually exclusive.
|
||||
*/
|
||||
unsigned int debounce_period_us;
|
||||
/*
|
||||
* sw_debounce is accessed by linereq_set_config(), which is the
|
||||
* only setter, and linereq_get_values(), which can live with a
|
||||
@ -514,17 +505,6 @@ struct line {
|
||||
#endif /* CONFIG_HTE */
|
||||
};
|
||||
|
||||
/*
|
||||
* a rbtree of the struct lines containing supplemental info.
|
||||
* Used to populate gpio_v2_line_info with cdev specific fields not contained
|
||||
* in the struct gpio_desc.
|
||||
* A line is determined to contain supplemental information by
|
||||
* line_has_supinfo().
|
||||
*/
|
||||
static struct rb_root supinfo_tree = RB_ROOT;
|
||||
/* covers supinfo_tree */
|
||||
static DEFINE_SPINLOCK(supinfo_lock);
|
||||
|
||||
/**
|
||||
* struct linereq - contains the state of a userspace line request
|
||||
* @gdev: the GPIO device the line request pertains to
|
||||
@ -538,8 +518,7 @@ static DEFINE_SPINLOCK(supinfo_lock);
|
||||
* this line request. Note that this is not used when @num_lines is 1, as
|
||||
* the line_seqno is then the same and is cheaper to calculate.
|
||||
* @config_mutex: mutex for serializing ioctl() calls to ensure consistency
|
||||
* of configuration, particularly multi-step accesses to desc flags and
|
||||
* changes to supinfo status.
|
||||
* of configuration, particularly multi-step accesses to desc flags.
|
||||
* @lines: the lines held by this line request, with @num_lines elements.
|
||||
*/
|
||||
struct linereq {
|
||||
@ -555,103 +534,6 @@ struct linereq {
|
||||
struct line lines[] __counted_by(num_lines);
|
||||
};
|
||||
|
||||
static void supinfo_insert(struct line *line)
|
||||
{
|
||||
struct rb_node **new = &(supinfo_tree.rb_node), *parent = NULL;
|
||||
struct line *entry;
|
||||
|
||||
guard(spinlock)(&supinfo_lock);
|
||||
|
||||
while (*new) {
|
||||
entry = container_of(*new, struct line, node);
|
||||
|
||||
parent = *new;
|
||||
if (line->desc < entry->desc) {
|
||||
new = &((*new)->rb_left);
|
||||
} else if (line->desc > entry->desc) {
|
||||
new = &((*new)->rb_right);
|
||||
} else {
|
||||
/* this should never happen */
|
||||
WARN(1, "duplicate line inserted");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
rb_link_node(&line->node, parent, new);
|
||||
rb_insert_color(&line->node, &supinfo_tree);
|
||||
}
|
||||
|
||||
static void supinfo_erase(struct line *line)
|
||||
{
|
||||
guard(spinlock)(&supinfo_lock);
|
||||
|
||||
rb_erase(&line->node, &supinfo_tree);
|
||||
}
|
||||
|
||||
static struct line *supinfo_find(struct gpio_desc *desc)
|
||||
{
|
||||
struct rb_node *node = supinfo_tree.rb_node;
|
||||
struct line *line;
|
||||
|
||||
while (node) {
|
||||
line = container_of(node, struct line, node);
|
||||
if (desc < line->desc)
|
||||
node = node->rb_left;
|
||||
else if (desc > line->desc)
|
||||
node = node->rb_right;
|
||||
else
|
||||
return line;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void supinfo_to_lineinfo(struct gpio_desc *desc,
|
||||
struct gpio_v2_line_info *info)
|
||||
{
|
||||
struct gpio_v2_line_attribute *attr;
|
||||
struct line *line;
|
||||
|
||||
guard(spinlock)(&supinfo_lock);
|
||||
|
||||
line = supinfo_find(desc);
|
||||
if (!line)
|
||||
return;
|
||||
|
||||
attr = &info->attrs[info->num_attrs];
|
||||
attr->id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE;
|
||||
attr->debounce_period_us = READ_ONCE(line->debounce_period_us);
|
||||
info->num_attrs++;
|
||||
}
|
||||
|
||||
static inline bool line_has_supinfo(struct line *line)
|
||||
{
|
||||
return READ_ONCE(line->debounce_period_us);
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks line_has_supinfo() before and after the change to avoid unnecessary
|
||||
* supinfo_tree access.
|
||||
* Called indirectly by linereq_create() or linereq_set_config() so line
|
||||
* is already protected from concurrent changes.
|
||||
*/
|
||||
static void line_set_debounce_period(struct line *line,
|
||||
unsigned int debounce_period_us)
|
||||
{
|
||||
bool was_suppl = line_has_supinfo(line);
|
||||
|
||||
WRITE_ONCE(line->debounce_period_us, debounce_period_us);
|
||||
|
||||
/* if supinfo status is unchanged then we're done */
|
||||
if (line_has_supinfo(line) == was_suppl)
|
||||
return;
|
||||
|
||||
/* supinfo status has changed, so update the tree */
|
||||
if (was_suppl)
|
||||
supinfo_erase(line);
|
||||
else
|
||||
supinfo_insert(line);
|
||||
}
|
||||
|
||||
#define GPIO_V2_LINE_BIAS_FLAGS \
|
||||
(GPIO_V2_LINE_FLAG_BIAS_PULL_UP | \
|
||||
GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN | \
|
||||
@ -819,7 +701,7 @@ static enum hte_return process_hw_ts(struct hte_ts_data *ts, void *p)
|
||||
line->total_discard_seq++;
|
||||
line->last_seqno = ts->seq;
|
||||
mod_delayed_work(system_wq, &line->work,
|
||||
usecs_to_jiffies(READ_ONCE(line->debounce_period_us)));
|
||||
usecs_to_jiffies(READ_ONCE(line->desc->debounce_period_us)));
|
||||
} else {
|
||||
if (unlikely(ts->seq < line->line_seqno))
|
||||
return HTE_CB_HANDLED;
|
||||
@ -960,7 +842,7 @@ static irqreturn_t debounce_irq_handler(int irq, void *p)
|
||||
struct line *line = p;
|
||||
|
||||
mod_delayed_work(system_wq, &line->work,
|
||||
usecs_to_jiffies(READ_ONCE(line->debounce_period_us)));
|
||||
usecs_to_jiffies(READ_ONCE(line->desc->debounce_period_us)));
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
@ -1040,12 +922,13 @@ static int debounce_setup(struct line *line, unsigned int debounce_period_us)
|
||||
int ret, level, irq;
|
||||
char *label;
|
||||
|
||||
/* try hardware */
|
||||
ret = gpiod_set_debounce(line->desc, debounce_period_us);
|
||||
if (!ret) {
|
||||
line_set_debounce_period(line, debounce_period_us);
|
||||
return ret;
|
||||
}
|
||||
/*
|
||||
* Try hardware. Skip gpiod_set_config() to avoid emitting two
|
||||
* CHANGED_CONFIG line state events.
|
||||
*/
|
||||
ret = gpio_do_set_config(line->desc,
|
||||
pinconf_to_config_packed(PIN_CONFIG_INPUT_DEBOUNCE,
|
||||
debounce_period_us));
|
||||
if (ret != -ENOTSUPP)
|
||||
return ret;
|
||||
|
||||
@ -1128,7 +1011,8 @@ static void edge_detector_stop(struct line *line)
|
||||
cancel_delayed_work_sync(&line->work);
|
||||
WRITE_ONCE(line->sw_debounced, 0);
|
||||
WRITE_ONCE(line->edflags, 0);
|
||||
line_set_debounce_period(line, 0);
|
||||
if (line->desc)
|
||||
WRITE_ONCE(line->desc->debounce_period_us, 0);
|
||||
/* do not change line->level - see comment in debounced_value() */
|
||||
}
|
||||
|
||||
@ -1161,7 +1045,7 @@ static int edge_detector_setup(struct line *line,
|
||||
ret = debounce_setup(line, debounce_period_us);
|
||||
if (ret)
|
||||
return ret;
|
||||
line_set_debounce_period(line, debounce_period_us);
|
||||
WRITE_ONCE(line->desc->debounce_period_us, debounce_period_us);
|
||||
}
|
||||
|
||||
/* detection disabled or sw debouncer will provide edge detection */
|
||||
@ -1209,12 +1093,12 @@ static int edge_detector_update(struct line *line,
|
||||
gpio_v2_line_config_debounce_period(lc, line_idx);
|
||||
|
||||
if ((active_edflags == edflags) &&
|
||||
(READ_ONCE(line->debounce_period_us) == debounce_period_us))
|
||||
(READ_ONCE(line->desc->debounce_period_us) == debounce_period_us))
|
||||
return 0;
|
||||
|
||||
/* sw debounced and still will be...*/
|
||||
if (debounce_period_us && READ_ONCE(line->sw_debounced)) {
|
||||
line_set_debounce_period(line, debounce_period_us);
|
||||
WRITE_ONCE(line->desc->debounce_period_us, debounce_period_us);
|
||||
/*
|
||||
* ensure event fifo is initialised if edge detection
|
||||
* is now enabled.
|
||||
@ -1331,7 +1215,7 @@ static int gpio_v2_line_config_validate(struct gpio_v2_line_config *lc,
|
||||
if (lc->num_attrs > GPIO_V2_LINE_NUM_ATTRS_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
if (memchr_inv(lc->padding, 0, sizeof(lc->padding)))
|
||||
if (!mem_is_zero(lc->padding, sizeof(lc->padding)))
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < num_lines; i++) {
|
||||
@ -1348,38 +1232,42 @@ static int gpio_v2_line_config_validate(struct gpio_v2_line_config *lc,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gpio_v2_line_config_flags_to_desc_flags(u64 flags,
|
||||
static void gpio_v2_line_config_flags_to_desc_flags(u64 lflags,
|
||||
unsigned long *flagsp)
|
||||
{
|
||||
assign_bit(FLAG_ACTIVE_LOW, flagsp,
|
||||
flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW);
|
||||
unsigned long flags = READ_ONCE(*flagsp);
|
||||
|
||||
if (flags & GPIO_V2_LINE_FLAG_OUTPUT)
|
||||
set_bit(FLAG_IS_OUT, flagsp);
|
||||
else if (flags & GPIO_V2_LINE_FLAG_INPUT)
|
||||
clear_bit(FLAG_IS_OUT, flagsp);
|
||||
assign_bit(FLAG_ACTIVE_LOW, &flags,
|
||||
lflags & GPIO_V2_LINE_FLAG_ACTIVE_LOW);
|
||||
|
||||
assign_bit(FLAG_EDGE_RISING, flagsp,
|
||||
flags & GPIO_V2_LINE_FLAG_EDGE_RISING);
|
||||
assign_bit(FLAG_EDGE_FALLING, flagsp,
|
||||
flags & GPIO_V2_LINE_FLAG_EDGE_FALLING);
|
||||
if (lflags & GPIO_V2_LINE_FLAG_OUTPUT)
|
||||
set_bit(FLAG_IS_OUT, &flags);
|
||||
else if (lflags & GPIO_V2_LINE_FLAG_INPUT)
|
||||
clear_bit(FLAG_IS_OUT, &flags);
|
||||
|
||||
assign_bit(FLAG_OPEN_DRAIN, flagsp,
|
||||
flags & GPIO_V2_LINE_FLAG_OPEN_DRAIN);
|
||||
assign_bit(FLAG_OPEN_SOURCE, flagsp,
|
||||
flags & GPIO_V2_LINE_FLAG_OPEN_SOURCE);
|
||||
assign_bit(FLAG_EDGE_RISING, &flags,
|
||||
lflags & GPIO_V2_LINE_FLAG_EDGE_RISING);
|
||||
assign_bit(FLAG_EDGE_FALLING, &flags,
|
||||
lflags & GPIO_V2_LINE_FLAG_EDGE_FALLING);
|
||||
|
||||
assign_bit(FLAG_PULL_UP, flagsp,
|
||||
flags & GPIO_V2_LINE_FLAG_BIAS_PULL_UP);
|
||||
assign_bit(FLAG_PULL_DOWN, flagsp,
|
||||
flags & GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN);
|
||||
assign_bit(FLAG_BIAS_DISABLE, flagsp,
|
||||
flags & GPIO_V2_LINE_FLAG_BIAS_DISABLED);
|
||||
assign_bit(FLAG_OPEN_DRAIN, &flags,
|
||||
lflags & GPIO_V2_LINE_FLAG_OPEN_DRAIN);
|
||||
assign_bit(FLAG_OPEN_SOURCE, &flags,
|
||||
lflags & GPIO_V2_LINE_FLAG_OPEN_SOURCE);
|
||||
|
||||
assign_bit(FLAG_EVENT_CLOCK_REALTIME, flagsp,
|
||||
flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME);
|
||||
assign_bit(FLAG_EVENT_CLOCK_HTE, flagsp,
|
||||
flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE);
|
||||
assign_bit(FLAG_PULL_UP, &flags,
|
||||
lflags & GPIO_V2_LINE_FLAG_BIAS_PULL_UP);
|
||||
assign_bit(FLAG_PULL_DOWN, &flags,
|
||||
lflags & GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN);
|
||||
assign_bit(FLAG_BIAS_DISABLE, &flags,
|
||||
lflags & GPIO_V2_LINE_FLAG_BIAS_DISABLED);
|
||||
|
||||
assign_bit(FLAG_EVENT_CLOCK_REALTIME, &flags,
|
||||
lflags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME);
|
||||
assign_bit(FLAG_EVENT_CLOCK_HTE, &flags,
|
||||
lflags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE);
|
||||
|
||||
WRITE_ONCE(*flagsp, flags);
|
||||
}
|
||||
|
||||
static long linereq_get_values(struct linereq *lr, void __user *ip)
|
||||
@ -1546,11 +1434,11 @@ static long linereq_set_config(struct linereq *lr, void __user *ip)
|
||||
int val = gpio_v2_line_config_output_value(&lc, i);
|
||||
|
||||
edge_detector_stop(line);
|
||||
ret = gpiod_direction_output(desc, val);
|
||||
ret = gpiod_direction_output_nonotify(desc, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else {
|
||||
ret = gpiod_direction_input(desc);
|
||||
ret = gpiod_direction_input_nonotify(desc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -1669,7 +1557,6 @@ static ssize_t linereq_read(struct file *file, char __user *buf,
|
||||
|
||||
static void linereq_free(struct linereq *lr)
|
||||
{
|
||||
struct line *line;
|
||||
unsigned int i;
|
||||
|
||||
if (lr->device_unregistered_nb.notifier_call)
|
||||
@ -1677,14 +1564,10 @@ static void linereq_free(struct linereq *lr)
|
||||
&lr->device_unregistered_nb);
|
||||
|
||||
for (i = 0; i < lr->num_lines; i++) {
|
||||
line = &lr->lines[i];
|
||||
if (!line->desc)
|
||||
continue;
|
||||
|
||||
edge_detector_stop(line);
|
||||
if (line_has_supinfo(line))
|
||||
supinfo_erase(line);
|
||||
gpiod_free(line->desc);
|
||||
if (lr->lines[i].desc) {
|
||||
edge_detector_stop(&lr->lines[i]);
|
||||
gpiod_free(lr->lines[i].desc);
|
||||
}
|
||||
}
|
||||
kfifo_free(&lr->events);
|
||||
kfree(lr->label);
|
||||
@ -1746,7 +1629,7 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
|
||||
if ((ulr.num_lines == 0) || (ulr.num_lines > GPIO_V2_LINES_MAX))
|
||||
return -EINVAL;
|
||||
|
||||
if (memchr_inv(ulr.padding, 0, sizeof(ulr.padding)))
|
||||
if (!mem_is_zero(ulr.padding, sizeof(ulr.padding)))
|
||||
return -EINVAL;
|
||||
|
||||
lc = &ulr.config;
|
||||
@ -1818,11 +1701,11 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
|
||||
if (flags & GPIO_V2_LINE_FLAG_OUTPUT) {
|
||||
int val = gpio_v2_line_config_output_value(lc, i);
|
||||
|
||||
ret = gpiod_direction_output(desc, val);
|
||||
ret = gpiod_direction_output_nonotify(desc, val);
|
||||
if (ret)
|
||||
goto out_free_linereq;
|
||||
} else if (flags & GPIO_V2_LINE_FLAG_INPUT) {
|
||||
ret = gpiod_direction_input(desc);
|
||||
ret = gpiod_direction_input_nonotify(desc);
|
||||
if (ret)
|
||||
goto out_free_linereq;
|
||||
|
||||
@ -2353,8 +2236,9 @@ static void gpio_v2_line_info_changed_to_v1(
|
||||
#endif /* CONFIG_GPIO_CDEV_V1 */
|
||||
|
||||
static void gpio_desc_to_lineinfo(struct gpio_desc *desc,
|
||||
struct gpio_v2_line_info *info)
|
||||
struct gpio_v2_line_info *info, bool atomic)
|
||||
{
|
||||
u32 debounce_period_us;
|
||||
unsigned long dflags;
|
||||
const char *label;
|
||||
|
||||
@ -2391,12 +2275,14 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *desc,
|
||||
*/
|
||||
if (test_bit(FLAG_REQUESTED, &dflags) ||
|
||||
test_bit(FLAG_IS_HOGGED, &dflags) ||
|
||||
test_bit(FLAG_USED_AS_IRQ, &dflags) ||
|
||||
test_bit(FLAG_EXPORT, &dflags) ||
|
||||
test_bit(FLAG_SYSFS, &dflags) ||
|
||||
!gpiochip_line_is_valid(guard.gc, info->offset) ||
|
||||
!pinctrl_gpio_can_use_line(guard.gc, info->offset))
|
||||
!gpiochip_line_is_valid(guard.gc, info->offset)) {
|
||||
info->flags |= GPIO_V2_LINE_FLAG_USED;
|
||||
} else if (!atomic) {
|
||||
if (!pinctrl_gpio_can_use_line(guard.gc, info->offset))
|
||||
info->flags |= GPIO_V2_LINE_FLAG_USED;
|
||||
}
|
||||
|
||||
if (test_bit(FLAG_IS_OUT, &dflags))
|
||||
info->flags |= GPIO_V2_LINE_FLAG_OUTPUT;
|
||||
@ -2427,6 +2313,14 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *desc,
|
||||
info->flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME;
|
||||
else if (test_bit(FLAG_EVENT_CLOCK_HTE, &dflags))
|
||||
info->flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE;
|
||||
|
||||
debounce_period_us = READ_ONCE(desc->debounce_period_us);
|
||||
if (debounce_period_us) {
|
||||
info->attrs[info->num_attrs].id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE;
|
||||
info->attrs[info->num_attrs].debounce_period_us =
|
||||
debounce_period_us;
|
||||
info->num_attrs++;
|
||||
}
|
||||
}
|
||||
|
||||
struct gpio_chardev_data {
|
||||
@ -2439,6 +2333,7 @@ struct gpio_chardev_data {
|
||||
#ifdef CONFIG_GPIO_CDEV_V1
|
||||
atomic_t watch_abi_version;
|
||||
#endif
|
||||
struct file *fp;
|
||||
};
|
||||
|
||||
static int chipinfo_get(struct gpio_chardev_data *cdev, void __user *ip)
|
||||
@ -2494,7 +2389,7 @@ static int lineinfo_get_v1(struct gpio_chardev_data *cdev, void __user *ip,
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
gpio_desc_to_lineinfo(desc, &lineinfo_v2);
|
||||
gpio_desc_to_lineinfo(desc, &lineinfo_v2, false);
|
||||
gpio_v2_line_info_to_v1(&lineinfo_v2, &lineinfo);
|
||||
|
||||
if (copy_to_user(ip, &lineinfo, sizeof(lineinfo))) {
|
||||
@ -2516,7 +2411,7 @@ static int lineinfo_get(struct gpio_chardev_data *cdev, void __user *ip,
|
||||
if (copy_from_user(&lineinfo, ip, sizeof(lineinfo)))
|
||||
return -EFAULT;
|
||||
|
||||
if (memchr_inv(lineinfo.padding, 0, sizeof(lineinfo.padding)))
|
||||
if (!mem_is_zero(lineinfo.padding, sizeof(lineinfo.padding)))
|
||||
return -EINVAL;
|
||||
|
||||
desc = gpio_device_get_desc(cdev->gdev, lineinfo.offset);
|
||||
@ -2531,8 +2426,7 @@ static int lineinfo_get(struct gpio_chardev_data *cdev, void __user *ip,
|
||||
if (test_and_set_bit(lineinfo.offset, cdev->watched_lines))
|
||||
return -EBUSY;
|
||||
}
|
||||
gpio_desc_to_lineinfo(desc, &lineinfo);
|
||||
supinfo_to_lineinfo(desc, &lineinfo);
|
||||
gpio_desc_to_lineinfo(desc, &lineinfo, false);
|
||||
|
||||
if (copy_to_user(ip, &lineinfo, sizeof(lineinfo))) {
|
||||
if (watch)
|
||||
@ -2609,29 +2503,86 @@ static long gpio_ioctl_compat(struct file *file, unsigned int cmd,
|
||||
}
|
||||
#endif
|
||||
|
||||
struct lineinfo_changed_ctx {
|
||||
struct work_struct work;
|
||||
struct gpio_v2_line_info_changed chg;
|
||||
struct gpio_device *gdev;
|
||||
struct gpio_chardev_data *cdev;
|
||||
};
|
||||
|
||||
static void lineinfo_changed_func(struct work_struct *work)
|
||||
{
|
||||
struct lineinfo_changed_ctx *ctx =
|
||||
container_of(work, struct lineinfo_changed_ctx, work);
|
||||
struct gpio_chip *gc;
|
||||
int ret;
|
||||
|
||||
if (!(ctx->chg.info.flags & GPIO_V2_LINE_FLAG_USED)) {
|
||||
/*
|
||||
* If nobody set the USED flag earlier, let's see with pinctrl
|
||||
* now. We're doing this late because it's a sleeping function.
|
||||
* Pin functions are in general much more static and while it's
|
||||
* not 100% bullet-proof, it's good enough for most cases.
|
||||
*/
|
||||
scoped_guard(srcu, &ctx->gdev->srcu) {
|
||||
gc = srcu_dereference(ctx->gdev->chip, &ctx->gdev->srcu);
|
||||
if (gc &&
|
||||
!pinctrl_gpio_can_use_line(gc, ctx->chg.info.offset))
|
||||
ctx->chg.info.flags |= GPIO_V2_LINE_FLAG_USED;
|
||||
}
|
||||
}
|
||||
|
||||
ret = kfifo_in_spinlocked(&ctx->cdev->events, &ctx->chg, 1,
|
||||
&ctx->cdev->wait.lock);
|
||||
if (ret)
|
||||
wake_up_poll(&ctx->cdev->wait, EPOLLIN);
|
||||
else
|
||||
pr_debug_ratelimited("lineinfo event FIFO is full - event dropped\n");
|
||||
|
||||
gpio_device_put(ctx->gdev);
|
||||
fput(ctx->cdev->fp);
|
||||
kfree(ctx);
|
||||
}
|
||||
|
||||
static int lineinfo_changed_notify(struct notifier_block *nb,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
struct gpio_chardev_data *cdev =
|
||||
container_of(nb, struct gpio_chardev_data, lineinfo_changed_nb);
|
||||
struct gpio_v2_line_info_changed chg;
|
||||
struct lineinfo_changed_ctx *ctx;
|
||||
struct gpio_desc *desc = data;
|
||||
int ret;
|
||||
|
||||
if (!test_bit(gpio_chip_hwgpio(desc), cdev->watched_lines))
|
||||
return NOTIFY_DONE;
|
||||
|
||||
memset(&chg, 0, sizeof(chg));
|
||||
chg.event_type = action;
|
||||
chg.timestamp_ns = ktime_get_ns();
|
||||
gpio_desc_to_lineinfo(desc, &chg.info);
|
||||
supinfo_to_lineinfo(desc, &chg.info);
|
||||
/*
|
||||
* If this is called from atomic context (for instance: with a spinlock
|
||||
* taken by the atomic notifier chain), any sleeping calls must be done
|
||||
* outside of this function in process context of the dedicated
|
||||
* workqueue.
|
||||
*
|
||||
* Let's gather as much info as possible from the descriptor and
|
||||
* postpone just the call to pinctrl_gpio_can_use_line() until the work
|
||||
* is executed.
|
||||
*/
|
||||
|
||||
ret = kfifo_in_spinlocked(&cdev->events, &chg, 1, &cdev->wait.lock);
|
||||
if (ret)
|
||||
wake_up_poll(&cdev->wait, EPOLLIN);
|
||||
else
|
||||
pr_debug_ratelimited("lineinfo event FIFO is full - event dropped\n");
|
||||
ctx = kzalloc(sizeof(*ctx), GFP_ATOMIC);
|
||||
if (!ctx) {
|
||||
pr_err("Failed to allocate memory for line info notification\n");
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
ctx->chg.event_type = action;
|
||||
ctx->chg.timestamp_ns = ktime_get_ns();
|
||||
gpio_desc_to_lineinfo(desc, &ctx->chg.info, true);
|
||||
/* Keep the GPIO device alive until we emit the event. */
|
||||
ctx->gdev = gpio_device_get(desc->gdev);
|
||||
ctx->cdev = cdev;
|
||||
/* Keep the file descriptor alive too. */
|
||||
get_file(ctx->cdev->fp);
|
||||
|
||||
INIT_WORK(&ctx->work, lineinfo_changed_func);
|
||||
queue_work(ctx->gdev->line_state_wq, &ctx->work);
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
@ -2778,8 +2729,8 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file)
|
||||
cdev->gdev = gpio_device_get(gdev);
|
||||
|
||||
cdev->lineinfo_changed_nb.notifier_call = lineinfo_changed_notify;
|
||||
ret = blocking_notifier_chain_register(&gdev->line_state_notifier,
|
||||
&cdev->lineinfo_changed_nb);
|
||||
ret = atomic_notifier_chain_register(&gdev->line_state_notifier,
|
||||
&cdev->lineinfo_changed_nb);
|
||||
if (ret)
|
||||
goto out_free_bitmap;
|
||||
|
||||
@ -2791,6 +2742,7 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file)
|
||||
goto out_unregister_line_notifier;
|
||||
|
||||
file->private_data = cdev;
|
||||
cdev->fp = file;
|
||||
|
||||
ret = nonseekable_open(inode, file);
|
||||
if (ret)
|
||||
@ -2802,8 +2754,8 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file)
|
||||
blocking_notifier_chain_unregister(&gdev->device_notifier,
|
||||
&cdev->device_unregistered_nb);
|
||||
out_unregister_line_notifier:
|
||||
blocking_notifier_chain_unregister(&gdev->line_state_notifier,
|
||||
&cdev->lineinfo_changed_nb);
|
||||
atomic_notifier_chain_unregister(&gdev->line_state_notifier,
|
||||
&cdev->lineinfo_changed_nb);
|
||||
out_free_bitmap:
|
||||
gpio_device_put(gdev);
|
||||
bitmap_free(cdev->watched_lines);
|
||||
@ -2827,8 +2779,8 @@ static int gpio_chrdev_release(struct inode *inode, struct file *file)
|
||||
|
||||
blocking_notifier_chain_unregister(&gdev->device_notifier,
|
||||
&cdev->device_unregistered_nb);
|
||||
blocking_notifier_chain_unregister(&gdev->line_state_notifier,
|
||||
&cdev->lineinfo_changed_nb);
|
||||
atomic_notifier_chain_unregister(&gdev->line_state_notifier,
|
||||
&cdev->lineinfo_changed_nb);
|
||||
bitmap_free(cdev->watched_lines);
|
||||
gpio_device_put(gdev);
|
||||
kfree(cdev);
|
||||
@ -2857,6 +2809,11 @@ int gpiolib_cdev_register(struct gpio_device *gdev, dev_t devt)
|
||||
gdev->chrdev.owner = THIS_MODULE;
|
||||
gdev->dev.devt = MKDEV(MAJOR(devt), gdev->id);
|
||||
|
||||
gdev->line_state_wq = alloc_ordered_workqueue("%s", WQ_HIGHPRI,
|
||||
dev_name(&gdev->dev));
|
||||
if (!gdev->line_state_wq)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = cdev_device_add(&gdev->chrdev, &gdev->dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
@ -2873,6 +2830,7 @@ int gpiolib_cdev_register(struct gpio_device *gdev, dev_t devt)
|
||||
|
||||
void gpiolib_cdev_unregister(struct gpio_device *gdev)
|
||||
{
|
||||
destroy_workqueue(gdev->line_state_wq);
|
||||
cdev_device_del(&gdev->chrdev, &gdev->dev);
|
||||
blocking_notifier_call_chain(&gdev->device_notifier, 0, NULL);
|
||||
}
|
||||
|
@ -46,9 +46,6 @@ int gpio_request_one(unsigned gpio, unsigned long flags, const char *label)
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (flags & GPIOF_ACTIVE_LOW)
|
||||
set_bit(FLAG_ACTIVE_LOW, &desc->flags);
|
||||
|
||||
if (flags & GPIOF_IN)
|
||||
err = gpiod_direction_input(desc);
|
||||
else
|
||||
|
@ -337,7 +337,7 @@ static void of_gpio_flags_quirks(const struct device_node *np,
|
||||
* to determine if the flags should have inverted semantics.
|
||||
*/
|
||||
if (IS_ENABLED(CONFIG_SPI_MASTER) && !strcmp(propname, "cs-gpios") &&
|
||||
of_property_read_bool(np, "cs-gpios")) {
|
||||
of_property_present(np, "cs-gpios")) {
|
||||
u32 cs;
|
||||
int ret;
|
||||
|
||||
|
@ -21,6 +21,8 @@
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
|
||||
#include <uapi/linux/gpio.h>
|
||||
|
||||
#include "gpiolib.h"
|
||||
#include "gpiolib-sysfs.h"
|
||||
|
||||
@ -77,12 +79,10 @@ static ssize_t direction_show(struct device *dev,
|
||||
struct gpio_desc *desc = data->desc;
|
||||
int value;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
|
||||
gpiod_get_direction(desc);
|
||||
value = !!test_bit(FLAG_IS_OUT, &desc->flags);
|
||||
|
||||
mutex_unlock(&data->mutex);
|
||||
scoped_guard(mutex, &data->mutex) {
|
||||
gpiod_get_direction(desc);
|
||||
value = !!test_bit(FLAG_IS_OUT, &desc->flags);
|
||||
}
|
||||
|
||||
return sysfs_emit(buf, "%s\n", value ? "out" : "in");
|
||||
}
|
||||
@ -94,7 +94,7 @@ static ssize_t direction_store(struct device *dev,
|
||||
struct gpio_desc *desc = data->desc;
|
||||
ssize_t status;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
guard(mutex)(&data->mutex);
|
||||
|
||||
if (sysfs_streq(buf, "high"))
|
||||
status = gpiod_direction_output_raw(desc, 1);
|
||||
@ -105,8 +105,6 @@ static ssize_t direction_store(struct device *dev,
|
||||
else
|
||||
status = -EINVAL;
|
||||
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
return status ? : size;
|
||||
}
|
||||
static DEVICE_ATTR_RW(direction);
|
||||
@ -118,11 +116,8 @@ static ssize_t value_show(struct device *dev,
|
||||
struct gpio_desc *desc = data->desc;
|
||||
ssize_t status;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
|
||||
status = gpiod_get_value_cansleep(desc);
|
||||
|
||||
mutex_unlock(&data->mutex);
|
||||
scoped_guard(mutex, &data->mutex)
|
||||
status = gpiod_get_value_cansleep(desc);
|
||||
|
||||
if (status < 0)
|
||||
return status;
|
||||
@ -140,18 +135,17 @@ static ssize_t value_store(struct device *dev,
|
||||
|
||||
status = kstrtol(buf, 0, &value);
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
guard(mutex)(&data->mutex);
|
||||
|
||||
if (!test_bit(FLAG_IS_OUT, &desc->flags)) {
|
||||
status = -EPERM;
|
||||
} else if (status == 0) {
|
||||
gpiod_set_value_cansleep(desc, value);
|
||||
status = size;
|
||||
}
|
||||
if (!test_bit(FLAG_IS_OUT, &desc->flags))
|
||||
return -EPERM;
|
||||
|
||||
mutex_unlock(&data->mutex);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
return status;
|
||||
gpiod_set_value_cansleep(desc, value);
|
||||
|
||||
return size;
|
||||
}
|
||||
static DEVICE_ATTR_PREALLOC(value, S_IWUSR | S_IRUGO, value_show, value_store);
|
||||
|
||||
@ -185,12 +179,16 @@ static int gpio_sysfs_request_irq(struct device *dev, unsigned char flags)
|
||||
return -ENODEV;
|
||||
|
||||
irq_flags = IRQF_SHARED;
|
||||
if (flags & GPIO_IRQF_TRIGGER_FALLING)
|
||||
if (flags & GPIO_IRQF_TRIGGER_FALLING) {
|
||||
irq_flags |= test_bit(FLAG_ACTIVE_LOW, &desc->flags) ?
|
||||
IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING;
|
||||
if (flags & GPIO_IRQF_TRIGGER_RISING)
|
||||
set_bit(FLAG_EDGE_FALLING, &desc->flags);
|
||||
}
|
||||
if (flags & GPIO_IRQF_TRIGGER_RISING) {
|
||||
irq_flags |= test_bit(FLAG_ACTIVE_LOW, &desc->flags) ?
|
||||
IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING;
|
||||
set_bit(FLAG_EDGE_RISING, &desc->flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* FIXME: This should be done in the irq_request_resources callback
|
||||
@ -216,6 +214,8 @@ static int gpio_sysfs_request_irq(struct device *dev, unsigned char flags)
|
||||
err_unlock:
|
||||
gpiochip_unlock_as_irq(guard.gc, gpio_chip_hwgpio(desc));
|
||||
err_put_kn:
|
||||
clear_bit(FLAG_EDGE_RISING, &desc->flags);
|
||||
clear_bit(FLAG_EDGE_FALLING, &desc->flags);
|
||||
sysfs_put(data->value_kn);
|
||||
|
||||
return ret;
|
||||
@ -237,6 +237,8 @@ static void gpio_sysfs_free_irq(struct device *dev)
|
||||
data->irq_flags = 0;
|
||||
free_irq(data->irq, data);
|
||||
gpiochip_unlock_as_irq(guard.gc, gpio_chip_hwgpio(desc));
|
||||
clear_bit(FLAG_EDGE_RISING, &desc->flags);
|
||||
clear_bit(FLAG_EDGE_FALLING, &desc->flags);
|
||||
sysfs_put(data->value_kn);
|
||||
}
|
||||
|
||||
@ -253,11 +255,8 @@ static ssize_t edge_show(struct device *dev,
|
||||
struct gpiod_data *data = dev_get_drvdata(dev);
|
||||
int flags;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
|
||||
flags = data->irq_flags;
|
||||
|
||||
mutex_unlock(&data->mutex);
|
||||
scoped_guard(mutex, &data->mutex)
|
||||
flags = data->irq_flags;
|
||||
|
||||
if (flags >= ARRAY_SIZE(trigger_names))
|
||||
return 0;
|
||||
@ -276,26 +275,24 @@ static ssize_t edge_store(struct device *dev,
|
||||
if (flags < 0)
|
||||
return flags;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
guard(mutex)(&data->mutex);
|
||||
|
||||
if (flags == data->irq_flags) {
|
||||
status = size;
|
||||
goto out_unlock;
|
||||
}
|
||||
if (flags == data->irq_flags)
|
||||
return size;
|
||||
|
||||
if (data->irq_flags)
|
||||
gpio_sysfs_free_irq(dev);
|
||||
|
||||
if (flags) {
|
||||
status = gpio_sysfs_request_irq(dev, flags);
|
||||
if (!status)
|
||||
status = size;
|
||||
}
|
||||
if (!flags)
|
||||
return size;
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&data->mutex);
|
||||
status = gpio_sysfs_request_irq(dev, flags);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
return status;
|
||||
gpiod_line_state_notify(data->desc, GPIO_V2_LINE_CHANGED_CONFIG);
|
||||
|
||||
return size;
|
||||
}
|
||||
static DEVICE_ATTR_RW(edge);
|
||||
|
||||
@ -320,6 +317,8 @@ static int gpio_sysfs_set_active_low(struct device *dev, int value)
|
||||
status = gpio_sysfs_request_irq(dev, flags);
|
||||
}
|
||||
|
||||
gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@ -330,11 +329,8 @@ static ssize_t active_low_show(struct device *dev,
|
||||
struct gpio_desc *desc = data->desc;
|
||||
int value;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
|
||||
value = !!test_bit(FLAG_ACTIVE_LOW, &desc->flags);
|
||||
|
||||
mutex_unlock(&data->mutex);
|
||||
scoped_guard(mutex, &data->mutex)
|
||||
value = !!test_bit(FLAG_ACTIVE_LOW, &desc->flags);
|
||||
|
||||
return sysfs_emit(buf, "%d\n", value);
|
||||
}
|
||||
@ -350,13 +346,9 @@ static ssize_t active_low_store(struct device *dev,
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
guard(mutex)(&data->mutex);
|
||||
|
||||
status = gpio_sysfs_set_active_low(dev, value);
|
||||
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
return status ? : size;
|
||||
return gpio_sysfs_set_active_low(dev, value) ?: size;
|
||||
}
|
||||
static DEVICE_ATTR_RW(active_low);
|
||||
|
||||
@ -463,7 +455,7 @@ static ssize_t export_store(const struct class *class,
|
||||
desc = gpio_to_desc(gpio);
|
||||
/* reject invalid GPIOs */
|
||||
if (!desc) {
|
||||
pr_warn("%s: invalid GPIO %ld\n", __func__, gpio);
|
||||
pr_debug_ratelimited("%s: invalid GPIO %ld\n", __func__, gpio);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@ -473,7 +465,7 @@ static ssize_t export_store(const struct class *class,
|
||||
|
||||
offset = gpio_chip_hwgpio(desc);
|
||||
if (!gpiochip_line_is_valid(guard.gc, offset)) {
|
||||
pr_warn("%s: GPIO %ld masked\n", __func__, gpio);
|
||||
pr_debug_ratelimited("%s: GPIO %ld masked\n", __func__, gpio);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@ -493,10 +485,12 @@ static ssize_t export_store(const struct class *class,
|
||||
}
|
||||
|
||||
status = gpiod_export(desc, true);
|
||||
if (status < 0)
|
||||
if (status < 0) {
|
||||
gpiod_free(desc);
|
||||
else
|
||||
} else {
|
||||
set_bit(FLAG_SYSFS, &desc->flags);
|
||||
gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_REQUESTED);
|
||||
}
|
||||
|
||||
done:
|
||||
if (status)
|
||||
@ -520,7 +514,7 @@ static ssize_t unexport_store(const struct class *class,
|
||||
desc = gpio_to_desc(gpio);
|
||||
/* reject bogus commands (gpiod_unexport() ignores them) */
|
||||
if (!desc) {
|
||||
pr_warn("%s: invalid GPIO %ld\n", __func__, gpio);
|
||||
pr_debug_ratelimited("%s: invalid GPIO %ld\n", __func__, gpio);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@ -549,12 +543,11 @@ static struct attribute *gpio_class_attrs[] = {
|
||||
};
|
||||
ATTRIBUTE_GROUPS(gpio_class);
|
||||
|
||||
static struct class gpio_class = {
|
||||
static const struct class gpio_class = {
|
||||
.name = "gpio",
|
||||
.class_groups = gpio_class_groups,
|
||||
.class_groups = gpio_class_groups,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* gpiod_export - export a GPIO through sysfs
|
||||
* @desc: GPIO to make available, already requested
|
||||
@ -573,11 +566,10 @@ static struct class gpio_class = {
|
||||
*/
|
||||
int gpiod_export(struct gpio_desc *desc, bool direction_may_change)
|
||||
{
|
||||
const char *ioname = NULL;
|
||||
struct gpio_device *gdev;
|
||||
struct gpiod_data *data;
|
||||
struct device *dev;
|
||||
int status, offset;
|
||||
int status;
|
||||
|
||||
/* can't export until sysfs is available ... */
|
||||
if (!class_is_registered(&gpio_class)) {
|
||||
@ -599,24 +591,24 @@ int gpiod_export(struct gpio_desc *desc, bool direction_may_change)
|
||||
|
||||
gdev = desc->gdev;
|
||||
|
||||
mutex_lock(&sysfs_lock);
|
||||
guard(mutex)(&sysfs_lock);
|
||||
|
||||
/* check if chip is being removed */
|
||||
if (!gdev->mockdev) {
|
||||
status = -ENODEV;
|
||||
goto err_unlock;
|
||||
goto err_clear_bit;
|
||||
}
|
||||
|
||||
if (!test_bit(FLAG_REQUESTED, &desc->flags)) {
|
||||
gpiod_dbg(desc, "%s: unavailable (not requested)\n", __func__);
|
||||
status = -EPERM;
|
||||
goto err_unlock;
|
||||
goto err_clear_bit;
|
||||
}
|
||||
|
||||
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
status = -ENOMEM;
|
||||
goto err_unlock;
|
||||
goto err_clear_bit;
|
||||
}
|
||||
|
||||
data->desc = desc;
|
||||
@ -626,26 +618,19 @@ int gpiod_export(struct gpio_desc *desc, bool direction_may_change)
|
||||
else
|
||||
data->direction_can_change = false;
|
||||
|
||||
offset = gpio_chip_hwgpio(desc);
|
||||
if (guard.gc->names && guard.gc->names[offset])
|
||||
ioname = guard.gc->names[offset];
|
||||
|
||||
dev = device_create_with_groups(&gpio_class, &gdev->dev,
|
||||
MKDEV(0, 0), data, gpio_groups,
|
||||
ioname ? ioname : "gpio%u",
|
||||
desc_to_gpio(desc));
|
||||
"gpio%u", desc_to_gpio(desc));
|
||||
if (IS_ERR(dev)) {
|
||||
status = PTR_ERR(dev);
|
||||
goto err_free_data;
|
||||
}
|
||||
|
||||
mutex_unlock(&sysfs_lock);
|
||||
return 0;
|
||||
|
||||
err_free_data:
|
||||
kfree(data);
|
||||
err_unlock:
|
||||
mutex_unlock(&sysfs_lock);
|
||||
err_clear_bit:
|
||||
clear_bit(FLAG_EXPORT, &desc->flags);
|
||||
gpiod_dbg(desc, "%s: status %d\n", __func__, status);
|
||||
return status;
|
||||
@ -709,36 +694,28 @@ void gpiod_unexport(struct gpio_desc *desc)
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(&sysfs_lock);
|
||||
scoped_guard(mutex, &sysfs_lock) {
|
||||
if (!test_bit(FLAG_EXPORT, &desc->flags))
|
||||
return;
|
||||
|
||||
if (!test_bit(FLAG_EXPORT, &desc->flags))
|
||||
goto err_unlock;
|
||||
dev = class_find_device(&gpio_class, NULL, desc, match_export);
|
||||
if (!dev)
|
||||
return;
|
||||
|
||||
dev = class_find_device(&gpio_class, NULL, desc, match_export);
|
||||
if (!dev)
|
||||
goto err_unlock;
|
||||
data = dev_get_drvdata(dev);
|
||||
clear_bit(FLAG_EXPORT, &desc->flags);
|
||||
device_unregister(dev);
|
||||
|
||||
data = dev_get_drvdata(dev);
|
||||
|
||||
clear_bit(FLAG_EXPORT, &desc->flags);
|
||||
|
||||
device_unregister(dev);
|
||||
|
||||
/*
|
||||
* Release irq after deregistration to prevent race with edge_store.
|
||||
*/
|
||||
if (data->irq_flags)
|
||||
gpio_sysfs_free_irq(dev);
|
||||
|
||||
mutex_unlock(&sysfs_lock);
|
||||
/*
|
||||
* Release irq after deregistration to prevent race with
|
||||
* edge_store.
|
||||
*/
|
||||
if (data->irq_flags)
|
||||
gpio_sysfs_free_irq(dev);
|
||||
}
|
||||
|
||||
put_device(dev);
|
||||
kfree(data);
|
||||
|
||||
return;
|
||||
|
||||
err_unlock:
|
||||
mutex_unlock(&sysfs_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpiod_unexport);
|
||||
|
||||
@ -779,9 +756,8 @@ int gpiochip_sysfs_register(struct gpio_device *gdev)
|
||||
if (IS_ERR(dev))
|
||||
return PTR_ERR(dev);
|
||||
|
||||
mutex_lock(&sysfs_lock);
|
||||
guard(mutex)(&sysfs_lock);
|
||||
gdev->mockdev = dev;
|
||||
mutex_unlock(&sysfs_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <linux/idr.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdesc.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/lockdep.h>
|
||||
@ -23,7 +24,6 @@
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/srcu.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
@ -713,6 +713,45 @@ bool gpiochip_line_is_valid(const struct gpio_chip *gc,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpiochip_line_is_valid);
|
||||
|
||||
static void gpiod_free_irqs(struct gpio_desc *desc)
|
||||
{
|
||||
int irq = gpiod_to_irq(desc);
|
||||
struct irq_desc *irqd = irq_to_desc(irq);
|
||||
void *cookie;
|
||||
|
||||
for (;;) {
|
||||
/*
|
||||
* Make sure the action doesn't go away while we're
|
||||
* dereferencing it. Retrieve and store the cookie value.
|
||||
* If the irq is freed after we release the lock, that's
|
||||
* alright - the underlying maple tree lookup will return NULL
|
||||
* and nothing will happen in free_irq().
|
||||
*/
|
||||
scoped_guard(mutex, &irqd->request_mutex) {
|
||||
if (!irq_desc_has_action(irqd))
|
||||
return;
|
||||
|
||||
cookie = irqd->action->dev_id;
|
||||
}
|
||||
|
||||
free_irq(irq, cookie);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The chip is going away but there may be users who had requested interrupts
|
||||
* on its GPIO lines who have no idea about its removal and have no way of
|
||||
* being notified about it. We need to free any interrupts still in use here or
|
||||
* we'll leak memory and resources (like procfs files).
|
||||
*/
|
||||
static void gpiochip_free_remaining_irqs(struct gpio_chip *gc)
|
||||
{
|
||||
struct gpio_desc *desc;
|
||||
|
||||
for_each_gpio_desc_with_flag(gc, desc, FLAG_USED_AS_IRQ)
|
||||
gpiod_free_irqs(desc);
|
||||
}
|
||||
|
||||
static void gpiodev_release(struct device *dev)
|
||||
{
|
||||
struct gpio_device *gdev = to_gpio_device(dev);
|
||||
@ -986,10 +1025,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
|
||||
}
|
||||
}
|
||||
|
||||
for (desc_index = 0; desc_index < gc->ngpio; desc_index++)
|
||||
gdev->descs[desc_index].gdev = gdev;
|
||||
|
||||
BLOCKING_INIT_NOTIFIER_HEAD(&gdev->line_state_notifier);
|
||||
ATOMIC_INIT_NOTIFIER_HEAD(&gdev->line_state_notifier);
|
||||
BLOCKING_INIT_NOTIFIER_HEAD(&gdev->device_notifier);
|
||||
|
||||
ret = init_srcu_struct(&gdev->srcu);
|
||||
@ -1018,6 +1054,8 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
|
||||
for (desc_index = 0; desc_index < gc->ngpio; desc_index++) {
|
||||
struct gpio_desc *desc = &gdev->descs[desc_index];
|
||||
|
||||
desc->gdev = gdev;
|
||||
|
||||
if (gc->get_direction && gpiochip_line_is_valid(gc, desc_index)) {
|
||||
assign_bit(FLAG_IS_OUT,
|
||||
&desc->flags, !gc->get_direction(gc, desc_index));
|
||||
@ -1125,6 +1163,7 @@ void gpiochip_remove(struct gpio_chip *gc)
|
||||
/* FIXME: should the legacy sysfs handling be moved to gpio_device? */
|
||||
gpiochip_sysfs_unregister(gdev);
|
||||
gpiochip_free_hogs(gc);
|
||||
gpiochip_free_remaining_irqs(gc);
|
||||
|
||||
scoped_guard(mutex, &gpio_devices_lock)
|
||||
list_del_rcu(&gdev->list);
|
||||
@ -1183,11 +1222,6 @@ struct gpio_device *gpio_device_find(const void *data,
|
||||
struct gpio_device *gdev;
|
||||
struct gpio_chip *gc;
|
||||
|
||||
/*
|
||||
* Not yet but in the future the spinlock below will become a mutex.
|
||||
* Annotate this function before anyone tries to use it in interrupt
|
||||
* context like it happened with gpiochip_find().
|
||||
*/
|
||||
might_sleep();
|
||||
|
||||
guard(srcu)(&gpio_devices_srcu);
|
||||
@ -2392,8 +2426,10 @@ static void gpiod_free_commit(struct gpio_desc *desc)
|
||||
#endif
|
||||
desc_set_label(desc, NULL);
|
||||
WRITE_ONCE(desc->flags, flags);
|
||||
|
||||
gpiod_line_state_notify(desc, GPIOLINE_CHANGED_RELEASED);
|
||||
#ifdef CONFIG_GPIO_CDEV
|
||||
WRITE_ONCE(desc->debounce_period_us, 0);
|
||||
#endif
|
||||
gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_RELEASED);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2492,6 +2528,8 @@ struct gpio_desc *gpiochip_request_own_desc(struct gpio_chip *gc,
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_REQUESTED);
|
||||
|
||||
return desc;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpiochip_request_own_desc);
|
||||
@ -2520,13 +2558,28 @@ EXPORT_SYMBOL_GPL(gpiochip_free_own_desc);
|
||||
* rely on gpio_request() having been called beforehand.
|
||||
*/
|
||||
|
||||
static int gpio_do_set_config(struct gpio_chip *gc, unsigned int offset,
|
||||
unsigned long config)
|
||||
int gpio_do_set_config(struct gpio_desc *desc, unsigned long config)
|
||||
{
|
||||
if (!gc->set_config)
|
||||
int ret;
|
||||
|
||||
CLASS(gpio_chip_guard, guard)(desc);
|
||||
if (!guard.gc)
|
||||
return -ENODEV;
|
||||
|
||||
if (!guard.gc->set_config)
|
||||
return -ENOTSUPP;
|
||||
|
||||
return gc->set_config(gc, offset, config);
|
||||
ret = guard.gc->set_config(guard.gc, gpio_chip_hwgpio(desc), config);
|
||||
#ifdef CONFIG_GPIO_CDEV
|
||||
/*
|
||||
* Special case - if we're setting debounce period, we need to store
|
||||
* it in the descriptor in case user-space wants to know it.
|
||||
*/
|
||||
if (!ret && pinconf_to_config_param(config) == PIN_CONFIG_INPUT_DEBOUNCE)
|
||||
WRITE_ONCE(desc->debounce_period_us,
|
||||
pinconf_to_config_argument(config));
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int gpio_set_config_with_argument(struct gpio_desc *desc,
|
||||
@ -2535,12 +2588,8 @@ static int gpio_set_config_with_argument(struct gpio_desc *desc,
|
||||
{
|
||||
unsigned long config;
|
||||
|
||||
CLASS(gpio_chip_guard, guard)(desc);
|
||||
if (!guard.gc)
|
||||
return -ENODEV;
|
||||
|
||||
config = pinconf_to_config_packed(mode, argument);
|
||||
return gpio_do_set_config(guard.gc, gpio_chip_hwgpio(desc), config);
|
||||
return gpio_do_set_config(desc, config);
|
||||
}
|
||||
|
||||
static int gpio_set_config_with_argument_optional(struct gpio_desc *desc,
|
||||
@ -2615,9 +2664,15 @@ static int gpio_set_bias(struct gpio_desc *desc)
|
||||
*/
|
||||
int gpio_set_debounce_timeout(struct gpio_desc *desc, unsigned int debounce)
|
||||
{
|
||||
return gpio_set_config_with_argument_optional(desc,
|
||||
PIN_CONFIG_INPUT_DEBOUNCE,
|
||||
debounce);
|
||||
int ret;
|
||||
|
||||
ret = gpio_set_config_with_argument_optional(desc,
|
||||
PIN_CONFIG_INPUT_DEBOUNCE,
|
||||
debounce);
|
||||
if (!ret)
|
||||
gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2632,10 +2687,22 @@ int gpio_set_debounce_timeout(struct gpio_desc *desc, unsigned int debounce)
|
||||
*/
|
||||
int gpiod_direction_input(struct gpio_desc *desc)
|
||||
{
|
||||
int ret = 0;
|
||||
int ret;
|
||||
|
||||
VALIDATE_DESC(desc);
|
||||
|
||||
ret = gpiod_direction_input_nonotify(desc);
|
||||
if (ret == 0)
|
||||
gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpiod_direction_input);
|
||||
|
||||
int gpiod_direction_input_nonotify(struct gpio_desc *desc)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
CLASS(gpio_chip_guard, guard)(desc);
|
||||
if (!guard.gc)
|
||||
return -ENODEV;
|
||||
@ -2678,7 +2745,6 @@ int gpiod_direction_input(struct gpio_desc *desc)
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpiod_direction_input);
|
||||
|
||||
static int gpiod_direction_output_raw_commit(struct gpio_desc *desc, int value)
|
||||
{
|
||||
@ -2740,8 +2806,15 @@ static int gpiod_direction_output_raw_commit(struct gpio_desc *desc, int value)
|
||||
*/
|
||||
int gpiod_direction_output_raw(struct gpio_desc *desc, int value)
|
||||
{
|
||||
int ret;
|
||||
|
||||
VALIDATE_DESC(desc);
|
||||
return gpiod_direction_output_raw_commit(desc, value);
|
||||
|
||||
ret = gpiod_direction_output_raw_commit(desc, value);
|
||||
if (ret == 0)
|
||||
gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpiod_direction_output_raw);
|
||||
|
||||
@ -2760,11 +2833,23 @@ EXPORT_SYMBOL_GPL(gpiod_direction_output_raw);
|
||||
*/
|
||||
int gpiod_direction_output(struct gpio_desc *desc, int value)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
VALIDATE_DESC(desc);
|
||||
|
||||
ret = gpiod_direction_output_nonotify(desc, value);
|
||||
if (ret == 0)
|
||||
gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpiod_direction_output);
|
||||
|
||||
int gpiod_direction_output_nonotify(struct gpio_desc *desc, int value)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
flags = READ_ONCE(desc->flags);
|
||||
|
||||
if (test_bit(FLAG_ACTIVE_LOW, &flags))
|
||||
@ -2788,7 +2873,7 @@ int gpiod_direction_output(struct gpio_desc *desc, int value)
|
||||
goto set_output_value;
|
||||
/* Emulate open drain by not actively driving the line high */
|
||||
if (value) {
|
||||
ret = gpiod_direction_input(desc);
|
||||
ret = gpiod_direction_input_nonotify(desc);
|
||||
goto set_output_flag;
|
||||
}
|
||||
} else if (test_bit(FLAG_OPEN_SOURCE, &flags)) {
|
||||
@ -2797,7 +2882,7 @@ int gpiod_direction_output(struct gpio_desc *desc, int value)
|
||||
goto set_output_value;
|
||||
/* Emulate open source by not actively driving the line low */
|
||||
if (!value) {
|
||||
ret = gpiod_direction_input(desc);
|
||||
ret = gpiod_direction_input_nonotify(desc);
|
||||
goto set_output_flag;
|
||||
}
|
||||
} else {
|
||||
@ -2821,7 +2906,6 @@ int gpiod_direction_output(struct gpio_desc *desc, int value)
|
||||
set_bit(FLAG_IS_OUT, &desc->flags);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpiod_direction_output);
|
||||
|
||||
/**
|
||||
* gpiod_enable_hw_timestamp_ns - Enable hardware timestamp in nanoseconds.
|
||||
@ -2900,13 +2984,30 @@ EXPORT_SYMBOL_GPL(gpiod_disable_hw_timestamp_ns);
|
||||
*/
|
||||
int gpiod_set_config(struct gpio_desc *desc, unsigned long config)
|
||||
{
|
||||
int ret;
|
||||
|
||||
VALIDATE_DESC(desc);
|
||||
|
||||
CLASS(gpio_chip_guard, guard)(desc);
|
||||
if (!guard.gc)
|
||||
return -ENODEV;
|
||||
ret = gpio_do_set_config(desc, config);
|
||||
if (!ret) {
|
||||
/* These are the only options we notify the userspace about. */
|
||||
switch (pinconf_to_config_param(config)) {
|
||||
case PIN_CONFIG_BIAS_DISABLE:
|
||||
case PIN_CONFIG_BIAS_PULL_DOWN:
|
||||
case PIN_CONFIG_BIAS_PULL_UP:
|
||||
case PIN_CONFIG_DRIVE_OPEN_DRAIN:
|
||||
case PIN_CONFIG_DRIVE_OPEN_SOURCE:
|
||||
case PIN_CONFIG_DRIVE_PUSH_PULL:
|
||||
case PIN_CONFIG_INPUT_DEBOUNCE:
|
||||
gpiod_line_state_notify(desc,
|
||||
GPIO_V2_LINE_CHANGED_CONFIG);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return gpio_do_set_config(guard.gc, gpio_chip_hwgpio(desc), config);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpiod_set_config);
|
||||
|
||||
@ -2973,6 +3074,7 @@ void gpiod_toggle_active_low(struct gpio_desc *desc)
|
||||
{
|
||||
VALIDATE_DESC_VOID(desc);
|
||||
change_bit(FLAG_ACTIVE_LOW, &desc->flags);
|
||||
gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpiod_toggle_active_low);
|
||||
|
||||
@ -3617,9 +3719,15 @@ EXPORT_SYMBOL_GPL(gpiod_cansleep);
|
||||
*/
|
||||
int gpiod_set_consumer_name(struct gpio_desc *desc, const char *name)
|
||||
{
|
||||
int ret;
|
||||
|
||||
VALIDATE_DESC(desc);
|
||||
|
||||
return desc_set_label(desc, name);
|
||||
ret = desc_set_label(desc, name);
|
||||
if (ret == 0)
|
||||
gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpiod_set_consumer_name);
|
||||
|
||||
@ -4047,8 +4155,8 @@ EXPORT_SYMBOL_GPL(gpiod_set_array_value_cansleep);
|
||||
|
||||
void gpiod_line_state_notify(struct gpio_desc *desc, unsigned long action)
|
||||
{
|
||||
blocking_notifier_call_chain(&desc->gdev->line_state_notifier,
|
||||
action, desc);
|
||||
atomic_notifier_call_chain(&desc->gdev->line_state_notifier,
|
||||
action, desc);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -4325,7 +4433,7 @@ struct gpio_desc *gpiod_find_and_request(struct device *consumer,
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
gpiod_line_state_notify(desc, GPIOLINE_CHANGED_REQUESTED);
|
||||
gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_REQUESTED);
|
||||
|
||||
return desc;
|
||||
}
|
||||
@ -4497,10 +4605,10 @@ int gpiod_configure_flags(struct gpio_desc *desc, const char *con_id,
|
||||
|
||||
/* Process flags */
|
||||
if (dflags & GPIOD_FLAGS_BIT_DIR_OUT)
|
||||
ret = gpiod_direction_output(desc,
|
||||
ret = gpiod_direction_output_nonotify(desc,
|
||||
!!(dflags & GPIOD_FLAGS_BIT_DIR_VAL));
|
||||
else
|
||||
ret = gpiod_direction_input(desc);
|
||||
ret = gpiod_direction_input_nonotify(desc);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -4967,19 +5075,19 @@ static int gpiolib_seq_show(struct seq_file *s, void *v)
|
||||
struct gpio_chip *gc;
|
||||
struct device *parent;
|
||||
|
||||
if (priv->newline)
|
||||
seq_putc(s, '\n');
|
||||
|
||||
guard(srcu)(&gdev->srcu);
|
||||
|
||||
gc = srcu_dereference(gdev->chip, &gdev->srcu);
|
||||
if (!gc) {
|
||||
seq_printf(s, "%s%s: (dangling chip)\n",
|
||||
priv->newline ? "\n" : "",
|
||||
dev_name(&gdev->dev));
|
||||
seq_printf(s, "%s: (dangling chip)\n", dev_name(&gdev->dev));
|
||||
return 0;
|
||||
}
|
||||
|
||||
seq_printf(s, "%s%s: GPIOs %u-%u", priv->newline ? "\n" : "",
|
||||
dev_name(&gdev->dev),
|
||||
gdev->base, gdev->base + gdev->ngpio - 1);
|
||||
seq_printf(s, "%s: GPIOs %u-%u", dev_name(&gdev->dev), gdev->base,
|
||||
gdev->base + gdev->ngpio - 1);
|
||||
parent = gc->parent;
|
||||
if (parent)
|
||||
seq_printf(s, ", parent: %s/%s",
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/srcu.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#define GPIOCHIP_NAME "gpiochip"
|
||||
|
||||
@ -44,6 +45,8 @@
|
||||
* @list: links gpio_device:s together for traversal
|
||||
* @line_state_notifier: used to notify subscribers about lines being
|
||||
* requested, released or reconfigured
|
||||
* @line_state_wq: used to emit line state events from a separate thread in
|
||||
* process context
|
||||
* @device_notifier: used to notify character device wait queues about the GPIO
|
||||
* device being unregistered
|
||||
* @srcu: protects the pointer to the underlying GPIO chip
|
||||
@ -69,7 +72,8 @@ struct gpio_device {
|
||||
const char *label;
|
||||
void *data;
|
||||
struct list_head list;
|
||||
struct blocking_notifier_head line_state_notifier;
|
||||
struct atomic_notifier_head line_state_notifier;
|
||||
struct workqueue_struct *line_state_wq;
|
||||
struct blocking_notifier_head device_notifier;
|
||||
struct srcu_struct srcu;
|
||||
|
||||
@ -151,6 +155,8 @@ int gpiod_set_array_value_complex(bool raw, bool can_sleep,
|
||||
int gpiod_set_transitory(struct gpio_desc *desc, bool transitory);
|
||||
|
||||
void gpiod_line_state_notify(struct gpio_desc *desc, unsigned long action);
|
||||
int gpiod_direction_output_nonotify(struct gpio_desc *desc, int value);
|
||||
int gpiod_direction_input_nonotify(struct gpio_desc *desc);
|
||||
|
||||
struct gpio_desc_label {
|
||||
struct rcu_head rh;
|
||||
@ -165,6 +171,7 @@ struct gpio_desc_label {
|
||||
* @label: Name of the consumer
|
||||
* @name: Line name
|
||||
* @hog: Pointer to the device node that hogs this line (if any)
|
||||
* @debounce_period_us: Debounce period in microseconds
|
||||
*
|
||||
* These are obtained using gpiod_get() and are preferable to the old
|
||||
* integer-based handles.
|
||||
@ -202,6 +209,10 @@ struct gpio_desc {
|
||||
#ifdef CONFIG_OF_DYNAMIC
|
||||
struct device_node *hog;
|
||||
#endif
|
||||
#ifdef CONFIG_GPIO_CDEV
|
||||
/* debounce period in microseconds */
|
||||
unsigned int debounce_period_us;
|
||||
#endif
|
||||
};
|
||||
|
||||
#define gpiod_not_found(desc) (IS_ERR(desc) && PTR_ERR(desc) == -ENOENT)
|
||||
@ -249,6 +260,7 @@ struct gpio_desc *gpiod_find_and_request(struct device *consumer,
|
||||
const char *label,
|
||||
bool platform_lookup_allowed);
|
||||
|
||||
int gpio_do_set_config(struct gpio_desc *desc, unsigned long config);
|
||||
int gpiod_configure_flags(struct gpio_desc *desc, const char *con_id,
|
||||
unsigned long lflags, enum gpiod_flags dflags);
|
||||
int gpio_set_debounce_timeout(struct gpio_desc *desc, unsigned int debounce);
|
||||
|
@ -535,6 +535,16 @@ config I2C_CBUS_GPIO
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called i2c-cbus-gpio.
|
||||
|
||||
config I2C_CGBC
|
||||
tristate "Congatec I2C Controller"
|
||||
depends on MFD_CGBC
|
||||
help
|
||||
This driver supports the 2 I2C interfaces on the Congatec Board
|
||||
Controller.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called i2c-cgbc.ko.
|
||||
|
||||
config I2C_CPM
|
||||
tristate "Freescale CPM1 or CPM2 (MPC8xx/826x)"
|
||||
depends on CPM1 || CPM2
|
||||
|
@ -50,6 +50,7 @@ obj-$(CONFIG_I2C_BCM2835) += i2c-bcm2835.o
|
||||
obj-$(CONFIG_I2C_BCM_IPROC) += i2c-bcm-iproc.o
|
||||
obj-$(CONFIG_I2C_CADENCE) += i2c-cadence.o
|
||||
obj-$(CONFIG_I2C_CBUS_GPIO) += i2c-cbus-gpio.o
|
||||
obj-$(CONFIG_I2C_CGBC) += i2c-cgbc.o
|
||||
obj-$(CONFIG_I2C_CPM) += i2c-cpm.o
|
||||
obj-$(CONFIG_I2C_DAVINCI) += i2c-davinci.o
|
||||
obj-$(CONFIG_I2C_DESIGNWARE_CORE) += i2c-designware-core.o
|
||||
|
406
drivers/i2c/busses/i2c-cgbc.c
Normal file
406
drivers/i2c/busses/i2c-cgbc.c
Normal file
@ -0,0 +1,406 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Congatec Board Controller I2C busses driver
|
||||
*
|
||||
* Copyright (C) 2024 Bootlin
|
||||
* Author: Thomas Richard <thomas.richard@bootlin.com>
|
||||
*/
|
||||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/mfd/cgbc.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#define CGBC_I2C_PRIMARY_BUS_ID 0
|
||||
#define CGBC_I2C_PM_BUS_ID 4
|
||||
|
||||
#define CGBC_I2C_CMD_START 0x40
|
||||
#define CGBC_I2C_CMD_STAT 0x48
|
||||
#define CGBC_I2C_CMD_DATA 0x50
|
||||
#define CGBC_I2C_CMD_SPEED 0x58
|
||||
|
||||
#define CGBC_I2C_STAT_IDL 0x00
|
||||
#define CGBC_I2C_STAT_DAT 0x01
|
||||
#define CGBC_I2C_STAT_BUSY 0x02
|
||||
|
||||
#define CGBC_I2C_START 0x80
|
||||
#define CGBC_I2C_STOP 0x40
|
||||
|
||||
#define CGBC_I2C_LAST_ACK 0x80 /* send ACK on last read byte */
|
||||
|
||||
/*
|
||||
* Reference code defines 1kHz as min freq and 6.1MHz as max freq.
|
||||
* But in practice, the board controller limits the frequency to 1MHz, and the
|
||||
* 1kHz is not functional (minimal working freq is 50kHz).
|
||||
* So use these values as limits.
|
||||
*/
|
||||
#define CGBC_I2C_FREQ_MIN_HZ 50000 /* 50 kHz */
|
||||
#define CGBC_I2C_FREQ_MAX_HZ 1000000 /* 1 MHz */
|
||||
|
||||
#define CGBC_I2C_FREQ_UNIT_1KHZ 0x40
|
||||
#define CGBC_I2C_FREQ_UNIT_10KHZ 0x80
|
||||
#define CGBC_I2C_FREQ_UNIT_100KHZ 0xC0
|
||||
|
||||
#define CGBC_I2C_FREQ_UNIT_MASK 0xC0
|
||||
#define CGBC_I2C_FREQ_VALUE_MASK 0x3F
|
||||
|
||||
#define CGBC_I2C_READ_MAX_LEN 31
|
||||
#define CGBC_I2C_WRITE_MAX_LEN 32
|
||||
|
||||
#define CGBC_I2C_CMD_HEADER_SIZE 4
|
||||
#define CGBC_I2C_CMD_SIZE (CGBC_I2C_CMD_HEADER_SIZE + CGBC_I2C_WRITE_MAX_LEN)
|
||||
|
||||
enum cgbc_i2c_state {
|
||||
CGBC_I2C_STATE_DONE = 0,
|
||||
CGBC_I2C_STATE_INIT,
|
||||
CGBC_I2C_STATE_START,
|
||||
CGBC_I2C_STATE_READ,
|
||||
CGBC_I2C_STATE_WRITE,
|
||||
CGBC_I2C_STATE_ERROR,
|
||||
};
|
||||
|
||||
struct i2c_algo_cgbc_data {
|
||||
u8 bus_id;
|
||||
unsigned long read_maxtime_us;
|
||||
};
|
||||
|
||||
struct cgbc_i2c_data {
|
||||
struct device *dev;
|
||||
struct cgbc_device_data *cgbc;
|
||||
struct i2c_adapter adap;
|
||||
struct i2c_msg *msg;
|
||||
int nmsgs;
|
||||
int pos;
|
||||
enum cgbc_i2c_state state;
|
||||
};
|
||||
|
||||
struct cgbc_i2c_transfer {
|
||||
u8 bus_id;
|
||||
bool start;
|
||||
bool stop;
|
||||
bool last_ack;
|
||||
u8 read;
|
||||
u8 write;
|
||||
u8 addr;
|
||||
u8 data[CGBC_I2C_WRITE_MAX_LEN];
|
||||
};
|
||||
|
||||
static u8 cgbc_i2c_freq_to_reg(unsigned int bus_frequency)
|
||||
{
|
||||
u8 reg;
|
||||
|
||||
if (bus_frequency <= 10000)
|
||||
reg = CGBC_I2C_FREQ_UNIT_1KHZ | (bus_frequency / 1000);
|
||||
else if (bus_frequency <= 100000)
|
||||
reg = CGBC_I2C_FREQ_UNIT_10KHZ | (bus_frequency / 10000);
|
||||
else
|
||||
reg = CGBC_I2C_FREQ_UNIT_100KHZ | (bus_frequency / 100000);
|
||||
|
||||
return reg;
|
||||
}
|
||||
|
||||
static unsigned int cgbc_i2c_reg_to_freq(u8 reg)
|
||||
{
|
||||
unsigned int freq = reg & CGBC_I2C_FREQ_VALUE_MASK;
|
||||
u8 unit = reg & CGBC_I2C_FREQ_UNIT_MASK;
|
||||
|
||||
if (unit == CGBC_I2C_FREQ_UNIT_100KHZ)
|
||||
return freq * 100000;
|
||||
else if (unit == CGBC_I2C_FREQ_UNIT_10KHZ)
|
||||
return freq * 10000;
|
||||
else
|
||||
return freq * 1000;
|
||||
}
|
||||
|
||||
static int cgbc_i2c_get_status(struct i2c_adapter *adap)
|
||||
{
|
||||
struct i2c_algo_cgbc_data *algo_data = adap->algo_data;
|
||||
struct cgbc_i2c_data *i2c = i2c_get_adapdata(adap);
|
||||
struct cgbc_device_data *cgbc = i2c->cgbc;
|
||||
u8 cmd = CGBC_I2C_CMD_STAT | algo_data->bus_id;
|
||||
u8 status;
|
||||
int ret;
|
||||
|
||||
ret = cgbc_command(cgbc, &cmd, sizeof(cmd), NULL, 0, &status);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int cgbc_i2c_set_frequency(struct i2c_adapter *adap,
|
||||
unsigned int bus_frequency)
|
||||
{
|
||||
struct i2c_algo_cgbc_data *algo_data = adap->algo_data;
|
||||
struct cgbc_i2c_data *i2c = i2c_get_adapdata(adap);
|
||||
struct cgbc_device_data *cgbc = i2c->cgbc;
|
||||
u8 cmd[2], data;
|
||||
int ret;
|
||||
|
||||
if (bus_frequency > CGBC_I2C_FREQ_MAX_HZ ||
|
||||
bus_frequency < CGBC_I2C_FREQ_MIN_HZ) {
|
||||
dev_info(i2c->dev, "invalid frequency %u, using default\n", bus_frequency);
|
||||
bus_frequency = I2C_MAX_STANDARD_MODE_FREQ;
|
||||
}
|
||||
|
||||
cmd[0] = CGBC_I2C_CMD_SPEED | algo_data->bus_id;
|
||||
cmd[1] = cgbc_i2c_freq_to_reg(bus_frequency);
|
||||
|
||||
ret = cgbc_command(cgbc, &cmd, sizeof(cmd), &data, 1, NULL);
|
||||
if (ret)
|
||||
return dev_err_probe(i2c->dev, ret,
|
||||
"Failed to initialize I2C bus %s",
|
||||
adap->name);
|
||||
|
||||
cmd[1] = 0x00;
|
||||
|
||||
ret = cgbc_command(cgbc, &cmd, sizeof(cmd), &data, 1, NULL);
|
||||
if (ret)
|
||||
return dev_err_probe(i2c->dev, ret,
|
||||
"Failed to get I2C bus frequency");
|
||||
|
||||
bus_frequency = cgbc_i2c_reg_to_freq(data);
|
||||
|
||||
dev_dbg(i2c->dev, "%s is running at %d Hz\n", adap->name, bus_frequency);
|
||||
|
||||
/*
|
||||
* The read_maxtime_us variable represents the maximum time to wait
|
||||
* for data during a read operation. The maximum amount of data that
|
||||
* can be read by a command is CGBC_I2C_READ_MAX_LEN.
|
||||
* Therefore, calculate the max time to properly size the timeout.
|
||||
*/
|
||||
algo_data->read_maxtime_us = (BITS_PER_BYTE + 1) * CGBC_I2C_READ_MAX_LEN
|
||||
* USEC_PER_SEC / bus_frequency;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int cgbc_i2c_xfer_to_cmd(struct cgbc_i2c_transfer xfer, u8 *cmd)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
cmd[i++] = CGBC_I2C_CMD_START | xfer.bus_id;
|
||||
|
||||
cmd[i] = (xfer.start) ? CGBC_I2C_START : 0x00;
|
||||
if (xfer.stop)
|
||||
cmd[i] |= CGBC_I2C_STOP;
|
||||
cmd[i++] |= (xfer.start) ? xfer.write + 1 : xfer.write;
|
||||
|
||||
cmd[i++] = (xfer.last_ack) ? (xfer.read | CGBC_I2C_LAST_ACK) : xfer.read;
|
||||
|
||||
if (xfer.start)
|
||||
cmd[i++] = xfer.addr;
|
||||
|
||||
if (xfer.write > 0)
|
||||
memcpy(&cmd[i], &xfer.data, xfer.write);
|
||||
|
||||
return i + xfer.write;
|
||||
}
|
||||
|
||||
static int cgbc_i2c_xfer_msg(struct i2c_adapter *adap)
|
||||
{
|
||||
struct i2c_algo_cgbc_data *algo_data = adap->algo_data;
|
||||
struct cgbc_i2c_data *i2c = i2c_get_adapdata(adap);
|
||||
struct cgbc_device_data *cgbc = i2c->cgbc;
|
||||
struct i2c_msg *msg = i2c->msg;
|
||||
u8 cmd[CGBC_I2C_CMD_SIZE];
|
||||
int ret, max_len, len, i;
|
||||
unsigned int cmd_len;
|
||||
u8 cmd_data;
|
||||
|
||||
struct cgbc_i2c_transfer xfer = {
|
||||
.bus_id = algo_data->bus_id,
|
||||
.addr = i2c_8bit_addr_from_msg(msg),
|
||||
};
|
||||
|
||||
if (i2c->state == CGBC_I2C_STATE_DONE)
|
||||
return 0;
|
||||
|
||||
ret = cgbc_i2c_get_status(adap);
|
||||
|
||||
if (ret == CGBC_I2C_STAT_BUSY)
|
||||
return -EBUSY;
|
||||
else if (ret < 0)
|
||||
goto err;
|
||||
|
||||
if (i2c->state == CGBC_I2C_STATE_INIT ||
|
||||
(i2c->state == CGBC_I2C_STATE_WRITE && msg->flags & I2C_M_RD))
|
||||
xfer.start = true;
|
||||
|
||||
i2c->state = (msg->flags & I2C_M_RD) ? CGBC_I2C_STATE_READ : CGBC_I2C_STATE_WRITE;
|
||||
|
||||
max_len = (i2c->state == CGBC_I2C_STATE_READ) ?
|
||||
CGBC_I2C_READ_MAX_LEN : CGBC_I2C_WRITE_MAX_LEN;
|
||||
|
||||
if (msg->len - i2c->pos > max_len) {
|
||||
len = max_len;
|
||||
} else {
|
||||
len = msg->len - i2c->pos;
|
||||
|
||||
if (i2c->nmsgs == 1)
|
||||
xfer.stop = true;
|
||||
}
|
||||
|
||||
if (i2c->state == CGBC_I2C_STATE_WRITE) {
|
||||
xfer.write = len;
|
||||
xfer.read = 0;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
xfer.data[i] = msg->buf[i2c->pos + i];
|
||||
|
||||
cmd_len = cgbc_i2c_xfer_to_cmd(xfer, &cmd[0]);
|
||||
|
||||
ret = cgbc_command(cgbc, &cmd, cmd_len, NULL, 0, NULL);
|
||||
if (ret)
|
||||
goto err;
|
||||
} else if (i2c->state == CGBC_I2C_STATE_READ) {
|
||||
xfer.write = 0;
|
||||
xfer.read = len;
|
||||
|
||||
if (i2c->nmsgs > 1 || msg->len - i2c->pos > max_len)
|
||||
xfer.read |= CGBC_I2C_LAST_ACK;
|
||||
|
||||
cmd_len = cgbc_i2c_xfer_to_cmd(xfer, &cmd[0]);
|
||||
ret = cgbc_command(cgbc, &cmd, cmd_len, NULL, 0, NULL);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
ret = read_poll_timeout(cgbc_i2c_get_status, ret,
|
||||
ret != CGBC_I2C_STAT_BUSY, 0,
|
||||
2 * algo_data->read_maxtime_us, false, adap);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
cmd_data = CGBC_I2C_CMD_DATA | algo_data->bus_id;
|
||||
ret = cgbc_command(cgbc, &cmd_data, sizeof(cmd_data),
|
||||
msg->buf + i2c->pos, len, NULL);
|
||||
if (ret)
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (len == (msg->len - i2c->pos)) {
|
||||
i2c->msg++;
|
||||
i2c->nmsgs--;
|
||||
i2c->pos = 0;
|
||||
} else {
|
||||
i2c->pos += len;
|
||||
}
|
||||
|
||||
if (i2c->nmsgs == 0)
|
||||
i2c->state = CGBC_I2C_STATE_DONE;
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
i2c->state = CGBC_I2C_STATE_ERROR;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cgbc_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
|
||||
int num)
|
||||
{
|
||||
struct cgbc_i2c_data *i2c = i2c_get_adapdata(adap);
|
||||
unsigned long timeout = jiffies + HZ;
|
||||
int ret;
|
||||
|
||||
i2c->state = CGBC_I2C_STATE_INIT;
|
||||
i2c->msg = msgs;
|
||||
i2c->nmsgs = num;
|
||||
i2c->pos = 0;
|
||||
|
||||
while (time_before(jiffies, timeout)) {
|
||||
ret = cgbc_i2c_xfer_msg(adap);
|
||||
if (i2c->state == CGBC_I2C_STATE_DONE)
|
||||
return num;
|
||||
|
||||
if (i2c->state == CGBC_I2C_STATE_ERROR)
|
||||
return ret;
|
||||
|
||||
if (ret == 0)
|
||||
timeout = jiffies + HZ;
|
||||
}
|
||||
|
||||
i2c->state = CGBC_I2C_STATE_ERROR;
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static u32 cgbc_i2c_func(struct i2c_adapter *adap)
|
||||
{
|
||||
return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~(I2C_FUNC_SMBUS_QUICK));
|
||||
}
|
||||
|
||||
static const struct i2c_algorithm cgbc_i2c_algorithm = {
|
||||
.master_xfer = cgbc_i2c_xfer,
|
||||
.functionality = cgbc_i2c_func,
|
||||
};
|
||||
|
||||
static struct i2c_algo_cgbc_data cgbc_i2c_algo_data[] = {
|
||||
{ .bus_id = CGBC_I2C_PRIMARY_BUS_ID },
|
||||
{ .bus_id = CGBC_I2C_PM_BUS_ID },
|
||||
};
|
||||
|
||||
static const struct i2c_adapter cgbc_i2c_adapter[] = {
|
||||
{
|
||||
.owner = THIS_MODULE,
|
||||
.name = "Congatec General Purpose I2C adapter",
|
||||
.class = I2C_CLASS_DEPRECATED,
|
||||
.algo = &cgbc_i2c_algorithm,
|
||||
.algo_data = &cgbc_i2c_algo_data[0],
|
||||
.nr = -1,
|
||||
},
|
||||
{
|
||||
.owner = THIS_MODULE,
|
||||
.name = "Congatec Power Management I2C adapter",
|
||||
.class = I2C_CLASS_DEPRECATED,
|
||||
.algo = &cgbc_i2c_algorithm,
|
||||
.algo_data = &cgbc_i2c_algo_data[1],
|
||||
.nr = -1,
|
||||
},
|
||||
};
|
||||
|
||||
static int cgbc_i2c_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct cgbc_device_data *cgbc = dev_get_drvdata(pdev->dev.parent);
|
||||
struct cgbc_i2c_data *i2c;
|
||||
int ret;
|
||||
|
||||
i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL);
|
||||
if (!i2c)
|
||||
return -ENOMEM;
|
||||
|
||||
i2c->cgbc = cgbc;
|
||||
i2c->dev = &pdev->dev;
|
||||
i2c->adap = cgbc_i2c_adapter[pdev->id];
|
||||
i2c->adap.dev.parent = i2c->dev;
|
||||
i2c_set_adapdata(&i2c->adap, i2c);
|
||||
platform_set_drvdata(pdev, i2c);
|
||||
|
||||
ret = cgbc_i2c_set_frequency(&i2c->adap, I2C_MAX_STANDARD_MODE_FREQ);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return i2c_add_numbered_adapter(&i2c->adap);
|
||||
}
|
||||
|
||||
static void cgbc_i2c_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct cgbc_i2c_data *i2c = platform_get_drvdata(pdev);
|
||||
|
||||
i2c_del_adapter(&i2c->adap);
|
||||
}
|
||||
|
||||
static struct platform_driver cgbc_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "cgbc-i2c",
|
||||
},
|
||||
.probe = cgbc_i2c_probe,
|
||||
.remove_new = cgbc_i2c_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(cgbc_i2c_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Congatec Board Controller I2C Driver");
|
||||
MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:cgbc_i2c");
|
@ -531,12 +531,7 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
|
||||
* Legacy GPIO number, so request the GPIO here and
|
||||
* convert it to descriptor.
|
||||
*/
|
||||
unsigned flags = GPIOF_IN;
|
||||
|
||||
if (button->active_low)
|
||||
flags |= GPIOF_ACTIVE_LOW;
|
||||
|
||||
error = devm_gpio_request_one(dev, button->gpio, flags, desc);
|
||||
error = devm_gpio_request_one(dev, button->gpio, GPIOF_IN, desc);
|
||||
if (error < 0) {
|
||||
dev_err(dev, "Failed to request GPIO %d, error %d\n",
|
||||
button->gpio, error);
|
||||
@ -546,6 +541,9 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
|
||||
bdata->gpiod = gpio_to_desc(button->gpio);
|
||||
if (!bdata->gpiod)
|
||||
return -EINVAL;
|
||||
|
||||
if (button->active_low ^ gpiod_is_active_low(bdata->gpiod))
|
||||
gpiod_toggle_active_low(bdata->gpiod);
|
||||
}
|
||||
|
||||
if (bdata->gpiod) {
|
||||
|
@ -306,13 +306,8 @@ static int gpio_keys_polled_probe(struct platform_device *pdev)
|
||||
* Legacy GPIO number so request the GPIO here and
|
||||
* convert it to descriptor.
|
||||
*/
|
||||
unsigned flags = GPIOF_IN;
|
||||
|
||||
if (button->active_low)
|
||||
flags |= GPIOF_ACTIVE_LOW;
|
||||
|
||||
error = devm_gpio_request_one(dev, button->gpio,
|
||||
flags, button->desc ? : DRV_NAME);
|
||||
error = devm_gpio_request_one(dev, button->gpio, GPIOF_IN,
|
||||
button->desc ? : DRV_NAME);
|
||||
if (error)
|
||||
return dev_err_probe(dev, error,
|
||||
"unable to claim gpio %u\n",
|
||||
@ -325,6 +320,9 @@ static int gpio_keys_polled_probe(struct platform_device *pdev)
|
||||
button->gpio);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (button->active_low ^ gpiod_is_active_low(bdata->gpiod))
|
||||
gpiod_toggle_active_low(bdata->gpiod);
|
||||
}
|
||||
|
||||
bdata->last_state = -1;
|
||||
|
@ -217,7 +217,6 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx,
|
||||
const struct gpio_led *template)
|
||||
{
|
||||
struct gpio_desc *gpiod;
|
||||
unsigned long flags = GPIOF_OUT_INIT_LOW;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
@ -244,10 +243,7 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx,
|
||||
if (!gpio_is_valid(template->gpio))
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
if (template->active_low)
|
||||
flags |= GPIOF_ACTIVE_LOW;
|
||||
|
||||
ret = devm_gpio_request_one(dev, template->gpio, flags,
|
||||
ret = devm_gpio_request_one(dev, template->gpio, GPIOF_OUT_INIT_LOW,
|
||||
template->name);
|
||||
if (ret < 0)
|
||||
return ERR_PTR(ret);
|
||||
@ -256,6 +252,9 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx,
|
||||
if (!gpiod)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
if (template->active_low ^ gpiod_is_active_low(gpiod))
|
||||
gpiod_toggle_active_low(gpiod);
|
||||
|
||||
return gpiod;
|
||||
}
|
||||
|
||||
|
@ -236,6 +236,18 @@ config MFD_AXP20X_RSB
|
||||
components like regulators or the PEK (Power Enable Key) under the
|
||||
corresponding menus.
|
||||
|
||||
config MFD_CGBC
|
||||
tristate "Congatec Board Controller"
|
||||
select MFD_CORE
|
||||
depends on X86
|
||||
help
|
||||
This is the core driver of the Board Controller found on some Congatec
|
||||
SMARC modules. The Board Controller provides functions like watchdog,
|
||||
I2C busses, and GPIO controller.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called cgbc-core.
|
||||
|
||||
config MFD_CROS_EC_DEV
|
||||
tristate "ChromeOS Embedded Controller multifunction device"
|
||||
select MFD_CORE
|
||||
|
@ -13,6 +13,7 @@ obj-$(CONFIG_MFD_SM501) += sm501.o
|
||||
obj-$(CONFIG_ARCH_BCM2835) += bcm2835-pm.o
|
||||
obj-$(CONFIG_MFD_BCM590XX) += bcm590xx.o
|
||||
obj-$(CONFIG_MFD_BD9571MWV) += bd9571mwv.o
|
||||
obj-$(CONFIG_MFD_CGBC) += cgbc-core.o
|
||||
obj-$(CONFIG_MFD_CROS_EC_DEV) += cros_ec_dev.o
|
||||
obj-$(CONFIG_MFD_CS42L43) += cs42l43.o
|
||||
obj-$(CONFIG_MFD_CS42L43_I2C) += cs42l43-i2c.o
|
||||
|
411
drivers/mfd/cgbc-core.c
Normal file
411
drivers/mfd/cgbc-core.c
Normal file
@ -0,0 +1,411 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Congatec Board Controller core driver.
|
||||
*
|
||||
* The x86 Congatec modules have an embedded micro controller named Board
|
||||
* Controller. This Board Controller has a Watchdog timer, some GPIOs, and two
|
||||
* I2C busses.
|
||||
*
|
||||
* Copyright (C) 2024 Bootlin
|
||||
*
|
||||
* Author: Thomas Richard <thomas.richard@bootlin.com>
|
||||
*/
|
||||
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/mfd/cgbc.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
#define CGBC_IO_SESSION_BASE 0x0E20
|
||||
#define CGBC_IO_SESSION_END 0x0E30
|
||||
#define CGBC_IO_CMD_BASE 0x0E00
|
||||
#define CGBC_IO_CMD_END 0x0E10
|
||||
|
||||
#define CGBC_MASK_STATUS (BIT(6) | BIT(7))
|
||||
#define CGBC_MASK_DATA_COUNT 0x1F
|
||||
#define CGBC_MASK_ERROR_CODE 0x1F
|
||||
|
||||
#define CGBC_STATUS_DATA_READY 0x00
|
||||
#define CGBC_STATUS_CMD_READY BIT(6)
|
||||
#define CGBC_STATUS_ERROR (BIT(6) | BIT(7))
|
||||
|
||||
#define CGBC_SESSION_CMD 0x00
|
||||
#define CGBC_SESSION_CMD_IDLE 0x00
|
||||
#define CGBC_SESSION_CMD_REQUEST 0x01
|
||||
#define CGBC_SESSION_DATA 0x01
|
||||
#define CGBC_SESSION_STATUS 0x02
|
||||
#define CGBC_SESSION_STATUS_FREE 0x03
|
||||
#define CGBC_SESSION_ACCESS 0x04
|
||||
#define CGBC_SESSION_ACCESS_GAINED 0x00
|
||||
|
||||
#define CGBC_SESSION_VALID_MIN 0x02
|
||||
#define CGBC_SESSION_VALID_MAX 0xFE
|
||||
|
||||
#define CGBC_CMD_STROBE 0x00
|
||||
#define CGBC_CMD_INDEX 0x02
|
||||
#define CGBC_CMD_INDEX_CBM_MAN8 0x00
|
||||
#define CGBC_CMD_INDEX_CBM_AUTO32 0x03
|
||||
#define CGBC_CMD_DATA 0x04
|
||||
#define CGBC_CMD_ACCESS 0x0C
|
||||
|
||||
#define CGBC_CMD_GET_FW_REV 0x21
|
||||
|
||||
static struct platform_device *cgbc_pdev;
|
||||
|
||||
/* Wait the Board Controller is ready to receive some session commands */
|
||||
static int cgbc_wait_device(struct cgbc_device_data *cgbc)
|
||||
{
|
||||
u16 status;
|
||||
int ret;
|
||||
|
||||
ret = readx_poll_timeout(ioread16, cgbc->io_session + CGBC_SESSION_STATUS, status,
|
||||
status == CGBC_SESSION_STATUS_FREE, 0, 500000);
|
||||
|
||||
if (ret || ioread32(cgbc->io_session + CGBC_SESSION_ACCESS))
|
||||
ret = -ENODEV;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cgbc_session_command(struct cgbc_device_data *cgbc, u8 cmd)
|
||||
{
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
ret = readx_poll_timeout(ioread8, cgbc->io_session + CGBC_SESSION_CMD, val,
|
||||
val == CGBC_SESSION_CMD_IDLE, 0, 100000);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
iowrite8(cmd, cgbc->io_session + CGBC_SESSION_CMD);
|
||||
|
||||
ret = readx_poll_timeout(ioread8, cgbc->io_session + CGBC_SESSION_CMD, val,
|
||||
val == CGBC_SESSION_CMD_IDLE, 0, 100000);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = (int)ioread8(cgbc->io_session + CGBC_SESSION_DATA);
|
||||
|
||||
iowrite8(CGBC_SESSION_STATUS_FREE, cgbc->io_session + CGBC_SESSION_STATUS);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cgbc_session_request(struct cgbc_device_data *cgbc)
|
||||
{
|
||||
unsigned int ret;
|
||||
|
||||
ret = cgbc_wait_device(cgbc);
|
||||
|
||||
if (ret)
|
||||
return dev_err_probe(cgbc->dev, ret, "device not found or not ready\n");
|
||||
|
||||
cgbc->session = cgbc_session_command(cgbc, CGBC_SESSION_CMD_REQUEST);
|
||||
|
||||
/* The Board Controller sent us a wrong session handle, we cannot communicate with it */
|
||||
if (cgbc->session < CGBC_SESSION_VALID_MIN || cgbc->session > CGBC_SESSION_VALID_MAX)
|
||||
return dev_err_probe(cgbc->dev, -ECONNREFUSED,
|
||||
"failed to get a valid session handle\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cgbc_session_release(struct cgbc_device_data *cgbc)
|
||||
{
|
||||
if (cgbc_session_command(cgbc, cgbc->session) != cgbc->session)
|
||||
dev_warn(cgbc->dev, "failed to release session\n");
|
||||
}
|
||||
|
||||
static bool cgbc_command_lock(struct cgbc_device_data *cgbc)
|
||||
{
|
||||
iowrite8(cgbc->session, cgbc->io_cmd + CGBC_CMD_ACCESS);
|
||||
|
||||
return ioread8(cgbc->io_cmd + CGBC_CMD_ACCESS) == cgbc->session;
|
||||
}
|
||||
|
||||
static void cgbc_command_unlock(struct cgbc_device_data *cgbc)
|
||||
{
|
||||
iowrite8(cgbc->session, cgbc->io_cmd + CGBC_CMD_ACCESS);
|
||||
}
|
||||
|
||||
int cgbc_command(struct cgbc_device_data *cgbc, void *cmd, unsigned int cmd_size, void *data,
|
||||
unsigned int data_size, u8 *status)
|
||||
{
|
||||
u8 checksum = 0, data_checksum = 0, istatus = 0, val;
|
||||
u8 *_data = (u8 *)data;
|
||||
u8 *_cmd = (u8 *)cmd;
|
||||
int mode_change = -1;
|
||||
bool lock;
|
||||
int ret, i;
|
||||
|
||||
mutex_lock(&cgbc->lock);
|
||||
|
||||
/* Request access */
|
||||
ret = readx_poll_timeout(cgbc_command_lock, cgbc, lock, lock, 0, 100000);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* Wait board controller is ready */
|
||||
ret = readx_poll_timeout(ioread8, cgbc->io_cmd + CGBC_CMD_STROBE, val,
|
||||
val == CGBC_CMD_STROBE, 0, 100000);
|
||||
if (ret)
|
||||
goto release;
|
||||
|
||||
/* Write command packet */
|
||||
if (cmd_size <= 2) {
|
||||
iowrite8(CGBC_CMD_INDEX_CBM_MAN8, cgbc->io_cmd + CGBC_CMD_INDEX);
|
||||
} else {
|
||||
iowrite8(CGBC_CMD_INDEX_CBM_AUTO32, cgbc->io_cmd + CGBC_CMD_INDEX);
|
||||
if ((cmd_size % 4) != 0x03)
|
||||
mode_change = (cmd_size & 0xFFFC) - 1;
|
||||
}
|
||||
|
||||
for (i = 0; i < cmd_size; i++) {
|
||||
iowrite8(_cmd[i], cgbc->io_cmd + CGBC_CMD_DATA + (i % 4));
|
||||
checksum ^= _cmd[i];
|
||||
if (mode_change == i)
|
||||
iowrite8((i + 1) | CGBC_CMD_INDEX_CBM_MAN8, cgbc->io_cmd + CGBC_CMD_INDEX);
|
||||
}
|
||||
|
||||
/* Append checksum byte */
|
||||
iowrite8(checksum, cgbc->io_cmd + CGBC_CMD_DATA + (i % 4));
|
||||
|
||||
/* Perform command strobe */
|
||||
iowrite8(cgbc->session, cgbc->io_cmd + CGBC_CMD_STROBE);
|
||||
|
||||
/* Rewind cmd buffer index */
|
||||
iowrite8(CGBC_CMD_INDEX_CBM_AUTO32, cgbc->io_cmd + CGBC_CMD_INDEX);
|
||||
|
||||
/* Wait command completion */
|
||||
ret = read_poll_timeout(ioread8, val, val == CGBC_CMD_STROBE, 0, 100000, false,
|
||||
cgbc->io_cmd + CGBC_CMD_STROBE);
|
||||
if (ret)
|
||||
goto release;
|
||||
|
||||
istatus = ioread8(cgbc->io_cmd + CGBC_CMD_DATA);
|
||||
checksum = istatus;
|
||||
|
||||
/* Check command status */
|
||||
switch (istatus & CGBC_MASK_STATUS) {
|
||||
case CGBC_STATUS_DATA_READY:
|
||||
if (istatus > data_size)
|
||||
istatus = data_size;
|
||||
for (i = 0; i < istatus; i++) {
|
||||
_data[i] = ioread8(cgbc->io_cmd + CGBC_CMD_DATA + ((i + 1) % 4));
|
||||
checksum ^= _data[i];
|
||||
}
|
||||
data_checksum = ioread8(cgbc->io_cmd + CGBC_CMD_DATA + ((i + 1) % 4));
|
||||
istatus &= CGBC_MASK_DATA_COUNT;
|
||||
break;
|
||||
case CGBC_STATUS_ERROR:
|
||||
case CGBC_STATUS_CMD_READY:
|
||||
data_checksum = ioread8(cgbc->io_cmd + CGBC_CMD_DATA + 1);
|
||||
if ((istatus & CGBC_MASK_STATUS) == CGBC_STATUS_ERROR)
|
||||
ret = -EIO;
|
||||
istatus = istatus & CGBC_MASK_ERROR_CODE;
|
||||
break;
|
||||
default:
|
||||
data_checksum = ioread8(cgbc->io_cmd + CGBC_CMD_DATA + 1);
|
||||
istatus &= CGBC_MASK_ERROR_CODE;
|
||||
ret = -EIO;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Checksum verification */
|
||||
if (ret == 0 && data_checksum != checksum)
|
||||
ret = -EIO;
|
||||
|
||||
release:
|
||||
cgbc_command_unlock(cgbc);
|
||||
|
||||
out:
|
||||
mutex_unlock(&cgbc->lock);
|
||||
|
||||
if (status)
|
||||
*status = istatus;
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cgbc_command);
|
||||
|
||||
static struct mfd_cell cgbc_devs[] = {
|
||||
{ .name = "cgbc-wdt" },
|
||||
{ .name = "cgbc-gpio" },
|
||||
{ .name = "cgbc-i2c", .id = 1 },
|
||||
{ .name = "cgbc-i2c", .id = 2 },
|
||||
};
|
||||
|
||||
static int cgbc_map(struct cgbc_device_data *cgbc)
|
||||
{
|
||||
struct device *dev = cgbc->dev;
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct resource *ioport;
|
||||
|
||||
ioport = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
if (!ioport)
|
||||
return -EINVAL;
|
||||
|
||||
cgbc->io_session = devm_ioport_map(dev, ioport->start, resource_size(ioport));
|
||||
if (!cgbc->io_session)
|
||||
return -ENOMEM;
|
||||
|
||||
ioport = platform_get_resource(pdev, IORESOURCE_IO, 1);
|
||||
if (!ioport)
|
||||
return -EINVAL;
|
||||
|
||||
cgbc->io_cmd = devm_ioport_map(dev, ioport->start, resource_size(ioport));
|
||||
if (!cgbc->io_cmd)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct resource cgbc_resources[] = {
|
||||
{
|
||||
.start = CGBC_IO_SESSION_BASE,
|
||||
.end = CGBC_IO_SESSION_END,
|
||||
.flags = IORESOURCE_IO,
|
||||
},
|
||||
{
|
||||
.start = CGBC_IO_CMD_BASE,
|
||||
.end = CGBC_IO_CMD_END,
|
||||
.flags = IORESOURCE_IO,
|
||||
},
|
||||
};
|
||||
|
||||
static ssize_t cgbc_version_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct cgbc_device_data *cgbc = dev_get_drvdata(dev);
|
||||
|
||||
return sysfs_emit(buf, "CGBCP%c%c%c\n", cgbc->version.feature, cgbc->version.major,
|
||||
cgbc->version.minor);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(cgbc_version);
|
||||
|
||||
static struct attribute *cgbc_attrs[] = {
|
||||
&dev_attr_cgbc_version.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
ATTRIBUTE_GROUPS(cgbc);
|
||||
|
||||
static int cgbc_get_version(struct cgbc_device_data *cgbc)
|
||||
{
|
||||
u8 cmd = CGBC_CMD_GET_FW_REV;
|
||||
u8 data[4];
|
||||
int ret;
|
||||
|
||||
ret = cgbc_command(cgbc, &cmd, 1, &data, sizeof(data), NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
cgbc->version.feature = data[0];
|
||||
cgbc->version.major = data[1];
|
||||
cgbc->version.minor = data[2];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cgbc_init_device(struct cgbc_device_data *cgbc)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = cgbc_session_request(cgbc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = cgbc_get_version(cgbc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return mfd_add_devices(cgbc->dev, -1, cgbc_devs, ARRAY_SIZE(cgbc_devs), NULL, 0, NULL);
|
||||
}
|
||||
|
||||
static int cgbc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct cgbc_device_data *cgbc;
|
||||
int ret;
|
||||
|
||||
cgbc = devm_kzalloc(dev, sizeof(*cgbc), GFP_KERNEL);
|
||||
if (!cgbc)
|
||||
return -ENOMEM;
|
||||
|
||||
cgbc->dev = dev;
|
||||
|
||||
ret = cgbc_map(cgbc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_init(&cgbc->lock);
|
||||
|
||||
platform_set_drvdata(pdev, cgbc);
|
||||
|
||||
return cgbc_init_device(cgbc);
|
||||
}
|
||||
|
||||
static void cgbc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct cgbc_device_data *cgbc = platform_get_drvdata(pdev);
|
||||
|
||||
cgbc_session_release(cgbc);
|
||||
|
||||
mfd_remove_devices(&pdev->dev);
|
||||
}
|
||||
|
||||
static struct platform_driver cgbc_driver = {
|
||||
.driver = {
|
||||
.name = "cgbc",
|
||||
.dev_groups = cgbc_groups,
|
||||
},
|
||||
.probe = cgbc_probe,
|
||||
.remove_new = cgbc_remove,
|
||||
};
|
||||
|
||||
static const struct dmi_system_id cgbc_dmi_table[] __initconst = {
|
||||
{
|
||||
.ident = "SA7",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "congatec"),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "conga-SA7"),
|
||||
},
|
||||
},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(dmi, cgbc_dmi_table);
|
||||
|
||||
static int __init cgbc_init(void)
|
||||
{
|
||||
const struct dmi_system_id *id;
|
||||
int ret = -ENODEV;
|
||||
|
||||
id = dmi_first_match(cgbc_dmi_table);
|
||||
if (IS_ERR_OR_NULL(id))
|
||||
return ret;
|
||||
|
||||
cgbc_pdev = platform_device_register_simple("cgbc", PLATFORM_DEVID_NONE, cgbc_resources,
|
||||
ARRAY_SIZE(cgbc_resources));
|
||||
if (IS_ERR(cgbc_pdev))
|
||||
return PTR_ERR(cgbc_pdev);
|
||||
|
||||
return platform_driver_register(&cgbc_driver);
|
||||
}
|
||||
|
||||
static void __exit cgbc_exit(void)
|
||||
{
|
||||
platform_device_unregister(cgbc_pdev);
|
||||
platform_driver_unregister(&cgbc_driver);
|
||||
}
|
||||
|
||||
module_init(cgbc_init);
|
||||
module_exit(cgbc_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Congatec Board Controller Core Driver");
|
||||
MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:cgbc-core");
|
@ -204,14 +204,8 @@ static int soc_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(skt->stat); i++) {
|
||||
if (gpio_is_valid(skt->stat[i].gpio)) {
|
||||
unsigned long flags = GPIOF_IN;
|
||||
|
||||
/* CD is active low by default */
|
||||
if (i == SOC_STAT_CD)
|
||||
flags |= GPIOF_ACTIVE_LOW;
|
||||
|
||||
ret = devm_gpio_request_one(skt->socket.dev.parent,
|
||||
skt->stat[i].gpio, flags,
|
||||
skt->stat[i].gpio, GPIOF_IN,
|
||||
skt->stat[i].name);
|
||||
if (ret) {
|
||||
__soc_pcmcia_hw_shutdown(skt, i);
|
||||
@ -219,6 +213,10 @@ static int soc_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
|
||||
}
|
||||
|
||||
skt->stat[i].desc = gpio_to_desc(skt->stat[i].gpio);
|
||||
|
||||
/* CD is active low by default */
|
||||
if ((i == SOC_STAT_CD) ^ gpiod_is_active_low(skt->stat[i].desc))
|
||||
gpiod_toggle_active_low(skt->stat[i].desc);
|
||||
}
|
||||
|
||||
if (i < SOC_STAT_VS1 && skt->stat[i].desc) {
|
||||
|
@ -2355,18 +2355,19 @@ static int pxa_udc_probe(struct platform_device *pdev)
|
||||
struct pxa_udc *udc = &memory;
|
||||
int retval = 0, gpio;
|
||||
struct pxa2xx_udc_mach_info *mach = dev_get_platdata(&pdev->dev);
|
||||
unsigned long gpio_flags;
|
||||
|
||||
if (mach) {
|
||||
gpio_flags = mach->gpio_pullup_inverted ? GPIOF_ACTIVE_LOW : 0;
|
||||
gpio = mach->gpio_pullup;
|
||||
if (gpio_is_valid(gpio)) {
|
||||
retval = devm_gpio_request_one(&pdev->dev, gpio,
|
||||
gpio_flags,
|
||||
GPIOF_OUT_INIT_LOW,
|
||||
"USB D+ pullup");
|
||||
if (retval)
|
||||
return retval;
|
||||
udc->gpiod = gpio_to_desc(mach->gpio_pullup);
|
||||
|
||||
if (mach->gpio_pullup_inverted ^ gpiod_is_active_low(udc->gpiod))
|
||||
gpiod_toggle_active_low(udc->gpiod);
|
||||
}
|
||||
udc->udc_command = mach->udc_command;
|
||||
} else {
|
||||
|
@ -1151,6 +1151,16 @@ config ALIM7101_WDT
|
||||
|
||||
Most people will say N.
|
||||
|
||||
config CGBC_WDT
|
||||
tristate "Congatec Board Controller Watchdog Timer"
|
||||
depends on MFD_CGBC
|
||||
select WATCHDOG_CORE
|
||||
help
|
||||
Enables watchdog timer support for the Congatec Board Controller.
|
||||
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called cgbc_wdt.
|
||||
|
||||
config EBC_C384_WDT
|
||||
tristate "WinSystems EBC-C384 Watchdog Timer"
|
||||
depends on (X86 || COMPILE_TEST) && HAS_IOPORT
|
||||
|
@ -107,6 +107,7 @@ obj-$(CONFIG_ADVANTECH_WDT) += advantechwdt.o
|
||||
obj-$(CONFIG_ADVANTECH_EC_WDT) += advantech_ec_wdt.o
|
||||
obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o
|
||||
obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o
|
||||
obj-$(CONFIG_CGBC_WDT) += cgbc_wdt.o
|
||||
obj-$(CONFIG_EBC_C384_WDT) += ebc-c384_wdt.o
|
||||
obj-$(CONFIG_EXAR_WDT) += exar_wdt.o
|
||||
obj-$(CONFIG_F71808E_WDT) += f71808e_wdt.o
|
||||
|
211
drivers/watchdog/cgbc_wdt.c
Normal file
211
drivers/watchdog/cgbc_wdt.c
Normal file
@ -0,0 +1,211 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Congatec Board Controller watchdog driver
|
||||
*
|
||||
* Copyright (C) 2024 Bootlin
|
||||
* Author: Thomas Richard <thomas.richard@bootlin.com>
|
||||
*/
|
||||
|
||||
#include <linux/build_bug.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/watchdog.h>
|
||||
|
||||
#include <linux/mfd/cgbc.h>
|
||||
|
||||
#define CGBC_WDT_CMD_TRIGGER 0x27
|
||||
#define CGBC_WDT_CMD_INIT 0x28
|
||||
#define CGBC_WDT_DISABLE 0x00
|
||||
|
||||
#define CGBC_WDT_MODE_SINGLE_EVENT 0x02
|
||||
|
||||
#define CGBC_WDT_MIN_TIMEOUT 1
|
||||
#define CGBC_WDT_MAX_TIMEOUT ((U32_MAX >> 8) / 1000)
|
||||
|
||||
#define CGBC_WDT_DEFAULT_TIMEOUT 30
|
||||
#define CGBC_WDT_DEFAULT_PRETIMEOUT 0
|
||||
|
||||
enum action {
|
||||
ACTION_INT = 0,
|
||||
ACTION_SMI,
|
||||
ACTION_RESET,
|
||||
ACTION_BUTTON,
|
||||
};
|
||||
|
||||
static unsigned int timeout;
|
||||
module_param(timeout, uint, 0);
|
||||
MODULE_PARM_DESC(timeout,
|
||||
"Watchdog timeout in seconds. (>=0, default="
|
||||
__MODULE_STRING(CGBC_WDT_DEFAULT_TIMEOUT) ")");
|
||||
|
||||
static unsigned int pretimeout = CGBC_WDT_DEFAULT_PRETIMEOUT;
|
||||
module_param(pretimeout, uint, 0);
|
||||
MODULE_PARM_DESC(pretimeout,
|
||||
"Watchdog pretimeout in seconds. (>=0, default="
|
||||
__MODULE_STRING(CGBC_WDT_DEFAULT_PRETIMEOUT) ")");
|
||||
|
||||
static bool nowayout = WATCHDOG_NOWAYOUT;
|
||||
module_param(nowayout, bool, 0);
|
||||
MODULE_PARM_DESC(nowayout,
|
||||
"Watchdog cannot be stopped once started (default="
|
||||
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
|
||||
|
||||
struct cgbc_wdt_data {
|
||||
struct cgbc_device_data *cgbc;
|
||||
struct watchdog_device wdd;
|
||||
};
|
||||
|
||||
struct cgbc_wdt_cmd_cfg {
|
||||
u8 cmd;
|
||||
u8 mode;
|
||||
u8 action;
|
||||
u8 timeout1[3];
|
||||
u8 timeout2[3];
|
||||
u8 reserved[3];
|
||||
u8 delay[3];
|
||||
} __packed;
|
||||
|
||||
static_assert(sizeof(struct cgbc_wdt_cmd_cfg) == 15);
|
||||
|
||||
static int cgbc_wdt_start(struct watchdog_device *wdd)
|
||||
{
|
||||
struct cgbc_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
|
||||
struct cgbc_device_data *cgbc = wdt_data->cgbc;
|
||||
unsigned int timeout1 = (wdd->timeout - wdd->pretimeout) * 1000;
|
||||
unsigned int timeout2 = wdd->pretimeout * 1000;
|
||||
u8 action;
|
||||
|
||||
struct cgbc_wdt_cmd_cfg cmd_start = {
|
||||
.cmd = CGBC_WDT_CMD_INIT,
|
||||
.mode = CGBC_WDT_MODE_SINGLE_EVENT,
|
||||
.timeout1[0] = (u8)timeout1,
|
||||
.timeout1[1] = (u8)(timeout1 >> 8),
|
||||
.timeout1[2] = (u8)(timeout1 >> 16),
|
||||
.timeout2[0] = (u8)timeout2,
|
||||
.timeout2[1] = (u8)(timeout2 >> 8),
|
||||
.timeout2[2] = (u8)(timeout2 >> 16),
|
||||
};
|
||||
|
||||
if (wdd->pretimeout) {
|
||||
action = 2;
|
||||
action |= ACTION_SMI << 2;
|
||||
action |= ACTION_RESET << 4;
|
||||
} else {
|
||||
action = 1;
|
||||
action |= ACTION_RESET << 2;
|
||||
}
|
||||
|
||||
cmd_start.action = action;
|
||||
|
||||
return cgbc_command(cgbc, &cmd_start, sizeof(cmd_start), NULL, 0, NULL);
|
||||
}
|
||||
|
||||
static int cgbc_wdt_stop(struct watchdog_device *wdd)
|
||||
{
|
||||
struct cgbc_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
|
||||
struct cgbc_device_data *cgbc = wdt_data->cgbc;
|
||||
struct cgbc_wdt_cmd_cfg cmd_stop = {
|
||||
.cmd = CGBC_WDT_CMD_INIT,
|
||||
.mode = CGBC_WDT_DISABLE,
|
||||
};
|
||||
|
||||
return cgbc_command(cgbc, &cmd_stop, sizeof(cmd_stop), NULL, 0, NULL);
|
||||
}
|
||||
|
||||
static int cgbc_wdt_keepalive(struct watchdog_device *wdd)
|
||||
{
|
||||
struct cgbc_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
|
||||
struct cgbc_device_data *cgbc = wdt_data->cgbc;
|
||||
u8 cmd_ping = CGBC_WDT_CMD_TRIGGER;
|
||||
|
||||
return cgbc_command(cgbc, &cmd_ping, sizeof(cmd_ping), NULL, 0, NULL);
|
||||
}
|
||||
|
||||
static int cgbc_wdt_set_pretimeout(struct watchdog_device *wdd,
|
||||
unsigned int pretimeout)
|
||||
{
|
||||
wdd->pretimeout = pretimeout;
|
||||
|
||||
if (watchdog_active(wdd))
|
||||
return cgbc_wdt_start(wdd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cgbc_wdt_set_timeout(struct watchdog_device *wdd,
|
||||
unsigned int timeout)
|
||||
{
|
||||
if (timeout < wdd->pretimeout)
|
||||
wdd->pretimeout = 0;
|
||||
|
||||
wdd->timeout = timeout;
|
||||
|
||||
if (watchdog_active(wdd))
|
||||
return cgbc_wdt_start(wdd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct watchdog_info cgbc_wdt_info = {
|
||||
.identity = "CGBC Watchdog",
|
||||
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
|
||||
WDIOF_MAGICCLOSE | WDIOF_PRETIMEOUT
|
||||
};
|
||||
|
||||
static const struct watchdog_ops cgbc_wdt_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.start = cgbc_wdt_start,
|
||||
.stop = cgbc_wdt_stop,
|
||||
.ping = cgbc_wdt_keepalive,
|
||||
.set_timeout = cgbc_wdt_set_timeout,
|
||||
.set_pretimeout = cgbc_wdt_set_pretimeout,
|
||||
};
|
||||
|
||||
static int cgbc_wdt_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct cgbc_device_data *cgbc = dev_get_drvdata(pdev->dev.parent);
|
||||
struct device *dev = &pdev->dev;
|
||||
struct cgbc_wdt_data *wdt_data;
|
||||
struct watchdog_device *wdd;
|
||||
|
||||
wdt_data = devm_kzalloc(dev, sizeof(*wdt_data), GFP_KERNEL);
|
||||
if (!wdt_data)
|
||||
return -ENOMEM;
|
||||
|
||||
wdt_data->cgbc = cgbc;
|
||||
wdd = &wdt_data->wdd;
|
||||
wdd->parent = dev;
|
||||
|
||||
wdd->info = &cgbc_wdt_info;
|
||||
wdd->ops = &cgbc_wdt_ops;
|
||||
wdd->max_timeout = CGBC_WDT_MAX_TIMEOUT;
|
||||
wdd->min_timeout = CGBC_WDT_MIN_TIMEOUT;
|
||||
|
||||
watchdog_set_drvdata(wdd, wdt_data);
|
||||
watchdog_set_nowayout(wdd, nowayout);
|
||||
|
||||
wdd->timeout = CGBC_WDT_DEFAULT_TIMEOUT;
|
||||
watchdog_init_timeout(wdd, timeout, dev);
|
||||
cgbc_wdt_set_pretimeout(wdd, pretimeout);
|
||||
|
||||
platform_set_drvdata(pdev, wdt_data);
|
||||
watchdog_stop_on_reboot(wdd);
|
||||
watchdog_stop_on_unregister(wdd);
|
||||
|
||||
return devm_watchdog_register_device(dev, wdd);
|
||||
}
|
||||
|
||||
static struct platform_driver cgbc_wdt_driver = {
|
||||
.driver = {
|
||||
.name = "cgbc-wdt",
|
||||
},
|
||||
.probe = cgbc_wdt_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(cgbc_wdt_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Congatec Board Controller Watchdog Driver");
|
||||
MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>");
|
||||
MODULE_LICENSE("GPL");
|
@ -21,9 +21,6 @@ struct device;
|
||||
#define GPIOF_OUT_INIT_LOW ((0 << 0) | (0 << 1))
|
||||
#define GPIOF_OUT_INIT_HIGH ((0 << 0) | (1 << 1))
|
||||
|
||||
/* Gpio pin is active-low */
|
||||
#define GPIOF_ACTIVE_LOW (1 << 2)
|
||||
|
||||
/**
|
||||
* struct gpio - a structure describing a GPIO with configuration
|
||||
* @gpio: the GPIO number
|
||||
|
44
include/linux/mfd/cgbc.h
Normal file
44
include/linux/mfd/cgbc.h
Normal file
@ -0,0 +1,44 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/*
|
||||
* Congatec Board Controller driver definitions
|
||||
*
|
||||
* Copyright (C) 2024 Bootlin
|
||||
* Author: Thomas Richard <thomas.richard@bootlin.com>
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_MFD_CGBC_H_
|
||||
|
||||
/**
|
||||
* struct cgbc_version - Board Controller device version structure
|
||||
* @feature: Board Controller feature number
|
||||
* @major: Board Controller major revision
|
||||
* @minor: Board Controller minor revision
|
||||
*/
|
||||
struct cgbc_version {
|
||||
unsigned char feature;
|
||||
unsigned char major;
|
||||
unsigned char minor;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct cgbc_device_data - Internal representation of the Board Controller device
|
||||
* @io_session: Pointer to the session IO memory
|
||||
* @io_cmd: Pointer to the command IO memory
|
||||
* @session: Session id returned by the Board Controller
|
||||
* @dev: Pointer to kernel device structure
|
||||
* @cgbc_version: Board Controller version structure
|
||||
* @mutex: Board Controller mutex
|
||||
*/
|
||||
struct cgbc_device_data {
|
||||
void __iomem *io_session;
|
||||
void __iomem *io_cmd;
|
||||
u8 session;
|
||||
struct device *dev;
|
||||
struct cgbc_version version;
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
int cgbc_command(struct cgbc_device_data *cgbc, void *cmd, unsigned int cmd_size,
|
||||
void *data, unsigned int data_size, u8 *status);
|
||||
|
||||
#endif /*_LINUX_MFD_CGBC_H_*/
|
@ -69,14 +69,14 @@ int monitor_device(const char *device_name,
|
||||
}
|
||||
|
||||
if (num_lines == 1) {
|
||||
fprintf(stdout, "Monitoring line %d on %s\n", lines[0], device_name);
|
||||
fprintf(stdout, "Monitoring line %u on %s\n", lines[0], device_name);
|
||||
fprintf(stdout, "Initial line value: %d\n",
|
||||
gpiotools_test_bit(values.bits, 0));
|
||||
} else {
|
||||
fprintf(stdout, "Monitoring lines %d", lines[0]);
|
||||
fprintf(stdout, "Monitoring lines %u", lines[0]);
|
||||
for (i = 1; i < num_lines - 1; i++)
|
||||
fprintf(stdout, ", %d", lines[i]);
|
||||
fprintf(stdout, " and %d on %s\n", lines[i], device_name);
|
||||
fprintf(stdout, ", %u", lines[i]);
|
||||
fprintf(stdout, " and %u on %s\n", lines[i], device_name);
|
||||
fprintf(stdout, "Initial line values: %d",
|
||||
gpiotools_test_bit(values.bits, 0));
|
||||
for (i = 1; i < num_lines - 1; i++)
|
||||
|
@ -113,7 +113,7 @@ init_cpu()
|
||||
taskset -p "$newmask" "$p" || continue
|
||||
done 2>/dev/null >/dev/null
|
||||
|
||||
# Big hammer! Working with 'rcu_momentary_dyntick_idle()' for a more fine-grained solution
|
||||
# Big hammer! Working with 'rcu_momentary_eqs()' for a more fine-grained solution
|
||||
# still printed warnings. Same for re-enabling the stall detector after sampling.
|
||||
echo 1 > /sys/module/rcupdate/parameters/rcu_cpu_stall_suppress
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user