* Re: [PATCH v5 03/14] pinctrl: add a pinctrl driver for the Ingenic jz47xx SoCs
From: Paul Cercueil @ 2017-05-03 9:12 UTC (permalink / raw)
To: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
Ralf Baechle
Cc: Boris Brezillon, Thierry Reding, Bartlomiej Zolnierkiewicz,
Maarten ter Huurne, Lars-Peter Clausen, Paul Burton, james.hogan,
linux-gpio, devicetree, linux-kernel, linux-mips, linux-mmc,
linux-mtd, linux-pwm, linux-fbdev
In-Reply-To: <20170428200824.10906-4-paul@crapouillou.net>
The dependency on MFD is gone but now I notice I forgot to remove the
'select MFD_CORE'
in the Kconfig. It'd be great if you can make a quick edit when merging
this,
otherwise I'll send a v6.
- Paul
Le 2017-04-28 22:08, Paul Cercueil a écrit :
> This driver handles pin configuration and pin muxing for the
> JZ4740 and JZ4780 SoCs from Ingenic.
>
> Signed-off-by: Paul Cercueil <paul@crapouillou.net>
> ---
> drivers/pinctrl/Kconfig | 10 +
> drivers/pinctrl/Makefile | 1 +
> drivers/pinctrl/pinctrl-ingenic.c | 852
> ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 863 insertions(+)
> create mode 100644 drivers/pinctrl/pinctrl-ingenic.c
>
> v2: Consider it's a new patch. Completely rewritten from v1.
> v3: 'unsigned' -> 'unsigned int'
> v4: Completely rewritten from v3.
> v5: Probe child devices directly instead of using MFD framework
>
> diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
> index 8f8c2af45781..82ce72fcb8e0 100644
> --- a/drivers/pinctrl/Kconfig
> +++ b/drivers/pinctrl/Kconfig
> @@ -285,6 +285,16 @@ config PINCTRL_ZYNQ
> help
> This selects the pinctrl driver for Xilinx Zynq.
>
> +config PINCTRL_INGENIC
> + bool "Pinctrl driver for the Ingenic JZ47xx SoCs"
> + default y
> + depends on MACH_INGENIC || COMPILE_TEST
> + select GENERIC_PINCONF
> + select GENERIC_PINCTRL_GROUPS
> + select GENERIC_PINMUX_FUNCTIONS
> + select REGMAP_MMIO
> + select MFD_CORE
> +
> source "drivers/pinctrl/aspeed/Kconfig"
> source "drivers/pinctrl/bcm/Kconfig"
> source "drivers/pinctrl/berlin/Kconfig"
> diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
> index a251f439626f..80f327239d4b 100644
> --- a/drivers/pinctrl/Makefile
> +++ b/drivers/pinctrl/Makefile
> @@ -38,6 +38,7 @@ obj-$(CONFIG_PINCTRL_LPC18XX) += pinctrl-lpc18xx.o
> obj-$(CONFIG_PINCTRL_TB10X) += pinctrl-tb10x.o
> obj-$(CONFIG_PINCTRL_ST) += pinctrl-st.o
> obj-$(CONFIG_PINCTRL_ZYNQ) += pinctrl-zynq.o
> +obj-$(CONFIG_PINCTRL_INGENIC) += pinctrl-ingenic.o
>
> obj-$(CONFIG_ARCH_ASPEED) += aspeed/
> obj-y += bcm/
> diff --git a/drivers/pinctrl/pinctrl-ingenic.c
> b/drivers/pinctrl/pinctrl-ingenic.c
> new file mode 100644
> index 000000000000..d8473d929cb1
> --- /dev/null
> +++ b/drivers/pinctrl/pinctrl-ingenic.c
> @@ -0,0 +1,852 @@
> +/*
> + * Ingenic SoCs pinctrl driver
> + *
> + * Copyright (c) 2017 Paul Cercueil <paul@crapouillou.net>
> + *
> + * License terms: GNU General Public License (GPL) version 2
> + */
> +
> +#include <linux/compiler.h>
> +#include <linux/gpio.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/of_device.h>
> +#include <linux/of_platform.h>
> +#include <linux/pinctrl/pinctrl.h>
> +#include <linux/pinctrl/pinmux.h>
> +#include <linux/pinctrl/pinconf.h>
> +#include <linux/pinctrl/pinconf-generic.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +
> +#include "core.h"
> +#include "pinconf.h"
> +#include "pinmux.h"
> +
> +#define JZ4740_GPIO_DATA 0x10
> +#define JZ4740_GPIO_PULL_DIS 0x30
> +#define JZ4740_GPIO_FUNC 0x40
> +#define JZ4740_GPIO_SELECT 0x50
> +#define JZ4740_GPIO_DIR 0x60
> +#define JZ4740_GPIO_TRIG 0x70
> +#define JZ4740_GPIO_FLAG 0x80
> +
> +#define JZ4770_GPIO_INT 0x10
> +#define JZ4770_GPIO_MSK 0x20
> +#define JZ4770_GPIO_PAT1 0x30
> +#define JZ4770_GPIO_PAT0 0x40
> +#define JZ4770_GPIO_FLAG 0x50
> +#define JZ4770_GPIO_PEN 0x70
> +
> +#define REG_SET(x) ((x) + 0x4)
> +#define REG_CLEAR(x) ((x) + 0x8)
> +
> +#define PINS_PER_GPIO_CHIP 32
> +
> +enum jz_version {
> + ID_JZ4740,
> + ID_JZ4770,
> + ID_JZ4780,
> +};
> +
> +struct ingenic_chip_info {
> + unsigned int num_chips;
> +
> + const struct group_desc *groups;
> + unsigned int num_groups;
> +
> + const struct function_desc *functions;
> + unsigned int num_functions;
> +
> + const u32 *pull_ups, *pull_downs;
> +};
> +
> +struct ingenic_pinctrl {
> + struct device *dev;
> + struct regmap *map;
> + struct pinctrl_dev *pctl;
> + struct pinctrl_pin_desc *pdesc;
> + enum jz_version version;
> +
> + const struct ingenic_chip_info *info;
> +};
> +
> +static const u32 jz4740_pull_ups[4] = {
> + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
> +};
> +
> +static const u32 jz4740_pull_downs[4] = {
> + 0x00000000, 0x00000000, 0x00000000, 0x00000000,
> +};
> +
> +static int jz4740_mmc_1bit_pins[] = { 0x69, 0x68, 0x6a, };
> +static int jz4740_mmc_4bit_pins[] = { 0x6b, 0x6c, 0x6d, };
> +static int jz4740_uart0_data_pins[] = { 0x7a, 0x79, };
> +static int jz4740_uart0_hwflow_pins[] = { 0x7e, 0x7f, };
> +static int jz4740_uart1_data_pins[] = { 0x7e, 0x7f, };
> +static int jz4740_lcd_8bit_pins[] = {
> + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x52, 0x53, 0x54,
> +};
> +static int jz4740_lcd_16bit_pins[] = {
> + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x55,
> +};
> +static int jz4740_lcd_18bit_pins[] = { 0x50, 0x51, };
> +static int jz4740_lcd_18bit_tft_pins[] = { 0x56, 0x57, 0x31, 0x32, };
> +static int jz4740_nand_cs1_pins[] = { 0x39, };
> +static int jz4740_nand_cs2_pins[] = { 0x3a, };
> +static int jz4740_nand_cs3_pins[] = { 0x3b, };
> +static int jz4740_nand_cs4_pins[] = { 0x3c, };
> +static int jz4740_pwm_pwm0_pins[] = { 0x77, };
> +static int jz4740_pwm_pwm1_pins[] = { 0x78, };
> +static int jz4740_pwm_pwm2_pins[] = { 0x79, };
> +static int jz4740_pwm_pwm3_pins[] = { 0x7a, };
> +static int jz4740_pwm_pwm4_pins[] = { 0x7b, };
> +static int jz4740_pwm_pwm5_pins[] = { 0x7c, };
> +static int jz4740_pwm_pwm6_pins[] = { 0x7e, };
> +static int jz4740_pwm_pwm7_pins[] = { 0x7f, };
> +
> +static int jz4740_mmc_1bit_funcs[] = { 0, 0, 0, };
> +static int jz4740_mmc_4bit_funcs[] = { 0, 0, 0, };
> +static int jz4740_uart0_data_funcs[] = { 1, 1, };
> +static int jz4740_uart0_hwflow_funcs[] = { 1, 1, };
> +static int jz4740_uart1_data_funcs[] = { 2, 2, };
> +static int jz4740_lcd_8bit_funcs[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
> 0, };
> +static int jz4740_lcd_16bit_funcs[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, };
> +static int jz4740_lcd_18bit_funcs[] = { 0, 0, };
> +static int jz4740_lcd_18bit_tft_funcs[] = { 0, 0, 0, 0, };
> +static int jz4740_nand_cs1_funcs[] = { 0, };
> +static int jz4740_nand_cs2_funcs[] = { 0, };
> +static int jz4740_nand_cs3_funcs[] = { 0, };
> +static int jz4740_nand_cs4_funcs[] = { 0, };
> +static int jz4740_pwm_pwm0_funcs[] = { 0, };
> +static int jz4740_pwm_pwm1_funcs[] = { 0, };
> +static int jz4740_pwm_pwm2_funcs[] = { 0, };
> +static int jz4740_pwm_pwm3_funcs[] = { 0, };
> +static int jz4740_pwm_pwm4_funcs[] = { 0, };
> +static int jz4740_pwm_pwm5_funcs[] = { 0, };
> +static int jz4740_pwm_pwm6_funcs[] = { 0, };
> +static int jz4740_pwm_pwm7_funcs[] = { 0, };
> +
> +#define INGENIC_PIN_GROUP(name, id) \
> + { \
> + name, \
> + id##_pins, \
> + ARRAY_SIZE(id##_pins), \
> + id##_funcs, \
> + }
> +
> +static const struct group_desc jz4740_groups[] = {
> + INGENIC_PIN_GROUP("mmc-1bit", jz4740_mmc_1bit),
> + INGENIC_PIN_GROUP("mmc-4bit", jz4740_mmc_4bit),
> + INGENIC_PIN_GROUP("uart0-data", jz4740_uart0_data),
> + INGENIC_PIN_GROUP("uart0-hwflow", jz4740_uart0_hwflow),
> + INGENIC_PIN_GROUP("uart1-data", jz4740_uart1_data),
> + INGENIC_PIN_GROUP("lcd-8bit", jz4740_lcd_8bit),
> + INGENIC_PIN_GROUP("lcd-16bit", jz4740_lcd_16bit),
> + INGENIC_PIN_GROUP("lcd-18bit", jz4740_lcd_18bit),
> + INGENIC_PIN_GROUP("lcd-18bit-tft", jz4740_lcd_18bit_tft),
> + { "lcd-no-pins", },
> + INGENIC_PIN_GROUP("nand-cs1", jz4740_nand_cs1),
> + INGENIC_PIN_GROUP("nand-cs2", jz4740_nand_cs2),
> + INGENIC_PIN_GROUP("nand-cs3", jz4740_nand_cs3),
> + INGENIC_PIN_GROUP("nand-cs4", jz4740_nand_cs4),
> + INGENIC_PIN_GROUP("pwm0", jz4740_pwm_pwm0),
> + INGENIC_PIN_GROUP("pwm1", jz4740_pwm_pwm1),
> + INGENIC_PIN_GROUP("pwm2", jz4740_pwm_pwm2),
> + INGENIC_PIN_GROUP("pwm3", jz4740_pwm_pwm3),
> + INGENIC_PIN_GROUP("pwm4", jz4740_pwm_pwm4),
> + INGENIC_PIN_GROUP("pwm5", jz4740_pwm_pwm5),
> + INGENIC_PIN_GROUP("pwm6", jz4740_pwm_pwm6),
> + INGENIC_PIN_GROUP("pwm7", jz4740_pwm_pwm7),
> +};
> +
> +static const char *jz4740_mmc_groups[] = { "mmc-1bit", "mmc-4bit", };
> +static const char *jz4740_uart0_groups[] = { "uart0-data",
> "uart0-hwflow", };
> +static const char *jz4740_uart1_groups[] = { "uart1-data", };
> +static const char *jz4740_lcd_groups[] = {
> + "lcd-8bit", "lcd-16bit", "lcd-18bit", "lcd-18bit-tft", "lcd-no-pins",
> +};
> +static const char *jz4740_nand_groups[] = {
> + "nand-cs1", "nand-cs2", "nand-cs3", "nand-cs4",
> +};
> +static const char *jz4740_pwm0_groups[] = { "pwm0", };
> +static const char *jz4740_pwm1_groups[] = { "pwm1", };
> +static const char *jz4740_pwm2_groups[] = { "pwm2", };
> +static const char *jz4740_pwm3_groups[] = { "pwm3", };
> +static const char *jz4740_pwm4_groups[] = { "pwm4", };
> +static const char *jz4740_pwm5_groups[] = { "pwm5", };
> +static const char *jz4740_pwm6_groups[] = { "pwm6", };
> +static const char *jz4740_pwm7_groups[] = { "pwm7", };
> +
> +static const struct function_desc jz4740_functions[] = {
> + { "mmc", jz4740_mmc_groups, ARRAY_SIZE(jz4740_mmc_groups), },
> + { "uart0", jz4740_uart0_groups, ARRAY_SIZE(jz4740_uart0_groups), },
> + { "uart1", jz4740_uart1_groups, ARRAY_SIZE(jz4740_uart1_groups), },
> + { "lcd", jz4740_lcd_groups, ARRAY_SIZE(jz4740_lcd_groups), },
> + { "nand", jz4740_nand_groups, ARRAY_SIZE(jz4740_nand_groups), },
> + { "pwm0", jz4740_pwm0_groups, ARRAY_SIZE(jz4740_pwm0_groups), },
> + { "pwm1", jz4740_pwm1_groups, ARRAY_SIZE(jz4740_pwm1_groups), },
> + { "pwm2", jz4740_pwm2_groups, ARRAY_SIZE(jz4740_pwm2_groups), },
> + { "pwm3", jz4740_pwm3_groups, ARRAY_SIZE(jz4740_pwm3_groups), },
> + { "pwm4", jz4740_pwm4_groups, ARRAY_SIZE(jz4740_pwm4_groups), },
> + { "pwm5", jz4740_pwm5_groups, ARRAY_SIZE(jz4740_pwm5_groups), },
> + { "pwm6", jz4740_pwm6_groups, ARRAY_SIZE(jz4740_pwm6_groups), },
> + { "pwm7", jz4740_pwm7_groups, ARRAY_SIZE(jz4740_pwm7_groups), },
> +};
> +
> +static const struct ingenic_chip_info jz4740_chip_info = {
> + .num_chips = 4,
> + .groups = jz4740_groups,
> + .num_groups = ARRAY_SIZE(jz4740_groups),
> + .functions = jz4740_functions,
> + .num_functions = ARRAY_SIZE(jz4740_functions),
> + .pull_ups = jz4740_pull_ups,
> + .pull_downs = jz4740_pull_downs,
> +};
> +
> +static const u32 jz4770_pull_ups[6] = {
> + 0x3fffffff, 0xfff0030c, 0xffffffff, 0xffff4fff, 0xfffffb7c,
> 0xffa7f00f,
> +};
> +
> +static const u32 jz4770_pull_downs[6] = {
> + 0x00000000, 0x000f0c03, 0x00000000, 0x0000b000, 0x00000483,
> 0x00580ff0,
> +};
> +
> +static int jz4770_uart0_data_pins[] = { 0xa0, 0xa3, };
> +static int jz4770_uart0_hwflow_pins[] = { 0xa1, 0xa2, };
> +static int jz4770_uart1_data_pins[] = { 0x7a, 0x7c, };
> +static int jz4770_uart1_hwflow_pins[] = { 0x7b, 0x7d, };
> +static int jz4770_uart2_data_pins[] = { 0x66, 0x67, };
> +static int jz4770_uart2_hwflow_pins[] = { 0x65, 0x64, };
> +static int jz4770_uart3_data_pins[] = { 0x6c, 0x85, };
> +static int jz4770_uart3_hwflow_pins[] = { 0x88, 0x89, };
> +static int jz4770_uart4_data_pins[] = { 0x54, 0x4a, };
> +static int jz4770_mmc0_8bit_a_pins[] = { 0x04, 0x05, 0x06, 0x07, 0x18,
> };
> +static int jz4770_mmc0_4bit_a_pins[] = { 0x15, 0x16, 0x17, };
> +static int jz4770_mmc0_1bit_a_pins[] = { 0x12, 0x13, 0x14, };
> +static int jz4770_mmc0_4bit_e_pins[] = { 0x95, 0x96, 0x97, };
> +static int jz4770_mmc0_1bit_e_pins[] = { 0x9c, 0x9d, 0x94, };
> +static int jz4770_mmc1_4bit_d_pins[] = { 0x75, 0x76, 0x77, };
> +static int jz4770_mmc1_1bit_d_pins[] = { 0x78, 0x79, 0x74, };
> +static int jz4770_mmc1_4bit_e_pins[] = { 0x95, 0x96, 0x97, };
> +static int jz4770_mmc1_1bit_e_pins[] = { 0x9c, 0x9d, 0x94, };
> +static int jz4770_nemc_data_pins[] = {
> + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
> +};
> +static int jz4770_nemc_cle_ale_pins[] = { 0x20, 0x21, };
> +static int jz4770_nemc_addr_pins[] = { 0x22, 0x23, 0x24, 0x25, };
> +static int jz4770_nemc_rd_we_pins[] = { 0x10, 0x11, };
> +static int jz4770_nemc_frd_fwe_pins[] = { 0x12, 0x13, };
> +static int jz4770_nemc_cs1_pins[] = { 0x15, };
> +static int jz4770_nemc_cs2_pins[] = { 0x16, };
> +static int jz4770_nemc_cs3_pins[] = { 0x17, };
> +static int jz4770_nemc_cs4_pins[] = { 0x18, };
> +static int jz4770_nemc_cs5_pins[] = { 0x19, };
> +static int jz4770_nemc_cs6_pins[] = { 0x1a, };
> +static int jz4770_i2c0_pins[] = { 0x6e, 0x6f, };
> +static int jz4770_i2c1_pins[] = { 0x8e, 0x8f, };
> +static int jz4770_i2c2_pins[] = { 0xb0, 0xb1, };
> +static int jz4770_i2c3_pins[] = { 0x6a, 0x6b, };
> +static int jz4770_i2c4_e_pins[] = { 0x8c, 0x8d, };
> +static int jz4770_i2c4_f_pins[] = { 0xb9, 0xb8, };
> +static int jz4770_cim_pins[] = {
> + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
> 0x31,
> +};
> +static int jz4770_lcd_32bit_pins[] = {
> + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
> + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
> + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
> + 0x58, 0x59, 0x51,
> +};
> +static int jz4770_pwm_pwm0_pins[] = { 0x80, };
> +static int jz4770_pwm_pwm1_pins[] = { 0x81, };
> +static int jz4770_pwm_pwm2_pins[] = { 0x82, };
> +static int jz4770_pwm_pwm3_pins[] = { 0x83, };
> +static int jz4770_pwm_pwm4_pins[] = { 0x84, };
> +static int jz4770_pwm_pwm5_pins[] = { 0x85, };
> +static int jz4770_pwm_pwm6_pins[] = { 0x6a, };
> +static int jz4770_pwm_pwm7_pins[] = { 0x6b, };
> +
> +static int jz4770_uart0_data_funcs[] = { 0, 0, };
> +static int jz4770_uart0_hwflow_funcs[] = { 0, 0, };
> +static int jz4770_uart1_data_funcs[] = { 0, 0, };
> +static int jz4770_uart1_hwflow_funcs[] = { 0, 0, };
> +static int jz4770_uart2_data_funcs[] = { 1, 1, };
> +static int jz4770_uart2_hwflow_funcs[] = { 1, 1, };
> +static int jz4770_uart3_data_funcs[] = { 0, 1, };
> +static int jz4770_uart3_hwflow_funcs[] = { 0, 0, };
> +static int jz4770_uart4_data_funcs[] = { 2, 2, };
> +static int jz4770_mmc0_8bit_a_funcs[] = { 1, 1, 1, 1, 1, };
> +static int jz4770_mmc0_4bit_a_funcs[] = { 1, 1, 1, };
> +static int jz4770_mmc0_1bit_a_funcs[] = { 1, 1, 0, };
> +static int jz4770_mmc0_4bit_e_funcs[] = { 0, 0, 0, };
> +static int jz4770_mmc0_1bit_e_funcs[] = { 0, 0, 0, };
> +static int jz4770_mmc1_4bit_d_funcs[] = { 0, 0, 0, };
> +static int jz4770_mmc1_1bit_d_funcs[] = { 0, 0, 0, };
> +static int jz4770_mmc1_4bit_e_funcs[] = { 1, 1, 1, };
> +static int jz4770_mmc1_1bit_e_funcs[] = { 1, 1, 1, };
> +static int jz4770_nemc_data_funcs[] = { 0, 0, 0, 0, 0, 0, 0, 0, };
> +static int jz4770_nemc_cle_ale_funcs[] = { 0, 0, };
> +static int jz4770_nemc_addr_funcs[] = { 0, 0, 0, 0, };
> +static int jz4770_nemc_rd_we_funcs[] = { 0, 0, };
> +static int jz4770_nemc_frd_fwe_funcs[] = { 0, 0, };
> +static int jz4770_nemc_cs1_funcs[] = { 0, };
> +static int jz4770_nemc_cs2_funcs[] = { 0, };
> +static int jz4770_nemc_cs3_funcs[] = { 0, };
> +static int jz4770_nemc_cs4_funcs[] = { 0, };
> +static int jz4770_nemc_cs5_funcs[] = { 0, };
> +static int jz4770_nemc_cs6_funcs[] = { 0, };
> +static int jz4770_i2c0_funcs[] = { 0, 0, };
> +static int jz4770_i2c1_funcs[] = { 0, 0, };
> +static int jz4770_i2c2_funcs[] = { 2, 2, };
> +static int jz4770_i2c3_funcs[] = { 1, 1, };
> +static int jz4770_i2c4_e_funcs[] = { 1, 1, };
> +static int jz4770_i2c4_f_funcs[] = { 1, 1, };
> +static int jz4770_cim_funcs[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
> };
> +static int jz4770_lcd_32bit_funcs[] = {
> + 0, 0, 0, 0, 0, 0, 0, 0,
> + 0, 0, 0, 0, 0, 0, 0, 0,
> + 0, 0, 0,
> +};
> +static int jz4770_pwm_pwm0_funcs[] = { 0, };
> +static int jz4770_pwm_pwm1_funcs[] = { 0, };
> +static int jz4770_pwm_pwm2_funcs[] = { 0, };
> +static int jz4770_pwm_pwm3_funcs[] = { 0, };
> +static int jz4770_pwm_pwm4_funcs[] = { 0, };
> +static int jz4770_pwm_pwm5_funcs[] = { 0, };
> +static int jz4770_pwm_pwm6_funcs[] = { 0, };
> +static int jz4770_pwm_pwm7_funcs[] = { 0, };
> +
> +static const struct group_desc jz4770_groups[] = {
> + INGENIC_PIN_GROUP("uart0-data", jz4770_uart0_data),
> + INGENIC_PIN_GROUP("uart0-hwflow", jz4770_uart0_hwflow),
> + INGENIC_PIN_GROUP("uart1-data", jz4770_uart1_data),
> + INGENIC_PIN_GROUP("uart1-hwflow", jz4770_uart1_hwflow),
> + INGENIC_PIN_GROUP("uart2-data", jz4770_uart2_data),
> + INGENIC_PIN_GROUP("uart2-hwflow", jz4770_uart2_hwflow),
> + INGENIC_PIN_GROUP("uart3-data", jz4770_uart3_data),
> + INGENIC_PIN_GROUP("uart3-hwflow", jz4770_uart3_hwflow),
> + INGENIC_PIN_GROUP("uart4-data", jz4770_uart4_data),
> + INGENIC_PIN_GROUP("mmc0-8bit-a", jz4770_mmc0_8bit_a),
> + INGENIC_PIN_GROUP("mmc0-4bit-a", jz4770_mmc0_4bit_a),
> + INGENIC_PIN_GROUP("mmc0-1bit-a", jz4770_mmc0_1bit_a),
> + INGENIC_PIN_GROUP("mmc0-4bit-e", jz4770_mmc0_4bit_e),
> + INGENIC_PIN_GROUP("mmc0-1bit-e", jz4770_mmc0_1bit_e),
> + INGENIC_PIN_GROUP("mmc1-4bit-d", jz4770_mmc1_4bit_d),
> + INGENIC_PIN_GROUP("mmc1-1bit-d", jz4770_mmc1_1bit_d),
> + INGENIC_PIN_GROUP("mmc1-4bit-e", jz4770_mmc1_4bit_e),
> + INGENIC_PIN_GROUP("mmc1-1bit-e", jz4770_mmc1_1bit_e),
> + INGENIC_PIN_GROUP("nemc-data", jz4770_nemc_data),
> + INGENIC_PIN_GROUP("nemc-cle-ale", jz4770_nemc_cle_ale),
> + INGENIC_PIN_GROUP("nemc-addr", jz4770_nemc_addr),
> + INGENIC_PIN_GROUP("nemc-rd-we", jz4770_nemc_rd_we),
> + INGENIC_PIN_GROUP("nemc-frd-fwe", jz4770_nemc_frd_fwe),
> + INGENIC_PIN_GROUP("nemc-cs1", jz4770_nemc_cs1),
> + INGENIC_PIN_GROUP("nemc-cs2", jz4770_nemc_cs2),
> + INGENIC_PIN_GROUP("nemc-cs3", jz4770_nemc_cs3),
> + INGENIC_PIN_GROUP("nemc-cs4", jz4770_nemc_cs4),
> + INGENIC_PIN_GROUP("nemc-cs5", jz4770_nemc_cs5),
> + INGENIC_PIN_GROUP("nemc-cs6", jz4770_nemc_cs6),
> + INGENIC_PIN_GROUP("i2c0-data", jz4770_i2c0),
> + INGENIC_PIN_GROUP("i2c1-data", jz4770_i2c1),
> + INGENIC_PIN_GROUP("i2c2-data", jz4770_i2c2),
> + INGENIC_PIN_GROUP("i2c3-data", jz4770_i2c3),
> + INGENIC_PIN_GROUP("i2c4-data-e", jz4770_i2c4_e),
> + INGENIC_PIN_GROUP("i2c4-data-f", jz4770_i2c4_f),
> + INGENIC_PIN_GROUP("cim-data", jz4770_cim),
> + INGENIC_PIN_GROUP("lcd-32bit", jz4770_lcd_32bit),
> + { "lcd-no-pins", },
> + INGENIC_PIN_GROUP("pwm0", jz4770_pwm_pwm0),
> + INGENIC_PIN_GROUP("pwm1", jz4770_pwm_pwm1),
> + INGENIC_PIN_GROUP("pwm2", jz4770_pwm_pwm2),
> + INGENIC_PIN_GROUP("pwm3", jz4770_pwm_pwm3),
> + INGENIC_PIN_GROUP("pwm4", jz4770_pwm_pwm4),
> + INGENIC_PIN_GROUP("pwm5", jz4770_pwm_pwm5),
> + INGENIC_PIN_GROUP("pwm6", jz4770_pwm_pwm6),
> + INGENIC_PIN_GROUP("pwm7", jz4770_pwm_pwm7),
> +};
> +
> +static const char *jz4770_uart0_groups[] = { "uart0-data",
> "uart0-hwflow", };
> +static const char *jz4770_uart1_groups[] = { "uart1-data",
> "uart1-hwflow", };
> +static const char *jz4770_uart2_groups[] = { "uart2-data",
> "uart2-hwflow", };
> +static const char *jz4770_uart3_groups[] = { "uart3-data",
> "uart3-hwflow", };
> +static const char *jz4770_uart4_groups[] = { "uart4-data", };
> +static const char *jz4770_mmc0_groups[] = {
> + "mmc0-8bit-a", "mmc0-4bit-a", "mmc0-1bit-a",
> + "mmc0-1bit-e", "mmc0-4bit-e",
> +};
> +static const char *jz4770_mmc1_groups[] = {
> + "mmc1-1bit-d", "mmc1-4bit-d", "mmc1-1bit-e", "mmc1-4bit-e",
> +};
> +static const char *jz4770_nemc_groups[] = {
> + "nemc-data", "nemc-cle-ale", "nemc-addr", "nemc-rd-we",
> "nemc-frd-fwe",
> +};
> +static const char *jz4770_cs1_groups[] = { "nemc-cs1", };
> +static const char *jz4770_cs6_groups[] = { "nemc-cs6", };
> +static const char *jz4770_i2c0_groups[] = { "i2c0-data", };
> +static const char *jz4770_i2c1_groups[] = { "i2c1-data", };
> +static const char *jz4770_i2c2_groups[] = { "i2c2-data", };
> +static const char *jz4770_i2c3_groups[] = { "i2c3-data", };
> +static const char *jz4770_i2c4_groups[] = { "i2c4-data-e",
> "i2c4-data-f", };
> +static const char *jz4770_cim_groups[] = { "cim-data", };
> +static const char *jz4770_lcd_groups[] = { "lcd-32bit", "lcd-no-pins",
> };
> +static const char *jz4770_pwm0_groups[] = { "pwm0", };
> +static const char *jz4770_pwm1_groups[] = { "pwm1", };
> +static const char *jz4770_pwm2_groups[] = { "pwm2", };
> +static const char *jz4770_pwm3_groups[] = { "pwm3", };
> +static const char *jz4770_pwm4_groups[] = { "pwm4", };
> +static const char *jz4770_pwm5_groups[] = { "pwm5", };
> +static const char *jz4770_pwm6_groups[] = { "pwm6", };
> +static const char *jz4770_pwm7_groups[] = { "pwm7", };
> +
> +static const struct function_desc jz4770_functions[] = {
> + { "uart0", jz4770_uart0_groups, ARRAY_SIZE(jz4770_uart0_groups), },
> + { "uart1", jz4770_uart1_groups, ARRAY_SIZE(jz4770_uart1_groups), },
> + { "uart2", jz4770_uart2_groups, ARRAY_SIZE(jz4770_uart2_groups), },
> + { "uart3", jz4770_uart3_groups, ARRAY_SIZE(jz4770_uart3_groups), },
> + { "uart4", jz4770_uart4_groups, ARRAY_SIZE(jz4770_uart4_groups), },
> + { "mmc0", jz4770_mmc0_groups, ARRAY_SIZE(jz4770_mmc0_groups), },
> + { "mmc1", jz4770_mmc1_groups, ARRAY_SIZE(jz4770_mmc1_groups), },
> + { "nemc", jz4770_nemc_groups, ARRAY_SIZE(jz4770_nemc_groups), },
> + { "nemc-cs1", jz4770_cs1_groups, ARRAY_SIZE(jz4770_cs1_groups), },
> + { "nemc-cs6", jz4770_cs6_groups, ARRAY_SIZE(jz4770_cs6_groups), },
> + { "i2c0", jz4770_i2c0_groups, ARRAY_SIZE(jz4770_i2c0_groups), },
> + { "i2c1", jz4770_i2c1_groups, ARRAY_SIZE(jz4770_i2c1_groups), },
> + { "i2c2", jz4770_i2c2_groups, ARRAY_SIZE(jz4770_i2c2_groups), },
> + { "i2c3", jz4770_i2c3_groups, ARRAY_SIZE(jz4770_i2c3_groups), },
> + { "i2c4", jz4770_i2c4_groups, ARRAY_SIZE(jz4770_i2c4_groups), },
> + { "cim", jz4770_cim_groups, ARRAY_SIZE(jz4770_cim_groups), },
> + { "lcd", jz4770_lcd_groups, ARRAY_SIZE(jz4770_lcd_groups), },
> + { "pwm0", jz4770_pwm0_groups, ARRAY_SIZE(jz4770_pwm0_groups), },
> + { "pwm1", jz4770_pwm1_groups, ARRAY_SIZE(jz4770_pwm1_groups), },
> + { "pwm2", jz4770_pwm2_groups, ARRAY_SIZE(jz4770_pwm2_groups), },
> + { "pwm3", jz4770_pwm3_groups, ARRAY_SIZE(jz4770_pwm3_groups), },
> + { "pwm4", jz4770_pwm4_groups, ARRAY_SIZE(jz4770_pwm4_groups), },
> + { "pwm5", jz4770_pwm5_groups, ARRAY_SIZE(jz4770_pwm5_groups), },
> + { "pwm6", jz4770_pwm6_groups, ARRAY_SIZE(jz4770_pwm6_groups), },
> + { "pwm7", jz4770_pwm7_groups, ARRAY_SIZE(jz4770_pwm7_groups), },
> +};
> +
> +static const struct ingenic_chip_info jz4770_chip_info = {
> + .num_chips = 6,
> + .groups = jz4770_groups,
> + .num_groups = ARRAY_SIZE(jz4770_groups),
> + .functions = jz4770_functions,
> + .num_functions = ARRAY_SIZE(jz4770_functions),
> + .pull_ups = jz4770_pull_ups,
> + .pull_downs = jz4770_pull_downs,
> +};
> +
> +static inline void ingenic_config_pin(struct ingenic_pinctrl *jzpc,
> + unsigned int pin, u8 reg, bool set)
> +{
> + unsigned int idx = pin % PINS_PER_GPIO_CHIP;
> + unsigned int offt = pin / PINS_PER_GPIO_CHIP;
> +
> + regmap_write(jzpc->map, offt * 0x100 +
> + (set ? REG_SET(reg) : REG_CLEAR(reg)), BIT(idx));
> +}
> +
> +static inline bool ingenic_get_pin_config(struct ingenic_pinctrl
> *jzpc,
> + unsigned int pin, u8 reg)
> +{
> + unsigned int idx = pin % PINS_PER_GPIO_CHIP;
> + unsigned int offt = pin / PINS_PER_GPIO_CHIP;
> + unsigned int val;
> +
> + regmap_read(jzpc->map, offt * 0x100 + reg, &val);
> +
> + return val & BIT(idx);
> +}
> +
> +static struct pinctrl_ops ingenic_pctlops = {
> + .get_groups_count = pinctrl_generic_get_group_count,
> + .get_group_name = pinctrl_generic_get_group_name,
> + .get_group_pins = pinctrl_generic_get_group_pins,
> + .dt_node_to_map = pinconf_generic_dt_node_to_map_all,
> + .dt_free_map = pinconf_generic_dt_free_map,
> +};
> +
> +static int ingenic_pinmux_set_pin_fn(struct ingenic_pinctrl *jzpc,
> + int pin, int func)
> +{
> + unsigned int idx = pin % PINS_PER_GPIO_CHIP;
> + unsigned int offt = pin / PINS_PER_GPIO_CHIP;
> +
> + dev_dbg(jzpc->dev, "set pin P%c%u to function %u\n",
> + 'A' + offt, idx, func);
> +
> + if (jzpc->version >= ID_JZ4770) {
> + ingenic_config_pin(jzpc, pin, JZ4770_GPIO_INT, false);
> + ingenic_config_pin(jzpc, pin, JZ4770_GPIO_MSK, false);
> + ingenic_config_pin(jzpc, pin, JZ4770_GPIO_PAT1, func & 0x2);
> + ingenic_config_pin(jzpc, pin, JZ4770_GPIO_PAT0, func & 0x1);
> + } else {
> + ingenic_config_pin(jzpc, pin, JZ4740_GPIO_FUNC, true);
> + ingenic_config_pin(jzpc, pin, JZ4740_GPIO_TRIG, func & 0x2);
> + ingenic_config_pin(jzpc, pin, JZ4740_GPIO_SELECT, func > 0);
> + }
> +
> + return 0;
> +}
> +
> +static int ingenic_pinmux_set_mux(struct pinctrl_dev *pctldev,
> + unsigned int selector, unsigned int group)
> +{
> + struct ingenic_pinctrl *jzpc = pinctrl_dev_get_drvdata(pctldev);
> + struct function_desc *func;
> + struct group_desc *grp;
> + unsigned int i;
> +
> + func = pinmux_generic_get_function(pctldev, selector);
> + if (!func)
> + return -EINVAL;
> +
> + grp = pinctrl_generic_get_group(pctldev, group);
> + if (!grp)
> + return -EINVAL;
> +
> + dev_dbg(pctldev->dev, "enable function %s group %s\n",
> + func->name, grp->name);
> +
> + for (i = 0; i < grp->num_pins; i++) {
> + int *pin_modes = grp->data;
> +
> + ingenic_pinmux_set_pin_fn(jzpc, grp->pins[i], pin_modes[i]);
> + }
> +
> + return 0;
> +}
> +
> +static int ingenic_pinmux_gpio_set_direction(struct pinctrl_dev
> *pctldev,
> + struct pinctrl_gpio_range *range,
> + unsigned int pin, bool input)
> +{
> + struct ingenic_pinctrl *jzpc = pinctrl_dev_get_drvdata(pctldev);
> + unsigned int idx = pin % PINS_PER_GPIO_CHIP;
> + unsigned int offt = pin / PINS_PER_GPIO_CHIP;
> +
> + dev_dbg(pctldev->dev, "set pin P%c%u to %sput\n",
> + 'A' + offt, idx, input ? "in" : "out");
> +
> + if (jzpc->version >= ID_JZ4770) {
> + ingenic_config_pin(jzpc, pin, JZ4770_GPIO_INT, false);
> + ingenic_config_pin(jzpc, pin, JZ4770_GPIO_MSK, true);
> + ingenic_config_pin(jzpc, pin, JZ4770_GPIO_PAT1, input);
> + } else {
> + ingenic_config_pin(jzpc, pin, JZ4740_GPIO_SELECT, false);
> + ingenic_config_pin(jzpc, pin, JZ4740_GPIO_DIR, input);
> + ingenic_config_pin(jzpc, pin, JZ4740_GPIO_FUNC, false);
> + }
> +
> + return 0;
> +}
> +
> +static struct pinmux_ops ingenic_pmxops = {
> + .get_functions_count = pinmux_generic_get_function_count,
> + .get_function_name = pinmux_generic_get_function_name,
> + .get_function_groups = pinmux_generic_get_function_groups,
> + .set_mux = ingenic_pinmux_set_mux,
> + .gpio_set_direction = ingenic_pinmux_gpio_set_direction,
> +};
> +
> +static int ingenic_pinconf_get(struct pinctrl_dev *pctldev,
> + unsigned int pin, unsigned long *config)
> +{
> + struct ingenic_pinctrl *jzpc = pinctrl_dev_get_drvdata(pctldev);
> + enum pin_config_param param = pinconf_to_config_param(*config);
> + unsigned int idx = pin % PINS_PER_GPIO_CHIP;
> + unsigned int offt = pin / PINS_PER_GPIO_CHIP;
> + bool pull;
> +
> + if (jzpc->version >= ID_JZ4770)
> + pull = !ingenic_get_pin_config(jzpc, pin, JZ4770_GPIO_PEN);
> + else
> + pull = !ingenic_get_pin_config(jzpc, pin, JZ4740_GPIO_PULL_DIS);
> +
> + switch (param) {
> + case PIN_CONFIG_BIAS_DISABLE:
> + if (pull)
> + return -EINVAL;
> + break;
> +
> + case PIN_CONFIG_BIAS_PULL_UP:
> + if (!pull || !(jzpc->info->pull_ups[offt] & BIT(idx)))
> + return -EINVAL;
> + break;
> +
> + case PIN_CONFIG_BIAS_PULL_DOWN:
> + if (!pull || !(jzpc->info->pull_downs[offt] & BIT(idx)))
> + return -EINVAL;
> + break;
> +
> + default:
> + return -ENOTSUPP;
> + }
> +
> + *config = pinconf_to_config_packed(param, 1);
> + return 0;
> +}
> +
> +static void ingenic_set_bias(struct ingenic_pinctrl *jzpc,
> + unsigned int pin, bool enabled)
> +{
> + if (jzpc->version >= ID_JZ4770)
> + ingenic_config_pin(jzpc, pin, JZ4770_GPIO_PEN, !enabled);
> + else
> + ingenic_config_pin(jzpc, pin, JZ4740_GPIO_PULL_DIS, !enabled);
> +}
> +
> +static int ingenic_pinconf_set(struct pinctrl_dev *pctldev, unsigned
> int pin,
> + unsigned long *configs, unsigned int num_configs)
> +{
> + struct ingenic_pinctrl *jzpc = pinctrl_dev_get_drvdata(pctldev);
> + unsigned int idx = pin % PINS_PER_GPIO_CHIP;
> + unsigned int offt = pin / PINS_PER_GPIO_CHIP;
> + unsigned int cfg;
> +
> + for (cfg = 0; cfg < num_configs; cfg++) {
> + switch (pinconf_to_config_param(configs[cfg])) {
> + case PIN_CONFIG_BIAS_DISABLE:
> + case PIN_CONFIG_BIAS_PULL_UP:
> + case PIN_CONFIG_BIAS_PULL_DOWN:
> + continue;
> + default:
> + return -ENOTSUPP;
> + }
> + }
> +
> + for (cfg = 0; cfg < num_configs; cfg++) {
> + switch (pinconf_to_config_param(configs[cfg])) {
> + case PIN_CONFIG_BIAS_DISABLE:
> + dev_dbg(jzpc->dev, "disable pull-over for pin P%c%u\n",
> + 'A' + offt, idx);
> + ingenic_set_bias(jzpc, pin, false);
> + break;
> +
> + case PIN_CONFIG_BIAS_PULL_UP:
> + if (!(jzpc->info->pull_ups[offt] & BIT(idx)))
> + return -EINVAL;
> + dev_dbg(jzpc->dev, "set pull-up for pin P%c%u\n",
> + 'A' + offt, idx);
> + ingenic_set_bias(jzpc, pin, true);
> + break;
> +
> + case PIN_CONFIG_BIAS_PULL_DOWN:
> + if (!(jzpc->info->pull_downs[offt] & BIT(idx)))
> + return -EINVAL;
> + dev_dbg(jzpc->dev, "set pull-down for pin P%c%u\n",
> + 'A' + offt, idx);
> + ingenic_set_bias(jzpc, pin, true);
> + break;
> +
> + default:
> + unreachable();
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int ingenic_pinconf_group_get(struct pinctrl_dev *pctldev,
> + unsigned int group, unsigned long *config)
> +{
> + const unsigned int *pins;
> + unsigned int i, npins, old = 0;
> + int ret;
> +
> + ret = pinctrl_generic_get_group_pins(pctldev, group, &pins, &npins);
> + if (ret)
> + return ret;
> +
> + for (i = 0; i < npins; i++) {
> + if (ingenic_pinconf_get(pctldev, pins[i], config))
> + return -ENOTSUPP;
> +
> + /* configs do not match between two pins */
> + if (i && (old != *config))
> + return -ENOTSUPP;
> +
> + old = *config;
> + }
> +
> + return 0;
> +}
> +
> +static int ingenic_pinconf_group_set(struct pinctrl_dev *pctldev,
> + unsigned int group, unsigned long *configs,
> + unsigned int num_configs)
> +{
> + const unsigned int *pins;
> + unsigned int i, npins;
> + int ret;
> +
> + ret = pinctrl_generic_get_group_pins(pctldev, group, &pins, &npins);
> + if (ret)
> + return ret;
> +
> + for (i = 0; i < npins; i++) {
> + ret = ingenic_pinconf_set(pctldev,
> + pins[i], configs, num_configs);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static struct pinconf_ops ingenic_confops = {
> + .is_generic = true,
> + .pin_config_get = ingenic_pinconf_get,
> + .pin_config_set = ingenic_pinconf_set,
> + .pin_config_group_get = ingenic_pinconf_group_get,
> + .pin_config_group_set = ingenic_pinconf_group_set,
> +};
> +
> +static const struct regmap_config ingenic_pinctrl_regmap_config = {
> + .reg_bits = 32,
> + .val_bits = 32,
> + .reg_stride = 4,
> +};
> +
> +static const struct of_device_id ingenic_pinctrl_of_match[] = {
> + { .compatible = "ingenic,jz4740-pinctrl", .data = (void *) ID_JZ4740
> },
> + { .compatible = "ingenic,jz4770-pinctrl", .data = (void *) ID_JZ4770
> },
> + { .compatible = "ingenic,jz4780-pinctrl", .data = (void *) ID_JZ4780
> },
> + {},
> +};
> +
> +int ingenic_pinctrl_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct ingenic_pinctrl *jzpc;
> + struct pinctrl_desc *pctl_desc;
> + void __iomem *base;
> + const struct platform_device_id *id = platform_get_device_id(pdev);
> + const struct of_device_id *of_id = of_match_device(
> + ingenic_pinctrl_of_match, dev);
> + const struct ingenic_chip_info *chip_info;
> + unsigned int i;
> + int err;
> +
> + jzpc = devm_kzalloc(dev, sizeof(*jzpc), GFP_KERNEL);
> + if (!jzpc)
> + return -ENOMEM;
> +
> + base = devm_ioremap_resource(dev,
> + platform_get_resource(pdev, IORESOURCE_MEM, 0));
> + if (IS_ERR(base)) {
> + dev_err(dev, "Failed to ioremap registers\n");
> + return PTR_ERR(base);
> + }
> +
> + jzpc->map = devm_regmap_init_mmio(dev, base,
> + &ingenic_pinctrl_regmap_config);
> + if (IS_ERR(jzpc->map)) {
> + dev_err(dev, "Failed to create regmap\n");
> + return PTR_ERR(jzpc->map);
> + }
> +
> + jzpc->dev = dev;
> +
> + if (of_id)
> + jzpc->version = (enum jz_version)of_id->data;
> + else
> + jzpc->version = (enum jz_version)id->driver_data;
> +
> + if (jzpc->version >= ID_JZ4770)
> + chip_info = &jz4770_chip_info;
> + else
> + chip_info = &jz4740_chip_info;
> + jzpc->info = chip_info;
> +
> + pctl_desc = devm_kzalloc(&pdev->dev, sizeof(*pctl_desc), GFP_KERNEL);
> + if (!pctl_desc)
> + return -ENOMEM;
> +
> + /* fill in pinctrl_desc structure */
> + pctl_desc->name = dev_name(dev);
> + pctl_desc->owner = THIS_MODULE;
> + pctl_desc->pctlops = &ingenic_pctlops;
> + pctl_desc->pmxops = &ingenic_pmxops;
> + pctl_desc->confops = &ingenic_confops;
> + pctl_desc->npins = chip_info->num_chips * PINS_PER_GPIO_CHIP;
> + pctl_desc->pins = jzpc->pdesc = devm_kzalloc(&pdev->dev,
> + sizeof(*jzpc->pdesc) * pctl_desc->npins, GFP_KERNEL);
> + if (!jzpc->pdesc)
> + return -ENOMEM;
> +
> + for (i = 0; i < pctl_desc->npins; i++) {
> + jzpc->pdesc[i].number = i;
> + jzpc->pdesc[i].name = kasprintf(GFP_KERNEL, "P%c%d",
> + 'A' + (i / PINS_PER_GPIO_CHIP),
> + i % PINS_PER_GPIO_CHIP);
> + }
> +
> + jzpc->pctl = devm_pinctrl_register(dev, pctl_desc, jzpc);
> + if (!jzpc->pctl) {
> + dev_err(dev, "Failed to register pinctrl\n");
> + return -EINVAL;
> + }
> +
> + for (i = 0; i < chip_info->num_groups; i++) {
> + const struct group_desc *group = &chip_info->groups[i];
> +
> + err = pinctrl_generic_add_group(jzpc->pctl, group->name,
> + group->pins, group->num_pins, group->data);
> + if (err) {
> + dev_err(dev, "Failed to register group %s\n",
> + group->name);
> + return err;
> + }
> + }
> +
> + for (i = 0; i < chip_info->num_functions; i++) {
> + const struct function_desc *func = &chip_info->functions[i];
> +
> + err = pinmux_generic_add_function(jzpc->pctl, func->name,
> + func->group_names, func->num_group_names,
> + func->data);
> + if (err) {
> + dev_err(dev, "Failed to register function %s\n",
> + func->name);
> + return err;
> + }
> + }
> +
> + dev_set_drvdata(dev, jzpc->map);
> +
> + if (dev->of_node) {
> + err = of_platform_populate(dev->of_node, NULL, NULL, dev);
> + if (err) {
> + dev_err(dev, "Failed to probe GPIO devices\n");
> + return err;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static const struct platform_device_id ingenic_pinctrl_ids[] = {
> + { "jz4740-pinctrl", ID_JZ4740 },
> + { "jz4770-pinctrl", ID_JZ4770 },
> + { "jz4780-pinctrl", ID_JZ4780 },
> + {},
> +};
> +
> +static struct platform_driver ingenic_pinctrl_driver = {
> + .driver = {
> + .name = "pinctrl-ingenic",
> + .of_match_table = of_match_ptr(ingenic_pinctrl_of_match),
> + .suppress_bind_attrs = true,
> + },
> + .probe = ingenic_pinctrl_probe,
> + .id_table = ingenic_pinctrl_ids,
> +};
> +
> +static int __init ingenic_pinctrl_drv_register(void)
> +{
> + return platform_driver_register(&ingenic_pinctrl_driver);
> +}
> +postcore_initcall(ingenic_pinctrl_drv_register);
^ permalink raw reply
* Re: [PATCH v5] media: platform: Renesas IMR driver
From: Laurent Pinchart @ 2017-05-03 9:04 UTC (permalink / raw)
To: Geert Uytterhoeven
Cc: Sergei Shtylyov, Rob Herring, Mark Rutland, Mauro Carvalho Chehab,
devicetree@vger.kernel.org, Linux Media Mailing List,
Linux-Renesas, Konstantin Kozhevnikov, Kuninori Morimoto
In-Reply-To: <CAMuHMdWE+o3gsFnxqBcvrD=PfHHb0i9uK3tsfWaNxfuhK3SNKg@mail.gmail.com>
Hi Geert,
On Wednesday 03 May 2017 09:22:00 Geert Uytterhoeven wrote:
> On Tue, May 2, 2017 at 11:17 PM, Laurent Pinchart wrote:
> > On Wednesday 22 Mar 2017 10:34:16 Geert Uytterhoeven wrote:
> >> On Thu, Mar 9, 2017 at 9:08 PM, Sergei Shtylyov wrote:
> >>> --- /dev/null
> >>> +++ media_tree/Documentation/devicetree/bindings/media/rcar_imr.txt
> >>> @@ -0,0 +1,27 @@
> >>> +Renesas R-Car Image Renderer (Distortion Correction Engine)
> >>> +-----------------------------------------------------------
> >>> +
> >>> +The image renderer, or the distortion correction engine, is a drawing
> >>> processor
> >>> +with a simple instruction system capable of referencing video capture
> >>> data or
> >>> +data in an external memory as 2D texture data and performing texture
> >>> mapping
> >>> +and drawing with respect to any shape that is split into triangular
> >>> objects.
> >>> +
> >>> +Required properties:
> >>> +
> >>> +- compatible: "renesas,<soctype>-imr-lx4", "renesas,imr-lx4" as a
> >>> fallback for
> >>> + the image renderer light extended 4 (IMR-LX4) found in the R-Car
> >>> gen3 SoCs,
> >>> + where the examples with <soctype> are:
> >>> + - "renesas,r8a7795-imr-lx4" for R-Car H3,
> >>> + - "renesas,r8a7796-imr-lx4" for R-Car M3-W.
> >>
> >> Laurent: what do you think about the need for SoC-specific compatible
> >> values for the various IM* blocks?
> >
> > There's no documented IP core version register, but when dumping all
> > configuration registers on H3 and M3-W I noticed that register 0x002c, not
> > documented in the datasheet, reads 0x14060514 on all four IMR instances in
> > H3, and 0x20150505 on both instances in M3-W.
> >
> > This looks like a version register to me. If my assumption is correct, we
> > could do without any SoC-specific compatible string.
>
> I read this assumed version registers on all R-Car SoCs, after writing
> zero to 0xe6150990 (SMSTPCR8).
>
> IMR-X2 on R-Car H2: 0x12072009
> IMR-LSX2 on R-Car H2: 0x12072009
> IMR-LSX3 on R-Car V2H: 0x13052617
> IMR-LX2 on R-Car M2-W: 0x12072009
> IMR-LX2 on R-Car M2-N: 0x12072009
> IMR-LX2 on R-Car E2: 0x13091909
> IMR-LX3 on R-Car V2H: 0x13052617
>
> Note that several IDs are the same, but you know the type from the
> compatible value.
>
> It would be good to get confirmation from the hardware team that this is
> indeed a version register.
Thank you for checking.
Morimoto-san, do you think there are still people alive in the Gen2 hardware
team who could provide the information ? :-) If not, information restricted to
Gen3 would still be useful.
--
Regards,
Laurent Pinchart
^ permalink raw reply
* Re: [PATCH 13/15] drm/sun4i: Add HDMI support
From: Maxime Ripard @ 2017-05-03 8:41 UTC (permalink / raw)
To: Chen-Yu Tsai
Cc: Mike Turquette, Stephen Boyd, dri-devel, Daniel Vetter,
David Airlie, Mark Rutland, Rob Herring, devicetree, linux-clk,
linux-arm-kernel, linux-kernel, linux-sunxi
In-Reply-To: <CAGb2v67Szt9Xj+hwJD6i9X0XgXOtZBS1Jy4wWjbfSGJSDFM1NQ-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
[-- Attachment #1: Type: text/plain, Size: 3950 bytes --]
On Wed, Apr 26, 2017 at 03:59:28PM +0800, Chen-Yu Tsai wrote:
> >> > + writel(SUN4I_HDMI_VID_TIMING_X(mode->hdisplay) |
> >> > + SUN4I_HDMI_VID_TIMING_Y(mode->vdisplay),
> >> > + hdmi->base + SUN4I_HDMI_VID_TIMING_ACT_REG);
> >> > +
> >> > + x = mode->htotal - mode->hsync_start;
> >> > + y = mode->vtotal - mode->vsync_start;
> >>
> >> I'm a bit skeptical about this one. All the other parameters are not
> >> inclusive of other, why would this one be different? Shouldn't it
> >> be "Xtotal - Xsync_end" instead?
> >
> > By the usual meaning of backporch, you're right. However, Allwinner's
> > seems to have it's own, which is actually the backporch + sync length.
> >
> > We also have that on all the other connectors (and TCON), and this was
> > confirmed at the time using a scope on an RGB signal.
>
> Yes. On the later SoCs such as the A31, the user manual actually has
> timing diagrams showing this.
>
> Unlike the TCON, the HDMI controller's timings lists the front porch
> separately, instead of an all inclusive Xtotal. This is what made me
> look twice. This should be easy to confirm though. Since the HDMI modes
> are well known and can be exactly reproduced on our hardware, we can
> just check for any distortions or refresh rate errors.
This isn't as trivial as that. Screens usually have some tolerancies
on the timings, which will probably make it go unnoticed, even though
they are wrong.
> >>
> >> > + writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
> >> > + hdmi->base + SUN4I_HDMI_VID_TIMING_BP_REG);
> >> > +
> >> > + x = mode->hsync_start - mode->hdisplay;
> >> > + y = mode->vsync_start - mode->vdisplay;
> >> > + writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
> >> > + hdmi->base + SUN4I_HDMI_VID_TIMING_FP_REG);
> >> > +
> >> > + x = mode->hsync_end - mode->hsync_start;
> >> > + y = mode->vsync_end - mode->vsync_start;
> >> > + writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
> >> > + hdmi->base + SUN4I_HDMI_VID_TIMING_SPW_REG);
> >> > +
> >> > + val = SUN4I_HDMI_VID_TIMING_POL_TX_CLK;
> >> > + if (mode->flags & DRM_MODE_FLAG_PHSYNC)
> >> > + val |= SUN4I_HDMI_VID_TIMING_POL_HSYNC;
> >> > +
> >> > + if (mode->flags & DRM_MODE_FLAG_PVSYNC)
> >> > + val |= SUN4I_HDMI_VID_TIMING_POL_VSYNC;
> >> > +
> >> > + writel(val, hdmi->base + SUN4I_HDMI_VID_TIMING_POL_REG);
> >>
> >> You don't handle the interlaced video here, even though you set
> >>
> >> hdmi->connector.interlace_allowed = true
> >>
> >> later.
> >
> > I'll fix that.
> >
> >> The double clock and double scan flags aren't handled either, though
> >> I don't understand which one is supposed to represent the need for the
> >> HDMI pixel repeater. AFAIK this is required for resolutions with pixel
> >> clocks lower than 25 MHz, the lower limit of HDMI's TMDS link.
> >
> > I'm not sure about this one though. I'd like to keep things quite
> > simple for now and build up on that once the basis is working. Is it
> > common in the wild?
>
> If you drive the display at SDTV resolutions, then yes. Mode lines from
> my HDMI monitor:
>
> 720x576i 50 720 732 795 864 576 580 586 625 flags: nhsync, nvsync,
> interlace, dblclk; type: driver
> 720x480i 60 720 739 801 858 480 488 494 525 flags: nhsync, nvsync,
> interlace, dblclk; type: driver
> 720x480i 60 720 739 801 858 480 488 494 525 flags: nhsync, nvsync,
> interlace, dblclk; type: driver
>
> AFAIK these are standard modes that all devices should support. Whether
> they are used daily is another thing. Maybe block modes with dblclk
> in .mode_fixup for now?
That would rather be atomic_check and / or mode_valid, but yeah, I can
do that.
Thanks!
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply
* Re: [PATCH v6 5/8] i2c: mux: pca954x: Call request irq after adding mux segments
From: Phil Reid @ 2017-05-03 8:36 UTC (permalink / raw)
To: Peter Rosin, wsa, robh+dt, mark.rutland, sre, linux-i2c,
devicetree, linux-pm, benjamin.tissoires
In-Reply-To: <28f3ce1d-ad4b-3082-7499-b30245a256c1@axentia.se>
On 2/05/2017 17:57, Peter Rosin wrote:
> On 2017-05-02 11:12, Phil Reid wrote:
>> The pca954x device do not have the ability to mask interrupts. For
>> i2c slave devices that also don't have masking ability (eg ltc1760
>> smbalert output) delay registering the irq until after the mux
>> segments have been configured. During the mux add_adaptor call the
>> core i2c system can register an smbalert handler which would then
>> be called immediately when the irq is registered. This smbalert
>> handler will then clear the pending irq.
>>
>> Signed-off-by: Phil Reid <preid@electromag.com.au>
>> ---
>> drivers/i2c/muxes/i2c-mux-pca954x.c | 52 +++++++++++++++++--------------------
>> 1 file changed, 24 insertions(+), 28 deletions(-)
>>
>> diff --git a/drivers/i2c/muxes/i2c-mux-pca954x.c b/drivers/i2c/muxes/i2c-mux-pca954x.c
>> index ad31d21..4299738 100644
>> --- a/drivers/i2c/muxes/i2c-mux-pca954x.c
>> +++ b/drivers/i2c/muxes/i2c-mux-pca954x.c
>> @@ -294,7 +294,7 @@ static int pca954x_irq_setup(struct i2c_mux_core *muxc)
>> {
>> struct pca954x *data = i2c_mux_priv(muxc);
>> struct i2c_client *client = data->client;
>> - int c, err, irq;
>> + int c, irq;
>>
>> if (!data->chip->has_irq || client->irq <= 0)
>> return 0;
>> @@ -314,24 +314,22 @@ static int pca954x_irq_setup(struct i2c_mux_core *muxc)
>> handle_simple_irq);
>> }
>>
>> - err = devm_request_threaded_irq(&client->dev, data->client->irq, NULL,
>> - pca954x_irq_handler,
>> - IRQF_ONESHOT | IRQF_SHARED,
>> - "pca954x", data);
>> - if (err)
>> - goto err_req_irq;
>> + return 0;
>> +}
>>
>> - disable_irq(data->client->irq);
>> +static void pca954x_cleanup(struct i2c_mux_core *muxc)
>> +{
>> + struct pca954x *data = i2c_mux_priv(muxc);
>> + int c, irq;
>>
>> - return 0;
>> -err_req_irq:
>> - for (c = 0; c < data->chip->nchans; c++) {
>> - irq = irq_find_mapping(data->irq, c);
>> - irq_dispose_mapping(irq);
>> + if (data->irq) {
>> + for (c = 0; c < data->chip->nchans; c++) {
>> + irq = irq_find_mapping(data->irq, c);
>> + irq_dispose_mapping(irq);
>> + }
>> + irq_domain_remove(data->irq);
>> }
>> - irq_domain_remove(data->irq);
>> -
>> - return err;
>> + i2c_mux_del_adapters(muxc);
>> }
>>
>> /*
>> @@ -422,6 +420,14 @@ static int pca954x_probe(struct i2c_client *client,
>> }
>> }
>>
>> + if (data->chip->has_irq || client->irq > 0) {
>
> This should be && instead of ||. However, I think it's better to not
> try to replicate the inverse of the test in pca954x_irq_setup and
> instead just check if the irq domain is there with "if (data->irq)".
> Assuming that is the intent...
Yeah, that's better.
>
>> + ret = devm_request_threaded_irq(&client->dev, data->client->irq,
>> + NULL, pca954x_irq_handler, IRQF_ONESHOT | IRQF_SHARED,
>> + "pca954x", data);
>
> The indentation is horrific. Please fix.
oops
>
> Acked with those two fixed.
>
> I also noticed that there is no check for failure of the call to
> irq_create_mapping in pca954x_irq_setup. For bonus points, can you
> fix that error path too, please? Or should failure to create those
> irq mappings not be fatal for some reason?
Seems reasonable to fail. I'll fix that as well.
>
> Cheers,
> peda
>
>> + if (ret)
>> + goto fail_del_adapters;
>> + }
>> +
>> dev_info(&client->dev,
>> "registered %d multiplexed busses for I2C %s %s\n",
>> num, data->chip->muxtype == pca954x_ismux
>> @@ -430,25 +436,15 @@ static int pca954x_probe(struct i2c_client *client,
>> return 0;
>>
>> fail_del_adapters:
>> - i2c_mux_del_adapters(muxc);
>> + pca954x_cleanup(muxc);
>> return ret;
>> }
>>
>> static int pca954x_remove(struct i2c_client *client)
>> {
>> struct i2c_mux_core *muxc = i2c_get_clientdata(client);
>> - struct pca954x *data = i2c_mux_priv(muxc);
>> - int c, irq;
>>
>> - if (data->irq) {
>> - for (c = 0; c < data->chip->nchans; c++) {
>> - irq = irq_find_mapping(data->irq, c);
>> - irq_dispose_mapping(irq);
>> - }
>> - irq_domain_remove(data->irq);
>> - }
>> -
>> - i2c_mux_del_adapters(muxc);
>> + pca954x_cleanup(muxc);
>> return 0;
>> }
>>
>>
>
>
>
--
Regards
Phil Reid
ElectroMagnetic Imaging Technology Pty Ltd
Development of Geophysical Instrumentation & Software
www.electromag.com.au
3 The Avenue, Midland WA 6056, AUSTRALIA
Ph: +61 8 9250 8100
Fax: +61 8 9250 7100
Email: preid@electromag.com.au
^ permalink raw reply
* Re: [PATCH 2/2] [media] platform: add video-multiplexer subdevice driver
From: Philipp Zabel @ 2017-05-03 8:35 UTC (permalink / raw)
To: Peter Rosin
Cc: linux-media-u79uwXL29TY76Z2rM5mHXA, Mark Rutland,
devicetree-u79uwXL29TY76Z2rM5mHXA, Steve Longerbeam,
kernel-bIcnvbaLZ9MEGnE8C9+IrQ, Sascha Hauer, Rob Herring,
Sakari Ailus, Pavel Machek, Steve Longerbeam, Vladimir Zapolskiy
In-Reply-To: <74bfa70b-3407-9484-9717-bb2d07356f70-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
On Tue, 2017-05-02 at 19:42 +0200, Peter Rosin wrote:
> On 2017-05-02 17:21, Philipp Zabel wrote:
> > On Sat, 2017-04-29 at 23:42 +0200, Peter Rosin wrote:
> >> On 2017-04-29 23:29, Peter Rosin wrote:
> >>> On 2017-04-28 16:13, Philipp Zabel wrote:
> >>>> This driver can handle SoC internal and external video bus multiplexers,
> >>>> controlled by mux controllers provided by the mux controller framework,
> >>>> such as MMIO register bitfields or GPIOs. The subdevice passes through
> >>>> the mbus configuration of the active input to the output side.
> >>>>
> >>>> Signed-off-by: Sascha Hauer <s.hauer-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> >>>> Signed-off-by: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> >>>> Signed-off-by: Steve Longerbeam <steve_longerbeam-nmGgyN9QBj3QT0dZR+AlfA@public.gmane.org>
> >>>> ---
> >>>> This has been last sent as part of the i.MX media series.
> >>>>
> >>>> Changes since https://patchwork.kernel.org/patch/9647869/:
> >>>> - Split out the actual mux operation to be provided by the mux controller
> >>>> framework [1]. GPIO and MMIO control can be provided by individual mux
> >>>> controller drivers [2][3].
> >>>> [1] https://patchwork.kernel.org/patch/9695837/
> >>>> [2] https://patchwork.kernel.org/patch/9695839/
> >>>> [3] https://patchwork.kernel.org/patch/9704509/
> >>>> - Shortened 'video-multiplexer' to 'video-mux', replaced all instances of
> >>>> vidsw with video_mux.
> >>>> - Made the mux inactive by default, only activated by user interaction.
> >>>> - Added CONFIG_OF and CONFIG_MULTIPLEXER dependencies.
> >>>> - Reuse subdev.entity.num_pads instead of keeping our own count.
> >>>> - Removed implicit link disabling. Instead, trying to enable a second
> >>>> sink pad link yields -EBUSY.
> >>>> - Merged _async_init into _probe.
> >>>> - Removed superfluous pad index check from _set_format.
> >>>> - Added is_source_pad helper to tell source and sink pads apart.
> >>>> - Removed test for status property in endpoint nodes. Disable the remote
> >>>> device or sever the endpoint link to disable a sink pad.
> >>>> ---
> >>>> drivers/media/platform/Kconfig | 6 +
> >>>> drivers/media/platform/Makefile | 2 +
> >>>> drivers/media/platform/video-mux.c | 341 +++++++++++++++++++++++++++++++++++++
> >>>> 3 files changed, 349 insertions(+)
> >>>> create mode 100644 drivers/media/platform/video-mux.c
> >>>>
> >>>> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> >>>> index c9106e105baba..b046a6d39fee5 100644
> >>>> --- a/drivers/media/platform/Kconfig
> >>>> +++ b/drivers/media/platform/Kconfig
> >>>> @@ -74,6 +74,12 @@ config VIDEO_M32R_AR_M64278
> >>>> To compile this driver as a module, choose M here: the
> >>>> module will be called arv.
> >>>>
> >>>> +config VIDEO_MUX
> >>>> + tristate "Video Multiplexer"
> >>>> + depends on OF && VIDEO_V4L2_SUBDEV_API && MEDIA_CONTROLLER && MULTIPLEXER
> >>>> + help
> >>>> + This driver provides support for N:1 video bus multiplexers.
> >>>> +
> >>>> config VIDEO_OMAP3
> >>>> tristate "OMAP 3 Camera support"
> >>>> depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API && ARCH_OMAP3
> >>>> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> >>>> index 349ddf6a69da2..fd2735ca3ff75 100644
> >>>> --- a/drivers/media/platform/Makefile
> >>>> +++ b/drivers/media/platform/Makefile
> >>>> @@ -27,6 +27,8 @@ obj-$(CONFIG_VIDEO_SH_VEU) += sh_veu.o
> >>>>
> >>>> obj-$(CONFIG_VIDEO_MEM2MEM_DEINTERLACE) += m2m-deinterlace.o
> >>>>
> >>>> +obj-$(CONFIG_VIDEO_MUX) += video-mux.o
> >>>> +
> >>>> obj-$(CONFIG_VIDEO_S3C_CAMIF) += s3c-camif/
> >>>> obj-$(CONFIG_VIDEO_SAMSUNG_EXYNOS4_IS) += exynos4-is/
> >>>> obj-$(CONFIG_VIDEO_SAMSUNG_S5P_JPEG) += s5p-jpeg/
> >>>> diff --git a/drivers/media/platform/video-mux.c b/drivers/media/platform/video-mux.c
> >>>> new file mode 100644
> >>>> index 0000000000000..419541729f67e
> >>>> --- /dev/null
> >>>> +++ b/drivers/media/platform/video-mux.c
> >>>> @@ -0,0 +1,341 @@
> >>>> +/*
> >>>> + * video stream multiplexer controlled via mux control
> >>>> + *
> >>>> + * Copyright (C) 2013 Pengutronix, Sascha Hauer <kernel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> >>>> + * Copyright (C) 2016 Pengutronix, Philipp Zabel <kernel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> >>>
> >>> 2017?
> >>>
> >>>> + *
> >>>> + * This program is free software; you can redistribute it and/or
> >>>> + * modify it under the terms of the GNU General Public License
> >>>> + * as published by the Free Software Foundation; either version 2
> >>>> + * of the License, or (at your option) any later version.
> >>>> + * This program is distributed in the hope that it will be useful,
> >>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> >>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> >>>> + * GNU General Public License for more details.
> >>>> + */
> >>>> +
> >>>> +#include <linux/err.h>
> >>>> +#include <linux/module.h>
> >>>> +#include <linux/mux/consumer.h>
> >>>> +#include <linux/of.h>
> >>>> +#include <linux/of_graph.h>
> >>>> +#include <linux/platform_device.h>
> >>>> +#include <media/v4l2-async.h>
> >>>> +#include <media/v4l2-device.h>
> >>>> +#include <media/v4l2-subdev.h>
> >>>> +#include <media/v4l2-of.h>
> >>>> +
> >>>> +struct video_mux {
> >>>> + struct v4l2_subdev subdev;
> >>>> + struct media_pad *pads;
> >>>> + struct v4l2_mbus_framefmt *format_mbus;
> >>>> + struct v4l2_of_endpoint *endpoint;
> >>>> + struct mux_control *mux;
> >>>> + int active;
> >>>> +};
> >>>> +
> >>>> +static inline struct video_mux *v4l2_subdev_to_video_mux(struct v4l2_subdev *sd)
> >>>> +{
> >>>> + return container_of(sd, struct video_mux, subdev);
> >>>> +}
> >>>> +
> >>>> +static inline bool is_source_pad(struct video_mux *vmux, unsigned int pad)
> >>>> +{
> >>>> + return pad == vmux->subdev.entity.num_pads - 1;
> >>>> +}
> >>>> +
> >>>> +static int video_mux_link_setup(struct media_entity *entity,
> >>>> + const struct media_pad *local,
> >>>> + const struct media_pad *remote, u32 flags)
> >>>> +{
> >>>> + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
> >>>> + struct video_mux *vmux = v4l2_subdev_to_video_mux(sd);
> >>>> + int ret;
> >>>> +
> >>>> + /*
> >>>> + * The mux state is determined by the enabled sink pad link.
> >>>> + * Enabling or disabling the source pad link has no effect.
> >>>> + */
> >>>> + if (is_source_pad(vmux, local->index))
> >>>> + return 0;
> >>>> +
> >>>> + dev_dbg(sd->dev, "link setup '%s':%d->'%s':%d[%d]",
> >>>> + remote->entity->name, remote->index, local->entity->name,
> >>>> + local->index, flags & MEDIA_LNK_FL_ENABLED);
> >>>> +
> >>>> + if (flags & MEDIA_LNK_FL_ENABLED) {
> >>>> + if (vmux->active == local->index)
> >>>
> >>> Here, you shortcut the mux_control_select_trylock test and return "OK"
> >>> based on a driver-local variable that is intended to keep track of mux
> >>> ownership.
> >>>
> >>>> + return 0;
> >>>> +
> >>>> + if (vmux->active >= 0)
> >>>
> >>> Here too (and this check is not needed, the situation will be covered by
> >>> the mux_control_try_select call).
> >>>
> >>>> + return -EBUSY;
> >>>> +
> >>>> + dev_dbg(sd->dev, "setting %d active\n", local->index);
> >>>> + ret = mux_control_try_select(vmux->mux, local->index);
> >>>> + if (ret < 0)
> >>>> + return ret;
> >>>> + vmux->active = local->index;
> >>>> + } else {
> >>>> + if (vmux->active != local->index)
> >>>> + return 0;
> >>>> +
> >>>> + dev_dbg(sd->dev, "going inactive\n");
> >>>> + mux_control_deselect(vmux->mux);
> >>>
> >>> But here you let go of the mux *before* you clear the driver-local
> >>> ownership indicator. That looks suspicious. My guess is that this is
> >>> "safe" because the upper layers has some serialization, but I don't
> >>> know. Anyway, even if there is something saving you in the upper
> >>> layers, it looks out of order and unneeded. I would have moved the
> >>> below vmux->active = -1; statement up to before the above deselect.
> >>>
> >>> With that fixed, mux usage looks good to me, so you can add an Acked-
> >>> by from me if you wish (goes for the bindings patch as well).
> >>
> >> Ouch, that was a bit too soon. If there is *no* serialization in the
> >> upper layers, this is *not* ok, even with my reordering. There must be
> >> only one call to mux_control_deselect, and w/o serialization there
> >> is a race where you might get multiple deselect calls when several
> >> callers makes it through the active != index check before any of them
> >> manages to set active = -1. That race must be taken care of!
> >
> > Thank you, I've resent a version with a mutex lock around vmux->active.
>
> I had a bunch of ifs in the above message, so I'm not sure it's needed.
> I would expect there to be a lock outside somewhere in the media layer.
> A cursory look gets me to media-entity.c and media_entity_setup_link()
> which does have a mutex. But I'm no media expert, so maybe there are other
> ways of getting to video_mux_link_setup that I'm not aware of?
link_setup is always called under the graph mutex of the /dev/media
device. That is why I didn't think about locking too hard. In fact, I
initially wrote this expecting mux_control_get_exclusive to exist and
the mux select/deselect not to be locked at all.
But set_format is called from an unlocked ioctl on a /dev/v4l-subdev
device. Until your comments I didn't notice that it would be possible to
let link_setup set active = -1 in the middle of the set_format call,
causing it to return garbage.
> If you do end up relying on external locking, a comment saying so would
> be nice. Or even better, some __must_hold markup if possible?
>
> Cheers,
> peda
regards
Philipp
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH v6 3/8] i2c: i2c-smbus: add of_i2c_setup_smbus_alert
From: Phil Reid @ 2017-05-03 8:31 UTC (permalink / raw)
To: Benjamin Tissoires
Cc: wsa, robh+dt, mark.rutland, sre, peda, linux-i2c, devicetree,
linux-pm
In-Reply-To: <20170502102013.GB2382@mail.corp.redhat.com>
G'day Benjamin,
On 2/05/2017 18:20, Benjamin Tissoires wrote:
> Hi Phil,
>
> On May 02 2017 or thereabouts, Phil Reid wrote:
>> This commit adds of_i2c_setup_smbus_alert which allows the smbalert
>> driver to be attached to an i2c adapter via the device tree.
>>
>> Signed-off-by: Phil Reid <preid@electromag.com.au>
>> ---
>> Documentation/devicetree/bindings/i2c/i2c.txt | 4 +--
>> drivers/i2c/i2c-smbus.c | 35 +++++++++++++++++++++++++++
>> include/linux/i2c-smbus.h | 9 +++++++
>> 3 files changed, 46 insertions(+), 2 deletions(-)
>>
>> diff --git a/Documentation/devicetree/bindings/i2c/i2c.txt b/Documentation/devicetree/bindings/i2c/i2c.txt
>> index cee9d50..1126398 100644
>> --- a/Documentation/devicetree/bindings/i2c/i2c.txt
>> +++ b/Documentation/devicetree/bindings/i2c/i2c.txt
>> @@ -59,8 +59,8 @@ wants to support one of the below features, it should adapt the bindings below.
>> interrupts used by the device.
>>
>> - interrupt-names
>> - "irq" and "wakeup" names are recognized by I2C core, other names are
>> - left to individual drivers.
>> + "irq", "wakeup" and "smbus_alert" names are recognized by I2C core,
>> + other names are left to individual drivers.
>>
>> - host-notify
>> device uses SMBus host notify protocol instead of interrupt line.
>> diff --git a/drivers/i2c/i2c-smbus.c b/drivers/i2c/i2c-smbus.c
>> index df0e2fa..a8f8439 100644
>> --- a/drivers/i2c/i2c-smbus.c
>> +++ b/drivers/i2c/i2c-smbus.c
>> @@ -21,6 +21,7 @@
>> #include <linux/interrupt.h>
>> #include <linux/kernel.h>
>> #include <linux/module.h>
>> +#include <linux/of_irq.h>
>> #include <linux/slab.h>
>> #include <linux/workqueue.h>
>>
>> @@ -238,6 +239,40 @@ struct i2c_client *i2c_setup_smbus_alert(struct i2c_adapter *adapter,
>> }
>> EXPORT_SYMBOL_GPL(i2c_setup_smbus_alert);
>>
>> +int of_i2c_setup_smbus_alert(struct i2c_adapter *adap)
>> +{
>> + struct i2c_client *client;
>> + struct i2c_smbus_alert_setup *setup;
>> + struct i2c_board_info info = {
>> + I2C_BOARD_INFO("smbus_alert", 0x0c),
>> + };
>
> Shouldn't you use i2c_setup_smbus_alert() instead of manually recreating
> the board_info?
Yes looks like that could be cleaned up I think.
Look at i2c_new_device also it looks like client needs to be kept around and
i2c_unregister_device. I was assuming the apdater would clean it up.
But I'm not sure that's the case when looking again. That makes the change
more invasive into the adpater. would need to add an i2c_client ptr to the
i2c_adapter struct. Thoughts?
> Also i2c_setup_smbus_alert() mentions that the level trigger has to be
> explicitely mentioned, and I can't find if this is the case in your
> patch.
I'm not too sure how this wrks with the device tree / OF stuff.
But initally I had the device tree irq type set incorrectly and things
didn't work. Fixing that up and everything flow thru to the request irq
and was set for the correct mode. So I guessed there was some "magic" in
the core irq code somewhere.
I actually think the thread irq handler should be able to handle everything
and the work queue should disappear. Pretty sure the irq core handles disabling
level interrupts will the threaded handler services things now. But I don't
quite understand how the original code was handle edge vs level trigger irq's.
Not having access to test that I was reluctant to change the hard irq.
Any suggestions helpful...
>
>> + int irq;
>> +
>> + if (!adap->dev.of_node)
>> + return 0;
>> +
>> + irq = of_irq_get_byname(adap->dev.of_node, "smbus_alert");
>> + if (irq == -EINVAL || irq == -ENODATA)
>> + return 0;
>> + else if (irq < 0)
>> + return irq;
>> +
>> + setup = devm_kzalloc(&adap->dev, sizeof(struct i2c_smbus_alert_setup),
>> + GFP_KERNEL);
>
> Problem is i2c-core doesn't use devres at all for now. So the code is
> correct here as it won't segfault but mixing both devres and non-devres
> is error prone.
Ok I look at how to avoid the devm alloc.
>
>> + if (!setup)
>> + return -ENOMEM;
>> +
>> + setup->irq = irq;
>> + info.platform_data = setup;
>> +
>> + client = i2c_new_device(adap, &info);
>> + if (!client)
>> + return -ENODEV;
>> +
>> + return 0;
>> +}
>> +EXPORT_SYMBOL_GPL(of_i2c_setup_smbus_alert);
>> +
>> /**
>> * i2c_handle_smbus_alert - Handle an SMBus alert
>> * @ara: the ARA client on the relevant adapter
>> diff --git a/include/linux/i2c-smbus.h b/include/linux/i2c-smbus.h
>> index a138502..4732d09 100644
>> --- a/include/linux/i2c-smbus.h
>> +++ b/include/linux/i2c-smbus.h
>> @@ -50,4 +50,13 @@ struct i2c_client *i2c_setup_smbus_alert(struct i2c_adapter *adapter,
>> struct i2c_smbus_alert_setup *setup);
>> int i2c_handle_smbus_alert(struct i2c_client *ara);
>>
>> +#if IS_ENABLED(CONFIG_I2C_SMBUS)
>> +int of_i2c_setup_smbus_alert(struct i2c_adapter *adap);
>> +#else
>> +static inline int of_i2c_setup_smbus_alert(struct i2c_adapter *adap)
>> +{
>> + return 0;
>> +}
>> +#endif
>> +
>> #endif /* _LINUX_I2C_SMBUS_H */
>> --
>> 1.8.3.1
>
> Cheers,
> Benjamin
>
>
--
Regards
Phil Reid
^ permalink raw reply
* Re: [PATCH v2 7/7] ARM: dts: imx7d-sdb: Enable PCIe peripheral
From: Shawn Guo @ 2017-05-03 8:30 UTC (permalink / raw)
To: Andrey Smirnov
Cc: yurovsky-Re5JQEeQqe8AvxtiuMwx3w, Dong Aisheng, Sascha Hauer,
Fabio Estevam, Rob Herring, Mark Rutland, Russell King,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <20170418150133.31679-8-andrew.smirnov-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
On Tue, Apr 18, 2017 at 08:01:33AM -0700, Andrey Smirnov wrote:
> Enable PCIe peripheral on this board.
>
> Cc: yurovsky-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org
> Cc: Dong Aisheng <aisheng.dong-3arQi8VN3Tc@public.gmane.org>
> Cc: Shawn Guo <shawnguo-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> Cc: Sascha Hauer <kernel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org>
> Cc: Rob Herring <robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> Cc: Mark Rutland <mark.rutland-5wv7dgnIgG8@public.gmane.org>
> Cc: Russell King <linux-I+IVW8TIWO2tmTQ+vhA3Yw@public.gmane.org>
> Cc: devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org
> Signed-off-by: Andrey Smirnov <andrew.smirnov-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> ---
> arch/arm/boot/dts/imx7d-sdb.dts | 6 ++++++
> 1 file changed, 6 insertions(+)
>
> diff --git a/arch/arm/boot/dts/imx7d-sdb.dts b/arch/arm/boot/dts/imx7d-sdb.dts
> index d6f2dda..65dda66 100644
> --- a/arch/arm/boot/dts/imx7d-sdb.dts
> +++ b/arch/arm/boot/dts/imx7d-sdb.dts
> @@ -349,6 +349,12 @@
> };
> };
>
> +&pcie {
> + pinctrl-names = "default";
No corresponding pinctrl-0 entry? In that case, there is no point to
have pinctrl-names.
Shawn
> + reset-gpio = <&extended_io 1 GPIO_ACTIVE_LOW>;
> + status = "okay";
> +};
> +
> &pwm1 {
> pinctrl-names = "default";
> pinctrl-0 = <&pinctrl_pwm1>;
> --
> 2.9.3
>
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH v2 6/7] ARM: dts: imx7d: Add node for PCIe controller
From: Shawn Guo @ 2017-05-03 8:28 UTC (permalink / raw)
To: Andrey Smirnov
Cc: yurovsky, Dong Aisheng, Sascha Hauer, Fabio Estevam, Rob Herring,
Mark Rutland, Russell King, devicetree, linux-kernel,
linux-arm-kernel
In-Reply-To: <20170418150133.31679-7-andrew.smirnov@gmail.com>
On Tue, Apr 18, 2017 at 08:01:32AM -0700, Andrey Smirnov wrote:
> Cc: yurovsky@gmail.com
> Cc: Dong Aisheng <aisheng.dong@nxp.com>
> Cc: Shawn Guo <shawnguo@kernel.org>
> Cc: Sascha Hauer <kernel@pengutronix.de>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: Mark Rutland <mark.rutland@arm.com>
> Cc: Russell King <linux@armlinux.org.uk>
> Cc: devicetree@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Cc: linux-arm-kernel@lists.infradead.org
> Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
> ---
> arch/arm/boot/dts/imx7d.dtsi | 37 +++++++++++++++++++++++++++++++++++++
> 1 file changed, 37 insertions(+)
>
> diff --git a/arch/arm/boot/dts/imx7d.dtsi b/arch/arm/boot/dts/imx7d.dtsi
> index f6dee41..f46814a 100644
> --- a/arch/arm/boot/dts/imx7d.dtsi
> +++ b/arch/arm/boot/dts/imx7d.dtsi
> @@ -42,6 +42,7 @@
> */
>
> #include "imx7s.dtsi"
> +#include <dt-bindings/reset/imx7-reset.h>
It has dependency on imx7 reset driver, and I need to wait for
v4.12-rc1 to apply the patch.
Shawn
^ permalink raw reply
* Re: [PATCH v2 3/7] ARM: dts: imx7s: Add node for GPC
From: Shawn Guo @ 2017-05-03 8:26 UTC (permalink / raw)
To: Andrey Smirnov
Cc: yurovsky-Re5JQEeQqe8AvxtiuMwx3w, Dong Aisheng, Sascha Hauer,
Fabio Estevam, Rob Herring, Mark Rutland, Russell King,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <20170418150133.31679-4-andrew.smirnov-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
On Tue, Apr 18, 2017 at 08:01:29AM -0700, Andrey Smirnov wrote:
> Add node for GPC and specify as a parent interrupt controller for SoC bus.
>
> Cc: yurovsky-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org
> Cc: Dong Aisheng <aisheng.dong-3arQi8VN3Tc@public.gmane.org>
> Cc: Shawn Guo <shawnguo-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> Cc: Sascha Hauer <kernel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org>
> Cc: Rob Herring <robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> Cc: Mark Rutland <mark.rutland-5wv7dgnIgG8@public.gmane.org>
> Cc: Russell King <linux-I+IVW8TIWO2tmTQ+vhA3Yw@public.gmane.org>
> Cc: devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org
> Signed-off-by: Andrey Smirnov <andrew.smirnov-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> ---
> arch/arm/boot/dts/imx7s.dtsi | 26 +++++++++++++++++++++++++-
> 1 file changed, 25 insertions(+), 1 deletion(-)
>
> diff --git a/arch/arm/boot/dts/imx7s.dtsi b/arch/arm/boot/dts/imx7s.dtsi
> index 7148eac..5ba1289 100644
> --- a/arch/arm/boot/dts/imx7s.dtsi
> +++ b/arch/arm/boot/dts/imx7s.dtsi
> @@ -42,6 +42,7 @@
> */
>
> #include <dt-bindings/clock/imx7d-clock.h>
> +#include <dt-bindings/power/imx7-power.h>
It has dependency on imx7 power domain driver, and I need to wait for
v4.12-rc1 to apply the patch.
Shawn
> #include <dt-bindings/gpio/gpio.h>
> #include <dt-bindings/input/input.h>
> #include <dt-bindings/interrupt-controller/arm-gic.h>
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH v2 2/7] ARM: imx: Select GPCv2 for i.MX7
From: Shawn Guo @ 2017-05-03 8:24 UTC (permalink / raw)
To: Andrey Smirnov
Cc: yurovsky-Re5JQEeQqe8AvxtiuMwx3w, Dong Aisheng, Sascha Hauer,
Fabio Estevam, Rob Herring, Mark Rutland, Russell King,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <20170418150133.31679-3-andrew.smirnov-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
On Tue, Apr 18, 2017 at 08:01:28AM -0700, Andrey Smirnov wrote:
> GPCv2 IP block is a part of i.MX7 SoC. Select it to make corresponding
> driver availible to support DT changes following this patch.
>
> Cc: yurovsky-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org
> Cc: Dong Aisheng <aisheng.dong-3arQi8VN3Tc@public.gmane.org>
> Cc: Shawn Guo <shawnguo-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> Cc: Sascha Hauer <kernel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org>
> Cc: Rob Herring <robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> Cc: Mark Rutland <mark.rutland-5wv7dgnIgG8@public.gmane.org>
> Cc: Russell King <linux-I+IVW8TIWO2tmTQ+vhA3Yw@public.gmane.org>
> Cc: devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org
> Signed-off-by: Andrey Smirnov <andrew.smirnov-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> ---
> arch/arm/mach-imx/Kconfig | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig
> index 936c59d..1a4ea3a 100644
> --- a/arch/arm/mach-imx/Kconfig
> +++ b/arch/arm/mach-imx/Kconfig
> @@ -532,6 +532,7 @@ config SOC_IMX7D
> bool "i.MX7 Dual support"
> select PINCTRL_IMX7D
> select ARM_GIC
> + select IMX_GPCV2
Yes, PINCTRL_IMX7D is already out of order, but still please keep new
added one sorted in alphabetic order.
Shawn
> select HAVE_ARM_ARCH_TIMER
> select HAVE_IMX_ANATOP
> select HAVE_IMX_MMDC
> --
> 2.9.3
>
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH v2 1/7] ARM: dts: i.MX: Reintroduce 'anatop-enable-bit' where appropriate
From: Shawn Guo @ 2017-05-03 8:22 UTC (permalink / raw)
To: Andrey Smirnov
Cc: yurovsky-Re5JQEeQqe8AvxtiuMwx3w, Dong Aisheng, Sascha Hauer,
Fabio Estevam, Rob Herring, Mark Rutland, Russell King,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <20170418150133.31679-2-andrew.smirnov-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
On Tue, Apr 18, 2017 at 08:01:27AM -0700, Andrey Smirnov wrote:
> Now that support for 'anatop-enable-bit' has been added to ANADIG
> driver, reintroduce 'anatop-enable-bit' for all applicable LDOs.
>
> Cc: yurovsky-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org
> Cc: Dong Aisheng <aisheng.dong-3arQi8VN3Tc@public.gmane.org>
> Cc: Shawn Guo <shawnguo-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> Cc: Sascha Hauer <kernel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org>
> Cc: Rob Herring <robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> Cc: Mark Rutland <mark.rutland-5wv7dgnIgG8@public.gmane.org>
> Cc: Russell King <linux-I+IVW8TIWO2tmTQ+vhA3Yw@public.gmane.org>
> Cc: devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org
> Signed-off-by: Andrey Smirnov <andrew.smirnov-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Please use 'imx' instead of 'i.MX' in subject prefix to make it more
consistent with other patches in the series.
Shawn
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH 4/5] arm64: dts: Add ipq8074 SoC and MTP board support
From: Varadarajan Narayanan @ 2017-05-03 7:30 UTC (permalink / raw)
To: Stephen Boyd
Cc: robh+dt, mark.rutland, mturquette, linus.walleij, andy.gross,
david.brown, catalin.marinas, will.deacon, devicetree,
linux-kernel, linux-clk, linux-gpio, linux-arm-msm, linux-soc,
linux-arm-kernel, sricharan, absahu, sjaganat,
Manoharan Vijaya Raghavan
In-Reply-To: <20170428185349.GD7065@codeaurora.org>
On 4/29/2017 12:23 AM, Stephen Boyd wrote:
> On 04/28, Varadarajan Narayanan wrote:
>> diff --git a/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts b/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts
>> new file mode 100644
>> index 0000000..c150bea
>> --- /dev/null
>> +++ b/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts
>> @@ -0,0 +1,48 @@
>> +/dts-v1/;
>> +/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 and
>> + * only version 2 as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> + * GNU General Public License for more details.
>> + */
>> +#include "ipq8074.dtsi"
>> +
>> +/ {
>> + #address-cells = <0x2>;
>> + #size-cells = <0x2>;
>> + model = "Qualcomm Technologies, Inc. IPQ8074-HK01";
>> + compatible = "qcom,ipq8074-hk01", "qcom,ipq8074";
>> + interrupt-parent = <&intc>;
>> +
>> + chosen {
>> + bootargs = "console=ttyMSM0,115200,n8 root=/dev/ram0 rw init=/init";
>
> Add an aliases node for serial0 and use a chosen node with stdout-path = "serial0" instead please.
Ok
>
>> + };
>> +
>> + memory {
>> + device_type = "memory";
>> + reg = <0x0 0x40000000 0x0 0x20000000>;
>> + };
>> +
>> + soc: soc {
>
> Do you need the soc label here? Please remove.
Ok
>
>> + pinctrl@1000000 {
>> + serial_4_pins: serial4_pinmux {
>> + mux {
>> + pins = "gpio23", "gpio24";
>> + function = "blsp4_uart1";
>> + bias-disable;
>> + };
>> + };
>> + };
>> +
>> + serial@78b3000 {
>> + pinctrl-0 = <&serial_4_pins>;
>> + pinctrl-names = "default";
>> + status = "ok";
>> + };
>> + };
>> +};
>> diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
>> new file mode 100644
>> index 0000000..f910cc0
>> --- /dev/null
>> +++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
>> @@ -0,0 +1,153 @@
>> +/*
>> + * Copyright (c) 2017, The Linux Foundation. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 and
>> + * only version 2 as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> + * GNU General Public License for more details.
>> + */
>> +
>> +#include <dt-bindings/interrupt-controller/arm-gic.h>
>> +#include <dt-bindings/clock/qcom,gcc-ipq8074.h>
>> +
>> +/ {
>> + model = "Qualcomm Technologies, Inc. IPQ8074";
>> + compatible = "qcom,ipq8074";
>> +
>> + soc: soc {
>> + #address-cells = <0x1>;
>> + #size-cells = <0x1>;
>> + ranges = <0 0 0 0xffffffff>;
>> + compatible = "simple-bus";
>> +
>> + pinctrl@1000000 {
>> + compatible = "qcom,ipq8074-pinctrl";
>> + reg = <0x1000000 0x300000>;
>> + interrupts = <GIC_SPI 208 IRQ_TYPE_LEVEL_HIGH>;
>> + gpio-controller;
>> + #gpio-cells = <0x2>;
>> + interrupt-controller;
>> + #interrupt-cells = <0x2>;
>> + };
>> +
>> + intc: interrupt-controller@b000000 {
>> + compatible = "qcom,msm-qgic2";
>> + interrupt-controller;
>> + #interrupt-cells = <0x3>;
>> + reg = <0xb000000 0x1000>,
>> + <0xb002000 0x1000>;
>
> Please align this up with previous reg property.
Ok
>
>> + };
>> +
>> + timer {
>> + compatible = "arm,armv8-timer";
>> + interrupts = <GIC_PPI 2 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
>> + <GIC_PPI 3 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
>> + <GIC_PPI 4 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
>> + <GIC_PPI 1 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>;
>> + };
>
> Is there an mmio timer as well? We should add it too.
Ok
>
>> +
>> + gcc: gcc@1800000 {
>> + compatible = "qcom,gcc-ipq8074";
>> + reg = <0x1800000 0x80000>;
>
> Wow that is a huge area! Is it really that large?
Yes, per the memory map this region is 512K.
>
>> + #clock-cells = <0x1>;
>> + #reset-cells = <0x1>;
>> + };
>> +
>> + serial@78b3000 {
>> + compatible = "qcom,msm-uartdm-v1.4", "qcom,msm-uartdm";
>> + reg = <0x78b3000 0x200>;
>> + interrupts = <GIC_SPI 308 IRQ_TYPE_LEVEL_HIGH>;
>> + clocks = <&gcc GCC_BLSP1_UART5_APPS_CLK>,
>> + <&gcc GCC_BLSP1_AHB_CLK>;
>> + clock-names = "core", "iface";
>> + status = "disabled";
>> + };
>> + };
>> +
>> + cpus {
>> + #address-cells = <0x1>;
>> + #size-cells = <0x0>;
>> +
>> + cpu-map {
>> +
>> + cluster0 {
>> +
>> + core0 {
>> + cpu = <&CPU0>;
>> + };
>> +
>> + core1 {
>> + cpu = <&CPU1>;
>> + };
>> +
>> + core2 {
>> + cpu = <&CPU2>;
>> + };
>> +
>> + core3 {
>> + cpu = <&CPU3>;
>> + };
>> + };
>> + };
>
> Is this needed? Looks ok, but just curious if we need to do it
> for other arm64 platforms we support.
Don't see the need for topology at this time, will add it later if needed.
>
>> +
>> + CPU0: cpu@0 {
>> + device_type = "cpu";
>> + compatible = "arm,cortex-a53", "arm,armv8";
>> + reg = <0x0>;
>> + next-level-cache = <&L2_0>;
>> + enable-method = "psci";
>> + };
>> +
>> + CPU1: cpu@1 {
>> + device_type = "cpu";
>> + compatible = "arm,cortex-a53", "arm,armv8";
>> + enable-method = "psci";
>> + reg = <0x1>;
>> + next-level-cache = <&L2_0>;
>> + };
>> +
>> + CPU2: cpu@2 {
>> + device_type = "cpu";
>> + compatible = "arm,cortex-a53", "arm,armv8";
>> + enable-method = "psci";
>> + reg = <0x2>;
>> + next-level-cache = <&L2_0>;
>> + };
>> +
>> + CPU3: cpu@3 {
>> + device_type = "cpu";
>> + compatible = "arm,cortex-a53", "arm,armv8";
>> + enable-method = "psci";
>> + reg = <0x3>;
>> + next-level-cache = <&L2_0>;
>> + };
>> +
>> + L2_0: l2-cache {
>> + compatible = "cache";
>> + cache-level = <0x2>;
>> + };
>
> This should be inside some CPU? CPU0?
There is only one L2. We followed the same convention as in msm8916.dtsi.
>
>> + };
>
> We should be able to add the performance monitor node too?
>
Ok
>> +
>> + psci {
>> + compatible = "arm,psci-1.0";
>> + method = "smc";
>> + };
>> +
>> + clocks {
>> + sleep_clk: sleep_clk {
>> + compatible = "fixed-clock";
>> + clock-frequency = <32000>;
>
> Not 32765 or 32768?
It is 32000.
>
>> + #clock-cells = <0>;
>> + };
>> +
>> + xo: xo {
>> + compatible = "fixed-clock";
>> + clock-frequency = <19200000>;
>> + #clock-cells = <0>;
>> + };
>> + };
>
-Varada
--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora
Forum, a Linux Foundation Collaborative Project
^ permalink raw reply
* Re: [PATCH v5] media: platform: Renesas IMR driver
From: Geert Uytterhoeven @ 2017-05-03 7:22 UTC (permalink / raw)
To: Laurent Pinchart
Cc: Sergei Shtylyov, Rob Herring, Mark Rutland, Mauro Carvalho Chehab,
devicetree@vger.kernel.org, Linux Media Mailing List,
Linux-Renesas, Konstantin Kozhevnikov, Kuninori Morimoto
In-Reply-To: <2382097.9ZIG3XAO0j@avalon>
Hi Laurent,
On Tue, May 2, 2017 at 11:17 PM, Laurent Pinchart
<laurent.pinchart@ideasonboard.com> wrote:
> On Wednesday 22 Mar 2017 10:34:16 Geert Uytterhoeven wrote:
>> On Thu, Mar 9, 2017 at 9:08 PM, Sergei Shtylyov wrote:
>> > --- /dev/null
>> > +++ media_tree/Documentation/devicetree/bindings/media/rcar_imr.txt
>> > @@ -0,0 +1,27 @@
>> > +Renesas R-Car Image Renderer (Distortion Correction Engine)
>> > +-----------------------------------------------------------
>> > +
>> > +The image renderer, or the distortion correction engine, is a drawing
>> > processor
>> > +with a simple instruction system capable of referencing video capture
>> > data or
>> > +data in an external memory as 2D texture data and performing texture
>> > mapping
>> > +and drawing with respect to any shape that is split into triangular
>> > objects.
>> > +
>> > +Required properties:
>> > +
>> > +- compatible: "renesas,<soctype>-imr-lx4", "renesas,imr-lx4" as a
>> > fallback for
>> > + the image renderer light extended 4 (IMR-LX4) found in the R-Car gen3
>> > SoCs,
>> > + where the examples with <soctype> are:
>> > + - "renesas,r8a7795-imr-lx4" for R-Car H3,
>> > + - "renesas,r8a7796-imr-lx4" for R-Car M3-W.
>>
>> Laurent: what do you think about the need for SoC-specific compatible
>> values for the various IM* blocks?
>
> There's no documented IP core version register, but when dumping all
> configuration registers on H3 and M3-W I noticed that register 0x002c, not
> documented in the datasheet, reads 0x14060514 on all four IMR instances in H3,
> and 0x20150505 on both instances in M3-W.
>
> This looks like a version register to me. If my assumption is correct, we
> could do without any SoC-specific compatible string.
I read this assumed version registers on all R-Car SoCs, after writing
zero to 0xe6150990 (SMSTPCR8).
IMR-X2 on R-Car H2: 0x12072009
IMR-LSX2 on R-Car H2: 0x12072009
IMR-LSX3 on R-Car V2H: 0x13052617
IMR-LX2 on R-Car M2-W: 0x12072009
IMR-LX2 on R-Car M2-N: 0x12072009
IMR-LX2 on R-Car E2: 0x13091909
IMR-LX3 on R-Car V2H: 0x13052617
Note that several IDs are the same, but you know the type from the
compatible value.
It would be good to get confirmation from the hardware team that this is
indeed a version register.
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* [PATCH v2 resend] arm: dts: sun7i-a20-bananapi: name the GPIO lines
From: Oleksij Rempel @ 2017-05-03 7:09 UTC (permalink / raw)
To: ore
Cc: devicetree, Chen-Yu Tsai, Maxime Ripard, linux-arm-kernel,
Oleksij Rempel
In-Reply-To: <CACRpkdZcnJ1eCNW4ZF_A4zbeCt3JcAuw-1meB_Pn7ctMy99B_g@mail.gmail.com>
This names the GPIO lines on the Banana Pi board in accordance with
the A20_Banana_Pi v1.4 Specification.
This will make these line names reflect through to userspace
so that they can easily be identified and used with the new
character device ABI.
Some care has been taken to name all lines, not just those used
by the external connectors, also lines that are muxed into some
other function than GPIO: these are named "[FOO]" so that users
can see with lsgpio what all lines are used for.
Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
Acked-by: Linus Walleij <linus.walleij@linaro.org>
Cc: devicetree@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: Chen-Yu Tsai <wens@csie.org>
Cc: Maxime Ripard <maxime.ripard@free-electrons.com>
---
arch/arm/boot/dts/sun7i-a20-bananapi.dts | 60 ++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/arch/arm/boot/dts/sun7i-a20-bananapi.dts b/arch/arm/boot/dts/sun7i-a20-bananapi.dts
index 91f2e5f..5b3e0ee 100644
--- a/arch/arm/boot/dts/sun7i-a20-bananapi.dts
+++ b/arch/arm/boot/dts/sun7i-a20-bananapi.dts
@@ -178,6 +178,66 @@
};
&pio {
+ /*
+ * Legend: proper name = the GPIO line is used as GPIO
+ * NC = not connected (not routed from the SoC)
+ * "[PER]" = pin is muxed for peripheral (not GPIO)
+ * "" = no idea, schematic doesn't say, could be
+ * unrouted (not connected to any external pin)
+ * LSEC = Low Speed External Connector
+ * HSEC = High Speed External Connector
+ */
+ gpio-line-names =
+ /* PA */
+ "[ERXD3]", "[ERXD2]", "[ERXD1]", "[ERXD0]", "[ETXD3]",
+ "[ETXD2]", "[ETXD1]", "[ETXD0]",
+ "[ERXCK]", "[ERXERR]", "[ERXDV]", "[EMDC]", "[EMDIO]",
+ "[ETXEN]", "[ETXCK]", "[ECRS]",
+ "[ECOL]", "[ETXERR]", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "",
+ /* PB */
+ "[PMU-SCK]", "[PMU-SDA]", "", "", "", "NC", "NC", "NC",
+ "NC", "[USB0-DRV]", "NC", "NC", "NC", "NC", "", "",
+ "", "", "", "", "SCL", "SDA", "", "",
+ "", "", "", "", "", "", "", "",
+ /* PC */
+ "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "",
+ /* PD */
+ "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "",
+ /* PE */
+ "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "",
+ /* PF */
+ "[SD0-D1]", "[SD0-D0]", "[SD0-CLK]", "[SD0-CMD]", "[SD0-D3]",
+ "[SD0-D2]", "", "",
+ "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "",
+ /* PG */
+ "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "",
+ /* PH */
+ "TXD0", "RXD0", "IO-1", "PH3", "[USB0-IDDET]", "PH5", "", "",
+ "", "", "[SD0-DET]", "", "", "", "", "",
+ "NC", "", "", "", "IO-4", "IO-5", "NC", "[EMAC-PWR-EN]",
+ "[LED1]", "NC", "NC", "NC", "", "", "", "",
+ /* PI */
+ "", "", "", "IO-GCLK", "NC", "NC", "NC", "NC",
+ "NC", "NC", "[SPI-CE0]", "[SPI-CLK]", "[SPI-MOSI]",
+ "[SPI-MISO]", "[SPI-CE1]", "NC",
+ "IO-6", "IO-3", "IO-2", "IO-0", "", "", "", "",
+ "", "", "", "", "", "", "", "";
+
usb0_id_detect_pin: usb0_id_detect_pin@0 {
pins = "PH4";
function = "gpio_in";
--
2.7.4
^ permalink raw reply related
* [PATCH v3 4/4] rpmsg: Introduce Qualcomm RPM glink driver
From: Bjorn Andersson @ 2017-05-03 5:29 UTC (permalink / raw)
To: Andy Gross, Rob Herring, Mark Rutland, Ohad Ben-Cohen
Cc: linux-arm-msm, linux-soc, devicetree, linux-kernel,
linux-remoteproc
In-Reply-To: <20170503052929.17422-1-bjorn.andersson@linaro.org>
This introduces a basic driver for communicating over "native glink"
with the RPM found in Qualcomm platforms.
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
---
Changes since v2:
- Replace syscon with apcs_ipc doorbell implementation
Changes since v1:
- Depend on HAS_IOMEM, for UM build failure
- Wrap read/write indices on >= size, to keep the values valid when message
aligns with end of fifo.
drivers/rpmsg/Kconfig | 10 +
drivers/rpmsg/Makefile | 1 +
drivers/rpmsg/qcom_glink_rpm.c | 1225 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 1236 insertions(+)
create mode 100644 drivers/rpmsg/qcom_glink_rpm.c
diff --git a/drivers/rpmsg/Kconfig b/drivers/rpmsg/Kconfig
index f12ac0b28263..449d43511c92 100644
--- a/drivers/rpmsg/Kconfig
+++ b/drivers/rpmsg/Kconfig
@@ -13,6 +13,16 @@ config RPMSG_CHAR
in /dev. They make it possible for user-space programs to send and
receive rpmsg packets.
+config RPMSG_QCOM_GLINK_RPM
+ tristate "Qualcomm RPM Glink driver"
+ select RPMSG
+ depends on HAS_IOMEM
+ depends on QCOM_APCS_IPC
+ help
+ Say y here to enable support for the GLINK RPM communication driver,
+ which serves as a channel for communication with the RPM in GLINK
+ enabled systems.
+
config RPMSG_QCOM_SMD
tristate "Qualcomm Shared Memory Driver (SMD)"
depends on QCOM_SMEM
diff --git a/drivers/rpmsg/Makefile b/drivers/rpmsg/Makefile
index fae9a6d548fb..28cc19088cc0 100644
--- a/drivers/rpmsg/Makefile
+++ b/drivers/rpmsg/Makefile
@@ -1,4 +1,5 @@
obj-$(CONFIG_RPMSG) += rpmsg_core.o
obj-$(CONFIG_RPMSG_CHAR) += rpmsg_char.o
+obj-$(CONFIG_RPMSG_QCOM_GLINK_RPM) += qcom_glink_rpm.o
obj-$(CONFIG_RPMSG_QCOM_SMD) += qcom_smd.o
obj-$(CONFIG_RPMSG_VIRTIO) += virtio_rpmsg_bus.o
diff --git a/drivers/rpmsg/qcom_glink_rpm.c b/drivers/rpmsg/qcom_glink_rpm.c
new file mode 100644
index 000000000000..d3a4951e0b78
--- /dev/null
+++ b/drivers/rpmsg/qcom_glink_rpm.c
@@ -0,0 +1,1225 @@
+/*
+ * Copyright (c) 2016-2017, Linaro Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/idr.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/list.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/rpmsg.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/soc/qcom/apcs_ipc.h>
+
+#include "rpmsg_internal.h"
+
+#define RPM_TOC_SIZE 256
+#define RPM_TOC_MAGIC 0x67727430 /* grt0 */
+#define RPM_TOC_MAX_ENTRIES ((RPM_TOC_SIZE - sizeof(struct rpm_toc)) / \
+ sizeof(struct rpm_toc_entry))
+
+#define RPM_TX_FIFO_ID 0x61703272 /* ap2r */
+#define RPM_RX_FIFO_ID 0x72326170 /* r2ap */
+
+#define GLINK_NAME_SIZE 32
+
+#define RPM_GLINK_CID_MIN 1
+#define RPM_GLINK_CID_MAX 65536
+
+struct rpm_toc_entry {
+ __le32 id;
+ __le32 offset;
+ __le32 size;
+} __packed;
+
+struct rpm_toc {
+ __le32 magic;
+ __le32 count;
+
+ struct rpm_toc_entry entries[];
+} __packed;
+
+struct glink_msg {
+ __le16 cmd;
+ __le16 param1;
+ __le32 param2;
+ u8 data[];
+} __packed;
+
+struct glink_rpm_pipe {
+ void __iomem *tail;
+ void __iomem *head;
+
+ void __iomem *fifo;
+
+ size_t length;
+};
+
+/**
+ * struct glink_defer_cmd - deferred incoming control message
+ * @node: list node
+ * @msg: message header
+ * data: payload of the message
+ *
+ * Copy of a received control message, to be added to @rx_queue and processed
+ * by @rx_work of @glink_rpm.
+ */
+struct glink_defer_cmd {
+ struct list_head node;
+
+ struct glink_msg msg;
+ u8 data[];
+};
+
+/**
+ * struct glink_rpm - driver context, relates to one remote subsystem
+ * @dev: reference to the associated struct device
+ * @doorbell: "rpm_hlos" ipc doorbell
+ * @rx_pipe: pipe object for receive FIFO
+ * @tx_pipe: pipe object for transmit FIFO
+ * @irq: IRQ for signaling incoming events
+ * @rx_work: worker for handling received control messages
+ * @rx_lock: protects the @rx_queue
+ * @rx_queue: queue of received control messages to be processed in @rx_work
+ * @tx_lock: synchronizes operations on the tx fifo
+ * @idr_lock: synchronizes @lcids and @rcids modifications
+ * @lcids: idr of all channels with a known local channel id
+ * @rcids: idr of all channels with a known remote channel id
+ */
+struct glink_rpm {
+ struct device *dev;
+
+ struct qcom_apcs_ipc_bell *doorbell;
+
+ struct glink_rpm_pipe rx_pipe;
+ struct glink_rpm_pipe tx_pipe;
+
+ int irq;
+
+ struct work_struct rx_work;
+ spinlock_t rx_lock;
+ struct list_head rx_queue;
+
+ struct mutex tx_lock;
+
+ struct mutex idr_lock;
+ struct idr lcids;
+ struct idr rcids;
+};
+
+enum {
+ GLINK_STATE_CLOSED,
+ GLINK_STATE_OPENING,
+ GLINK_STATE_OPEN,
+ GLINK_STATE_CLOSING,
+};
+
+/**
+ * struct glink_channel - internal representation of a channel
+ * @rpdev: rpdev reference, only used for primary endpoints
+ * @ept: rpmsg endpoint this channel is associated with
+ * @glink: glink_rpm context handle
+ * @refcount: refcount for the channel object
+ * @recv_lock: guard for @ept.cb
+ * @name: unique channel name/identifier
+ * @lcid: channel id, in local space
+ * @rcid: channel id, in remote space
+ * @buf: receive buffer, for gathering fragments
+ * @buf_offset: write offset in @buf
+ * @buf_size: size of current @buf
+ * @open_ack: completed once remote has acked the open-request
+ * @open_req: completed once open-request has been received
+ */
+struct glink_channel {
+ struct rpmsg_endpoint ept;
+
+ struct rpmsg_device *rpdev;
+ struct glink_rpm *glink;
+
+ struct kref refcount;
+
+ spinlock_t recv_lock;
+
+ char *name;
+ unsigned int lcid;
+ unsigned int rcid;
+
+ void *buf;
+ int buf_offset;
+ int buf_size;
+
+ struct completion open_ack;
+ struct completion open_req;
+};
+
+#define to_glink_channel(_ept) container_of(_ept, struct glink_channel, ept)
+
+static const struct rpmsg_endpoint_ops glink_endpoint_ops;
+
+#define RPM_CMD_VERSION 0
+#define RPM_CMD_VERSION_ACK 1
+#define RPM_CMD_OPEN 2
+#define RPM_CMD_CLOSE 3
+#define RPM_CMD_OPEN_ACK 4
+#define RPM_CMD_TX_DATA 9
+#define RPM_CMD_CLOSE_ACK 11
+#define RPM_CMD_TX_DATA_CONT 12
+#define RPM_CMD_READ_NOTIF 13
+
+#define GLINK_FEATURE_INTENTLESS BIT(1)
+
+static struct glink_channel *glink_rpm_alloc_channel(struct glink_rpm *glink,
+ const char *name)
+{
+ struct glink_channel *channel;
+
+ channel = kzalloc(sizeof(*channel), GFP_KERNEL);
+ if (!channel)
+ return ERR_PTR(-ENOMEM);
+
+ /* Setup glink internal glink_channel data */
+ spin_lock_init(&channel->recv_lock);
+ channel->glink = glink;
+ channel->name = kstrdup(name, GFP_KERNEL);
+
+ init_completion(&channel->open_req);
+ init_completion(&channel->open_ack);
+
+ kref_init(&channel->refcount);
+
+ return channel;
+}
+
+static void glink_rpm_channel_release(struct kref *ref)
+{
+ struct glink_channel *channel = container_of(ref, struct glink_channel,
+ refcount);
+
+ kfree(channel->name);
+ kfree(channel);
+}
+
+static size_t glink_rpm_rx_avail(struct glink_rpm *glink)
+{
+ struct glink_rpm_pipe *pipe = &glink->rx_pipe;
+ unsigned int head;
+ unsigned int tail;
+
+ head = readl(pipe->head);
+ tail = readl(pipe->tail);
+
+ if (head < tail)
+ return pipe->length - tail + head;
+ else
+ return head - tail;
+}
+
+static void glink_rpm_rx_peak(struct glink_rpm *glink,
+ void *data, size_t count)
+{
+ struct glink_rpm_pipe *pipe = &glink->rx_pipe;
+ unsigned int tail;
+ size_t len;
+
+ tail = readl(pipe->tail);
+
+ len = min_t(size_t, count, pipe->length - tail);
+ if (len) {
+ __ioread32_copy(data, pipe->fifo + tail,
+ len / sizeof(u32));
+ }
+
+ if (len != count) {
+ __ioread32_copy(data + len, pipe->fifo,
+ (count - len) / sizeof(u32));
+ }
+}
+
+static void glink_rpm_rx_advance(struct glink_rpm *glink,
+ size_t count)
+{
+ struct glink_rpm_pipe *pipe = &glink->rx_pipe;
+ unsigned int tail;
+
+ tail = readl(pipe->tail);
+
+ tail += count;
+ if (tail >= pipe->length)
+ tail -= pipe->length;
+
+ writel(tail, pipe->tail);
+}
+
+static size_t glink_rpm_tx_avail(struct glink_rpm *glink)
+{
+ struct glink_rpm_pipe *pipe = &glink->tx_pipe;
+ unsigned int head;
+ unsigned int tail;
+
+ head = readl(pipe->head);
+ tail = readl(pipe->tail);
+
+ if (tail <= head)
+ return pipe->length - head + tail;
+ else
+ return tail - head;
+}
+
+static unsigned int glink_rpm_tx_write(struct glink_rpm *glink,
+ unsigned int head,
+ const void *data, size_t count)
+{
+ struct glink_rpm_pipe *pipe = &glink->tx_pipe;
+ size_t len;
+
+ len = min_t(size_t, count, pipe->length - head);
+ if (len) {
+ __iowrite32_copy(pipe->fifo + head, data,
+ len / sizeof(u32));
+ }
+
+ if (len != count) {
+ __iowrite32_copy(pipe->fifo, data + len,
+ (count - len) / sizeof(u32));
+ }
+
+ head += count;
+ if (head >= pipe->length)
+ head -= pipe->length;
+
+ return head;
+}
+
+static int glink_rpm_tx(struct glink_rpm *glink,
+ const void *hdr, size_t hlen,
+ const void *data, size_t dlen, bool wait)
+{
+ struct glink_rpm_pipe *pipe = &glink->tx_pipe;
+ unsigned int head;
+ unsigned int tlen = hlen + dlen;
+ int ret;
+
+ /* Reject packets that are too big */
+ if (tlen >= glink->tx_pipe.length)
+ return -EINVAL;
+
+ if (WARN(tlen % 8, "Unaligned TX request"))
+ return -EINVAL;
+
+ ret = mutex_lock_interruptible(&glink->tx_lock);
+ if (ret)
+ return ret;
+
+ while (glink_rpm_tx_avail(glink) < tlen) {
+ if (!wait) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ msleep(10);
+ }
+
+ head = readl(pipe->head);
+ head = glink_rpm_tx_write(glink, head, hdr, hlen);
+ head = glink_rpm_tx_write(glink, head, data, dlen);
+ writel(head, pipe->head);
+
+ qcom_apcs_ipc_ring(glink->doorbell);
+
+out:
+ mutex_unlock(&glink->tx_lock);
+
+ return ret;
+}
+
+static int glink_rpm_send_version(struct glink_rpm *glink)
+{
+ struct glink_msg msg;
+
+ msg.cmd = cpu_to_le16(RPM_CMD_VERSION);
+ msg.param1 = cpu_to_le16(1);
+ msg.param2 = cpu_to_le32(GLINK_FEATURE_INTENTLESS);
+
+ return glink_rpm_tx(glink, &msg, sizeof(msg), NULL, 0, true);
+}
+
+static void glink_rpm_send_version_ack(struct glink_rpm *glink)
+{
+ struct glink_msg msg;
+
+ msg.cmd = cpu_to_le16(RPM_CMD_VERSION_ACK);
+ msg.param1 = cpu_to_le16(1);
+ msg.param2 = cpu_to_le32(0);
+
+ glink_rpm_tx(glink, &msg, sizeof(msg), NULL, 0, true);
+}
+
+static void glink_rpm_send_open_ack(struct glink_rpm *glink,
+ struct glink_channel *channel)
+{
+ struct glink_msg msg;
+
+ msg.cmd = cpu_to_le16(RPM_CMD_OPEN_ACK);
+ msg.param1 = cpu_to_le16(channel->rcid);
+ msg.param2 = cpu_to_le32(0);
+
+ glink_rpm_tx(glink, &msg, sizeof(msg), NULL, 0, true);
+}
+
+/**
+ * glink_rpm_send_open_req() - send a RPM_CMD_OPEN request to the remote
+ * @glink:
+ * @channel:
+ *
+ * Allocates a local channel id and sends a RPM_CMD_OPEN message to the remote.
+ * Will return with refcount held, regardless of outcome.
+ *
+ * Returns 0 on success, negative errno otherwise.
+ */
+static int glink_rpm_send_open_req(struct glink_rpm *glink,
+ struct glink_channel *channel)
+{
+ struct {
+ struct glink_msg msg;
+ u8 name[GLINK_NAME_SIZE];
+ } __packed req;
+ int name_len = strlen(channel->name) + 1;
+ int req_len = ALIGN(sizeof(req.msg) + name_len, 8);
+ int ret;
+
+ kref_get(&channel->refcount);
+
+ mutex_lock(&glink->idr_lock);
+ ret = idr_alloc_cyclic(&glink->lcids, channel,
+ RPM_GLINK_CID_MIN, RPM_GLINK_CID_MAX, GFP_KERNEL);
+ mutex_unlock(&glink->idr_lock);
+ if (ret < 0)
+ return ret;
+
+ channel->lcid = ret;
+
+ req.msg.cmd = cpu_to_le16(RPM_CMD_OPEN);
+ req.msg.param1 = cpu_to_le16(channel->lcid);
+ req.msg.param2 = cpu_to_le32(name_len);
+ strcpy(req.name, channel->name);
+
+ ret = glink_rpm_tx(glink, &req, req_len, NULL, 0, true);
+ if (ret)
+ goto remove_idr;
+
+ return 0;
+
+remove_idr:
+ mutex_lock(&glink->idr_lock);
+ idr_remove(&glink->lcids, channel->lcid);
+ channel->lcid = 0;
+ mutex_unlock(&glink->idr_lock);
+
+ return ret;
+}
+
+static void glink_rpm_send_close_req(struct glink_rpm *glink,
+ struct glink_channel *channel)
+{
+ struct glink_msg req;
+
+ req.cmd = cpu_to_le16(RPM_CMD_CLOSE);
+ req.param1 = cpu_to_le16(channel->lcid);
+ req.param2 = 0;
+
+ glink_rpm_tx(glink, &req, sizeof(req), NULL, 0, true);
+}
+
+static void glink_rpm_send_close_ack(struct glink_rpm *glink, unsigned int rcid)
+{
+ struct glink_msg req;
+
+ req.cmd = cpu_to_le16(RPM_CMD_CLOSE_ACK);
+ req.param1 = cpu_to_le16(rcid);
+ req.param2 = 0;
+
+ glink_rpm_tx(glink, &req, sizeof(req), NULL, 0, true);
+}
+
+static int glink_rpm_rx_defer(struct glink_rpm *glink, size_t extra)
+{
+ struct glink_defer_cmd *dcmd;
+
+ extra = ALIGN(extra, 8);
+
+ if (glink_rpm_rx_avail(glink) < sizeof(struct glink_msg) + extra) {
+ dev_dbg(glink->dev, "Insufficient data in rx fifo");
+ return -ENXIO;
+ }
+
+ dcmd = kzalloc(sizeof(*dcmd) + extra, GFP_ATOMIC);
+ if (!dcmd)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&dcmd->node);
+
+ glink_rpm_rx_peak(glink, &dcmd->msg, sizeof(dcmd->msg) + extra);
+
+ spin_lock(&glink->rx_lock);
+ list_add_tail(&dcmd->node, &glink->rx_queue);
+ spin_unlock(&glink->rx_lock);
+
+ schedule_work(&glink->rx_work);
+ glink_rpm_rx_advance(glink, sizeof(dcmd->msg) + extra);
+
+ return 0;
+}
+
+static int glink_rpm_rx_data(struct glink_rpm *glink, size_t avail)
+{
+ struct glink_channel *channel;
+ struct {
+ struct glink_msg msg;
+ __le32 chunk_size;
+ __le32 left_size;
+ } __packed hdr;
+ unsigned int chunk_size;
+ unsigned int left_size;
+ unsigned int rcid;
+
+ if (avail < sizeof(hdr)) {
+ dev_dbg(glink->dev, "Not enough data in fifo\n");
+ return -EAGAIN;
+ }
+
+ glink_rpm_rx_peak(glink, &hdr, sizeof(hdr));
+ chunk_size = le32_to_cpu(hdr.chunk_size);
+ left_size = le32_to_cpu(hdr.left_size);
+
+ if (avail < sizeof(hdr) + chunk_size) {
+ dev_dbg(glink->dev, "Payload not yet in fifo\n");
+ return -EAGAIN;
+ }
+
+ if (WARN(chunk_size % 4, "Incoming data must be word aligned\n"))
+ return -EINVAL;
+
+ rcid = le16_to_cpu(hdr.msg.param1);
+ channel = idr_find(&glink->rcids, rcid);
+ if (!channel) {
+ dev_dbg(glink->dev, "Data on non-existing channel\n");
+
+ /* Drop the message */
+ glink_rpm_rx_advance(glink, ALIGN(sizeof(hdr) + chunk_size, 8));
+ return 0;
+ }
+
+ /* Might have an ongoing, fragmented, message to append */
+ if (!channel->buf) {
+ channel->buf = kmalloc(chunk_size + left_size, GFP_ATOMIC);
+ if (!channel->buf)
+ return -ENOMEM;
+
+ channel->buf_size = chunk_size + left_size;
+ channel->buf_offset = 0;
+ }
+
+ glink_rpm_rx_advance(glink, sizeof(hdr));
+
+ if (channel->buf_size - channel->buf_offset < chunk_size) {
+ dev_err(glink->dev, "Insufficient space in input buffer\n");
+
+ /* The packet header lied, drop payload */
+ glink_rpm_rx_advance(glink, chunk_size);
+ return -ENOMEM;
+ }
+
+ glink_rpm_rx_peak(glink, channel->buf + channel->buf_offset, chunk_size);
+ channel->buf_offset += chunk_size;
+
+ /* Handle message when no fragments remain to be received */
+ if (!left_size) {
+ spin_lock(&channel->recv_lock);
+ if (channel->ept.cb) {
+ channel->ept.cb(channel->ept.rpdev,
+ channel->buf,
+ channel->buf_offset,
+ channel->ept.priv,
+ RPMSG_ADDR_ANY);
+ }
+ spin_unlock(&channel->recv_lock);
+
+ kfree(channel->buf);
+ channel->buf = NULL;
+ channel->buf_size = 0;
+ }
+
+ /* Each message starts at 8 byte aligned address */
+ glink_rpm_rx_advance(glink, ALIGN(chunk_size, 8));
+
+ return 0;
+}
+
+static int glink_rpm_rx_open_ack(struct glink_rpm *glink, unsigned int lcid)
+{
+ struct glink_channel *channel;
+
+ channel = idr_find(&glink->lcids, lcid);
+ if (!channel) {
+ dev_err(glink->dev, "Invalid open ack packet\n");
+ return -EINVAL;
+ }
+
+ complete(&channel->open_ack);
+
+ return 0;
+}
+
+static irqreturn_t glink_rpm_intr(int irq, void *data)
+{
+ struct glink_rpm *glink = data;
+ struct glink_msg msg;
+ unsigned int param1;
+ unsigned int param2;
+ unsigned int avail;
+ unsigned int cmd;
+ int ret;
+
+ for (;;) {
+ avail = glink_rpm_rx_avail(glink);
+ if (avail < sizeof(msg))
+ break;
+
+ glink_rpm_rx_peak(glink, &msg, sizeof(msg));
+
+ cmd = le16_to_cpu(msg.cmd);
+ param1 = le16_to_cpu(msg.param1);
+ param2 = le32_to_cpu(msg.param2);
+
+ switch (cmd) {
+ case RPM_CMD_VERSION:
+ case RPM_CMD_VERSION_ACK:
+ case RPM_CMD_CLOSE:
+ case RPM_CMD_CLOSE_ACK:
+ ret = glink_rpm_rx_defer(glink, 0);
+ break;
+ case RPM_CMD_OPEN_ACK:
+ ret = glink_rpm_rx_open_ack(glink, param1);
+ glink_rpm_rx_advance(glink, ALIGN(sizeof(msg), 8));
+ break;
+ case RPM_CMD_OPEN:
+ ret = glink_rpm_rx_defer(glink, param2);
+ break;
+ case RPM_CMD_TX_DATA:
+ case RPM_CMD_TX_DATA_CONT:
+ ret = glink_rpm_rx_data(glink, avail);
+ break;
+ case RPM_CMD_READ_NOTIF:
+ glink_rpm_rx_advance(glink, ALIGN(sizeof(msg), 8));
+ qcom_apcs_ipc_ring(glink->doorbell);
+
+ ret = 0;
+ break;
+ default:
+ dev_err(glink->dev, "unhandled rx cmd: %d\n", cmd);
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret)
+ break;
+ }
+
+ return IRQ_HANDLED;
+}
+
+/* Locally initiated rpmsg_create_ept */
+static struct glink_channel *glink_rpm_create_local(struct glink_rpm *glink,
+ const char *name)
+{
+ struct glink_channel *channel;
+ int ret;
+
+ channel = glink_rpm_alloc_channel(glink, name);
+ if (IS_ERR(channel))
+ return ERR_CAST(channel);
+
+ ret = glink_rpm_send_open_req(glink, channel);
+ if (ret)
+ goto release_channel;
+
+ ret = wait_for_completion_timeout(&channel->open_ack, 5 * HZ);
+ if (!ret)
+ goto err_timeout;
+
+ ret = wait_for_completion_timeout(&channel->open_req, 5 * HZ);
+ if (!ret)
+ goto err_timeout;
+
+ glink_rpm_send_open_ack(glink, channel);
+
+ return channel;
+
+err_timeout:
+ /* glink_rpm_send_open_req() did register the channel in lcids*/
+ mutex_lock(&glink->idr_lock);
+ idr_remove(&glink->lcids, channel->lcid);
+ mutex_unlock(&glink->idr_lock);
+
+release_channel:
+ /* Release glink_rpm_send_open_req() reference */
+ kref_put(&channel->refcount, glink_rpm_channel_release);
+ /* Release glink_rpm_alloc_channel() reference */
+ kref_put(&channel->refcount, glink_rpm_channel_release);
+
+ return ERR_PTR(-ETIMEDOUT);
+}
+
+/* Remote initiated rpmsg_create_ept */
+static int glink_rpm_create_remote(struct glink_rpm *glink,
+ struct glink_channel *channel)
+{
+ int ret;
+
+ glink_rpm_send_open_ack(glink, channel);
+
+ ret = glink_rpm_send_open_req(glink, channel);
+ if (ret)
+ goto close_link;
+
+ ret = wait_for_completion_timeout(&channel->open_ack, 5 * HZ);
+ if (!ret) {
+ ret = -ETIMEDOUT;
+ goto close_link;
+ }
+
+ return 0;
+
+close_link:
+ /*
+ * Send a close request to "undo" our open-ack. The close-ack will
+ * release the last reference.
+ */
+ glink_rpm_send_close_req(glink, channel);
+
+ /* Release glink_rpm_send_open_req() reference */
+ kref_put(&channel->refcount, glink_rpm_channel_release);
+
+ return ret;
+}
+
+static struct rpmsg_endpoint *glink_rpm_create_ept(struct rpmsg_device *rpdev,
+ rpmsg_rx_cb_t cb, void *priv,
+ struct rpmsg_channel_info chinfo)
+{
+ struct glink_channel *parent = to_glink_channel(rpdev->ept);
+ struct glink_channel *channel;
+ struct glink_rpm *glink = parent->glink;
+ struct rpmsg_endpoint *ept;
+ const char *name = chinfo.name;
+ int cid;
+ int ret;
+
+ idr_for_each_entry(&glink->rcids, channel, cid) {
+ if (!strcmp(channel->name, name))
+ break;
+ }
+
+ if (!channel) {
+ channel = glink_rpm_create_local(glink, name);
+ if (IS_ERR(channel))
+ return NULL;
+ } else {
+ ret = glink_rpm_create_remote(glink, channel);
+ if (ret)
+ return NULL;
+ }
+
+ ept = &channel->ept;
+ ept->rpdev = rpdev;
+ ept->cb = cb;
+ ept->priv = priv;
+ ept->ops = &glink_endpoint_ops;
+
+ return ept;
+}
+
+static void glink_rpm_destroy_ept(struct rpmsg_endpoint *ept)
+{
+ struct glink_channel *channel = to_glink_channel(ept);
+ struct glink_rpm *glink = channel->glink;
+ unsigned long flags;
+
+ spin_lock_irqsave(&channel->recv_lock, flags);
+ channel->ept.cb = NULL;
+ spin_unlock_irqrestore(&channel->recv_lock, flags);
+
+ /* Decouple the potential rpdev from the channel */
+ channel->rpdev = NULL;
+
+ glink_rpm_send_close_req(glink, channel);
+}
+
+static int __glink_rpm_send(struct glink_channel *channel,
+ void *data, int len, bool wait)
+{
+ struct glink_rpm *glink = channel->glink;
+ struct {
+ struct glink_msg msg;
+ __le32 chunk_size;
+ __le32 left_size;
+ } __packed req;
+
+ if (WARN(len % 8, "RPM GLINK expects 8 byte aligned messages\n"))
+ return -EINVAL;
+
+ req.msg.cmd = cpu_to_le16(RPM_CMD_TX_DATA);
+ req.msg.param1 = cpu_to_le16(channel->lcid);
+ req.msg.param2 = cpu_to_le32(channel->rcid);
+ req.chunk_size = cpu_to_le32(len);
+ req.left_size = cpu_to_le32(0);
+
+ return glink_rpm_tx(glink, &req, sizeof(req), data, len, wait);
+}
+
+static int glink_rpm_send(struct rpmsg_endpoint *ept, void *data, int len)
+{
+ struct glink_channel *channel = to_glink_channel(ept);
+
+ return __glink_rpm_send(channel, data, len, true);
+}
+
+static int glink_rpm_trysend(struct rpmsg_endpoint *ept, void *data, int len)
+{
+ struct glink_channel *channel = to_glink_channel(ept);
+
+ return __glink_rpm_send(channel, data, len, false);
+}
+
+/*
+ * Finds the device_node for the glink child interested in this channel.
+ */
+static struct device_node *glink_rpm_match_channel(struct device_node *node,
+ const char *channel)
+{
+ struct device_node *child;
+ const char *name;
+ const char *key;
+ int ret;
+
+ for_each_available_child_of_node(node, child) {
+ key = "qcom,glink-channels";
+ ret = of_property_read_string(child, key, &name);
+ if (ret)
+ continue;
+
+ if (strcmp(name, channel) == 0)
+ return child;
+ }
+
+ return NULL;
+}
+
+static const struct rpmsg_device_ops glink_device_ops = {
+ .create_ept = glink_rpm_create_ept,
+};
+
+static const struct rpmsg_endpoint_ops glink_endpoint_ops = {
+ .destroy_ept = glink_rpm_destroy_ept,
+ .send = glink_rpm_send,
+ .trysend = glink_rpm_trysend,
+};
+
+static void glink_rpm_rpdev_release(struct device *dev)
+{
+ struct rpmsg_device *rpdev = to_rpmsg_device(dev);
+ struct glink_channel *channel = to_glink_channel(rpdev->ept);
+
+ channel->rpdev = NULL;
+ kfree(rpdev);
+}
+
+static int glink_rpm_rx_open(struct glink_rpm *glink, unsigned int rcid,
+ char *name)
+{
+ struct glink_channel *channel;
+ struct rpmsg_device *rpdev;
+ bool create_device = false;
+ int lcid;
+ int ret;
+
+ idr_for_each_entry(&glink->lcids, channel, lcid) {
+ if (!strcmp(channel->name, name))
+ break;
+ }
+
+ if (!channel) {
+ channel = glink_rpm_alloc_channel(glink, name);
+ if (IS_ERR(channel))
+ return PTR_ERR(channel);
+
+ /* The opening dance was initiated by the remote */
+ create_device = true;
+ }
+
+ mutex_lock(&glink->idr_lock);
+ ret = idr_alloc(&glink->rcids, channel, rcid, rcid + 1, GFP_KERNEL);
+ if (ret < 0) {
+ dev_err(glink->dev, "Unable to insert channel into rcid list\n");
+ mutex_unlock(&glink->idr_lock);
+ goto free_channel;
+ }
+ channel->rcid = ret;
+ mutex_unlock(&glink->idr_lock);
+
+ complete(&channel->open_req);
+
+ if (create_device) {
+ rpdev = kzalloc(sizeof(*rpdev), GFP_KERNEL);
+ if (!rpdev) {
+ ret = -ENOMEM;
+ goto rcid_remove;
+ }
+
+ rpdev->ept = &channel->ept;
+ strncpy(rpdev->id.name, name, RPMSG_NAME_SIZE);
+ rpdev->src = RPMSG_ADDR_ANY;
+ rpdev->dst = RPMSG_ADDR_ANY;
+ rpdev->ops = &glink_device_ops;
+
+ rpdev->dev.of_node = glink_rpm_match_channel(glink->dev->of_node, name);
+ rpdev->dev.parent = glink->dev;
+ rpdev->dev.release = glink_rpm_rpdev_release;
+
+ ret = rpmsg_register_device(rpdev);
+ if (ret)
+ goto free_rpdev;
+
+ channel->rpdev = rpdev;
+ }
+
+ return 0;
+
+free_rpdev:
+ kfree(rpdev);
+rcid_remove:
+ mutex_lock(&glink->idr_lock);
+ idr_remove(&glink->rcids, channel->rcid);
+ channel->rcid = 0;
+ mutex_unlock(&glink->idr_lock);
+free_channel:
+ /* Release the reference, iff we took it */
+ if (create_device)
+ kref_put(&channel->refcount, glink_rpm_channel_release);
+
+ return ret;
+}
+
+static void glink_rpm_rx_close(struct glink_rpm *glink, unsigned int rcid)
+{
+ struct rpmsg_channel_info chinfo;
+ struct glink_channel *channel;
+
+ channel = idr_find(&glink->rcids, rcid);
+ if (WARN(!channel, "close request on unknown channel\n"))
+ return;
+
+ if (channel->rpdev) {
+ strncpy(chinfo.name, channel->name, sizeof(chinfo.name));
+ chinfo.src = RPMSG_ADDR_ANY;
+ chinfo.dst = RPMSG_ADDR_ANY;
+
+ rpmsg_unregister_device(glink->dev, &chinfo);
+ }
+
+ glink_rpm_send_close_ack(glink, channel->rcid);
+
+ mutex_lock(&glink->idr_lock);
+ idr_remove(&glink->rcids, channel->rcid);
+ channel->rcid = 0;
+ mutex_unlock(&glink->idr_lock);
+
+ kref_put(&channel->refcount, glink_rpm_channel_release);
+}
+
+static void glink_rpm_rx_close_ack(struct glink_rpm *glink, unsigned int lcid)
+{
+ struct glink_channel *channel;
+
+ channel = idr_find(&glink->lcids, lcid);
+ if (WARN(!channel, "close ack on unknown channel\n"))
+ return;
+
+ mutex_lock(&glink->idr_lock);
+ idr_remove(&glink->lcids, channel->lcid);
+ channel->lcid = 0;
+ mutex_unlock(&glink->idr_lock);
+
+ kref_put(&channel->refcount, glink_rpm_channel_release);
+}
+
+static void glink_rpm_work(struct work_struct *work)
+{
+ struct glink_rpm *glink = container_of(work, struct glink_rpm, rx_work);
+ struct glink_defer_cmd *dcmd;
+ struct glink_msg *msg;
+ unsigned long flags;
+ unsigned int param1;
+ unsigned int param2;
+ unsigned int cmd;
+
+ for (;;) {
+ spin_lock_irqsave(&glink->rx_lock, flags);
+ if (list_empty(&glink->rx_queue)) {
+ spin_unlock_irqrestore(&glink->rx_lock, flags);
+ break;
+ }
+ dcmd = list_first_entry(&glink->rx_queue, struct glink_defer_cmd, node);
+ list_del(&dcmd->node);
+ spin_unlock_irqrestore(&glink->rx_lock, flags);
+
+ msg = &dcmd->msg;
+ cmd = le16_to_cpu(msg->cmd);
+ param1 = le16_to_cpu(msg->param1);
+ param2 = le32_to_cpu(msg->param2);
+
+ switch (cmd) {
+ case RPM_CMD_VERSION:
+ glink_rpm_send_version_ack(glink);
+ break;
+ case RPM_CMD_VERSION_ACK:
+ break;
+ case RPM_CMD_OPEN:
+ glink_rpm_rx_open(glink, param1, msg->data);
+ break;
+ case RPM_CMD_CLOSE:
+ glink_rpm_rx_close(glink, param1);
+ break;
+ case RPM_CMD_CLOSE_ACK:
+ glink_rpm_rx_close_ack(glink, param1);
+ break;
+ default:
+ WARN(1, "Unknown defer object %d\n", cmd);
+ break;
+ }
+
+ kfree(dcmd);
+ }
+}
+
+static int glink_rpm_parse_toc(struct device *dev,
+ void __iomem *msg_ram,
+ size_t msg_ram_size,
+ struct glink_rpm_pipe *rx,
+ struct glink_rpm_pipe *tx)
+{
+ struct rpm_toc *toc;
+ int num_entries;
+ unsigned int id;
+ size_t offset;
+ size_t size;
+ void *buf;
+ int i;
+
+ buf = kzalloc(RPM_TOC_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ __ioread32_copy(buf, msg_ram + msg_ram_size - RPM_TOC_SIZE,
+ RPM_TOC_SIZE / sizeof(u32));
+
+ toc = buf;
+
+ if (le32_to_cpu(toc->magic) != RPM_TOC_MAGIC) {
+ dev_err(dev, "RPM TOC has invalid magic\n");
+ goto err_inval;
+ }
+
+ num_entries = le32_to_cpu(toc->count);
+ if (num_entries > RPM_TOC_MAX_ENTRIES) {
+ dev_err(dev, "Invalid number of toc entries\n");
+ goto err_inval;
+ }
+
+ for (i = 0; i < num_entries; i++) {
+ id = le32_to_cpu(toc->entries[i].id);
+ offset = le32_to_cpu(toc->entries[i].offset);
+ size = le32_to_cpu(toc->entries[i].size);
+
+ if (offset > msg_ram_size || offset + size > msg_ram_size) {
+ dev_err(dev, "TOC entry with invalid size\n");
+ continue;
+ }
+
+ switch (id) {
+ case RPM_RX_FIFO_ID:
+ rx->length = size;
+
+ rx->tail = msg_ram + offset;
+ rx->head = msg_ram + offset + sizeof(u32);
+ rx->fifo = msg_ram + offset + 2 * sizeof(u32);
+ break;
+ case RPM_TX_FIFO_ID:
+ tx->length = size;
+
+ tx->tail = msg_ram + offset;
+ tx->head = msg_ram + offset + sizeof(u32);
+ tx->fifo = msg_ram + offset + 2 * sizeof(u32);
+ break;
+ }
+ }
+
+ if (!rx->fifo || !tx->fifo) {
+ dev_err(dev, "Unable to find rx and tx descriptors\n");
+ goto err_inval;
+ }
+
+ kfree(buf);
+ return 0;
+
+err_inval:
+ kfree(buf);
+ return -EINVAL;
+}
+
+static int glink_rpm_probe(struct platform_device *pdev)
+{
+ struct glink_rpm *glink;
+ struct device_node *np;
+ void __iomem *msg_ram;
+ size_t msg_ram_size;
+ struct device *dev = &pdev->dev;
+ struct resource r;
+ int irq;
+ int ret;
+
+ glink = devm_kzalloc(dev, sizeof(*glink), GFP_KERNEL);
+ if (!glink)
+ return -ENOMEM;
+
+ glink->dev = dev;
+
+ mutex_init(&glink->tx_lock);
+ spin_lock_init(&glink->rx_lock);
+ INIT_LIST_HEAD(&glink->rx_queue);
+ INIT_WORK(&glink->rx_work, glink_rpm_work);
+
+ mutex_init(&glink->idr_lock);
+ idr_init(&glink->lcids);
+ idr_init(&glink->rcids);
+
+ glink->doorbell = devm_qcom_apcs_ipc_get(&pdev->dev, NULL);
+ if (IS_ERR(glink->doorbell))
+ return PTR_ERR(glink->doorbell);
+
+ np = of_parse_phandle(dev->of_node, "qcom,rpm-msg-ram", 0);
+ ret = of_address_to_resource(np, 0, &r);
+ of_node_put(np);
+ if (ret)
+ return ret;
+
+ msg_ram = devm_ioremap(dev, r.start, resource_size(&r));
+ msg_ram_size = resource_size(&r);
+ if (!msg_ram)
+ return -ENOMEM;
+
+ ret = glink_rpm_parse_toc(dev, msg_ram, msg_ram_size,
+ &glink->rx_pipe, &glink->tx_pipe);
+ if (ret)
+ return ret;
+
+ writel(0, glink->tx_pipe.head);
+ writel(0, glink->rx_pipe.tail);
+
+ irq = platform_get_irq(pdev, 0);
+ ret = devm_request_irq(dev, irq,
+ glink_rpm_intr,
+ IRQF_NO_SUSPEND | IRQF_SHARED,
+ "glink-rpm", glink);
+ if (ret) {
+ dev_err(dev, "Failed to request IRQ\n");
+ return ret;
+ }
+
+ glink->irq = irq;
+
+ ret = glink_rpm_send_version(glink);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, glink);
+
+ return 0;
+}
+
+static int glink_rpm_remove_device(struct device *dev, void *data)
+{
+ device_unregister(dev);
+
+ return 0;
+}
+
+static int glink_rpm_remove(struct platform_device *pdev)
+{
+ struct glink_rpm *glink = platform_get_drvdata(pdev);
+ struct glink_channel *channel;
+ int cid;
+ int ret;
+
+ disable_irq(glink->irq);
+ cancel_work_sync(&glink->rx_work);
+
+ ret = device_for_each_child(glink->dev, NULL, glink_rpm_remove_device);
+ if (ret)
+ dev_warn(glink->dev, "Can't remove GLINK devices: %d\n", ret);
+
+ /* Release any defunct local channels, waiting for close-ack */
+ idr_for_each_entry(&glink->lcids, channel, cid)
+ kref_put(&channel->refcount, glink_rpm_channel_release);
+
+ idr_destroy(&glink->lcids);
+ idr_destroy(&glink->rcids);
+
+ return 0;
+}
+
+static const struct of_device_id glink_rpm_of_match[] = {
+ { .compatible = "qcom,glink-rpm" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, glink_rpm_of_match);
+
+static struct platform_driver glink_rpm_driver = {
+ .probe = glink_rpm_probe,
+ .remove = glink_rpm_remove,
+ .driver = {
+ .name = "qcom_glink_rpm",
+ .of_match_table = glink_rpm_of_match,
+ },
+};
+
+static int __init glink_rpm_init(void)
+{
+ return platform_driver_register(&glink_rpm_driver);
+}
+subsys_initcall(glink_rpm_init);
+
+static void __exit glink_rpm_exit(void)
+{
+ platform_driver_unregister(&glink_rpm_driver);
+}
+module_exit(glink_rpm_exit);
+
+MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@linaro.org>");
+MODULE_DESCRIPTION("Qualcomm GLINK RPM driver");
+MODULE_LICENSE("GPL v2");
--
2.12.0
^ permalink raw reply related
* [PATCH v3 3/4] soc: qcom: Add device tree binding for GLINK RPM
From: Bjorn Andersson @ 2017-05-03 5:29 UTC (permalink / raw)
To: Andy Gross, Rob Herring, Mark Rutland, Ohad Ben-Cohen
Cc: linux-arm-msm, linux-soc, devicetree, linux-kernel,
linux-remoteproc
In-Reply-To: <20170503052929.17422-1-bjorn.andersson@linaro.org>
Add device tree binding documentation for the Qualcomm GLINK RPM, used
for communication with the Resource Power Management subsystem in
various Qualcomm SoCs.
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
---
Changes since v2:
- Replace qcom,ipc syscon with a "doorbell"
Changes since v1:
- None
.../devicetree/bindings/soc/qcom/qcom,glink.txt | 73 ++++++++++++++++++++++
1 file changed, 73 insertions(+)
create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,glink.txt
diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,glink.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,glink.txt
new file mode 100644
index 000000000000..4c8983f0dcb0
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/qcom/qcom,glink.txt
@@ -0,0 +1,73 @@
+Qualcomm RPM GLINK binding
+
+This binding describes the Qualcomm RPM GLINK, a fifo based mechanism for
+communication with the Resource Power Management system on various Qualcomm
+platforms.
+
+- compatible:
+ Usage: required
+ Value type: <stringlist>
+ Definition: must be "qcom,glink-rpm"
+
+- interrupts:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: should specify the IRQ used by the remote processor to
+ signal this processor about communication related events
+
+- qcom,rpm-msg-ram:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: handle to RPM message memory resource
+
+- doorbells:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: reference to the "rpm_hlos" doorbell in APCS, as described
+ in doorbell.txt
+
+= GLINK DEVICES
+Each subnode of the GLINK node represent function tied to a virtual
+communication channel. The name of the nodes are not important. The properties
+of these nodes are defined by the individual bindings for the specific function
+- but must contain the following property:
+
+- qcom,glink-channels:
+ Usage: required
+ Value type: <stringlist>
+ Definition: a list of channels tied to this function, used for matching
+ the function to a set of virtual channels
+
+= EXAMPLE
+The following example represents the GLINK RPM node on a MSM8996 device, with
+the function for the "rpm_request" channel defined, which is used for
+regualtors and root clocks.
+
+ apcs_glb: apcs-glb@9820000 {
+ compatible = "qcom,msm8996-apcs-hmss-global";
+ reg = <0x9820000 0x1000>;
+
+ #doorbell-cells = <1>;
+ };
+
+ rpm_msg_ram: memory@68000 {
+ compatible = "qcom,rpm-msg-ram";
+ reg = <0x68000 0x6000>;
+ };
+
+ rpm-glink {
+ compatible = "qcom,glink-rpm";
+
+ interrupts = <GIC_SPI 168 IRQ_TYPE_EDGE_RISING>;
+
+ qcom,rpm-msg-ram = <&rpm_msg_ram>;
+
+ doorbells = <&apcs_glb 0>;
+
+ rpm-requests {
+ compatible = "qcom,rpm-msm8996";
+ qcom,glink-channels = "rpm_requests";
+
+ ...
+ };
+ };
--
2.12.0
^ permalink raw reply related
* [PATCH v3 2/4] soc: qcom: Introduce APCS IPC driver
From: Bjorn Andersson @ 2017-05-03 5:29 UTC (permalink / raw)
To: Andy Gross, Rob Herring, Mark Rutland, Ohad Ben-Cohen
Cc: linux-arm-msm, linux-soc, devicetree, linux-kernel,
linux-remoteproc
In-Reply-To: <20170503052929.17422-1-bjorn.andersson@linaro.org>
This implements a driver that exposes the IPC bits found in the APCS
Global block in various Qualcomm platforms. The bits are used to signal
inter-processor communication signals from the application CPU to other
masters.
The driver implements the "doorbell" binding and could be used as basis
for a new Linux framework, if found useful outside Qualcomm.
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
---
Changes since v2:
- New driver
drivers/soc/qcom/Kconfig | 8 ++
drivers/soc/qcom/Makefile | 1 +
drivers/soc/qcom/apcs-ipc.c | 182 ++++++++++++++++++++++++++++++++++++++
include/linux/soc/qcom/apcs_ipc.h | 26 ++++++
4 files changed, 217 insertions(+)
create mode 100644 drivers/soc/qcom/apcs-ipc.c
create mode 100644 include/linux/soc/qcom/apcs_ipc.h
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 78b1bb7bcf20..4113da81d18b 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -1,6 +1,14 @@
#
# QCOM Soc drivers
#
+config QCOM_APCS_IPC
+ tristate "Qualcomm APCS IPC driver"
+ depends on ARCH_QCOM
+ help
+ Say y here to enable support for the APCS IPC doorbell driver,
+ providing an interface for invoking the inter-process communication
+ signals from the application processor to other masters.
+
config QCOM_GSBI
tristate "QCOM General Serial Bus Interface"
depends on ARCH_QCOM
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 1f30260b06b8..e15b33e5a630 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -1,3 +1,4 @@
+obj-$(CONFIG_QCOM_APCS_IPC) += apcs-ipc.o
obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o
obj-$(CONFIG_QCOM_MDT_LOADER) += mdt_loader.o
obj-$(CONFIG_QCOM_PM) += spm.o
diff --git a/drivers/soc/qcom/apcs-ipc.c b/drivers/soc/qcom/apcs-ipc.c
new file mode 100644
index 000000000000..ea835cb08657
--- /dev/null
+++ b/drivers/soc/qcom/apcs-ipc.c
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2017, Linaro Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+
+static struct platform_driver qcom_apcs_ipc_driver;
+
+struct qcom_apcs_ipc {
+ struct device *dev;
+
+ void __iomem *base;
+ unsigned long offset;
+};
+
+struct qcom_apcs_ipc_bell {
+ struct qcom_apcs_ipc *apcs;
+ unsigned int bit;
+};
+
+static void qcom_apcs_ipc_release(struct device *dev, void *res)
+{
+ struct qcom_apcs_ipc_bell *bell = res;
+ struct qcom_apcs_ipc *apcs = bell->apcs;
+
+ put_device(apcs->dev);
+}
+
+/**
+ * qcom_apcs_ipc_get() - acquire a handle to a doorbell
+ * @dev: client device handle
+ * @id: identifier of the doorbell
+ *
+ * Returns a doorbell reference, or negative errno on failure.
+ */
+struct qcom_apcs_ipc_bell *devm_qcom_apcs_ipc_get(struct device *dev,
+ const char *id)
+{
+ struct qcom_apcs_ipc_bell *bell;
+ struct platform_device *pdev;
+ struct of_phandle_args args;
+ int index = 0;
+ int ret;
+
+ if (id) {
+ index = of_property_match_string(dev->of_node,
+ "doorbell-names", id);
+ if (index < 0)
+ return ERR_PTR(index);
+ }
+
+ ret = of_parse_phandle_with_args(dev->of_node, "doorbells",
+ "#doorbell-cells", index, &args);
+ if (ret) {
+ dev_err(dev, "unable to resolve doorbell\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ pdev = of_find_device_by_node(args.np);
+ of_node_put(args.np);
+
+ if (!pdev)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ if (args.args[0] >= 32) {
+ dev_err(dev, "invalid doorbell requested\n");
+ ret = -EINVAL;
+ goto release_device;
+ }
+
+ if (pdev->dev.driver != &qcom_apcs_ipc_driver.driver) {
+ dev_err(dev, "failed to acquire apcs ipc driver\n");
+ ret = -EINVAL;
+ goto release_device;
+ }
+
+ bell = devres_alloc(qcom_apcs_ipc_release, sizeof(*bell), GFP_KERNEL);
+ if (!bell) {
+ ret = -ENOMEM;
+ goto release_device;
+ }
+
+ bell->apcs = platform_get_drvdata(pdev);
+ bell->bit = args.args[0];
+
+ devres_add(dev, bell);
+
+ return bell;
+
+release_device:
+ put_device(&pdev->dev);
+
+ return ERR_PTR(ret);
+
+}
+EXPORT_SYMBOL_GPL(devm_qcom_apcs_ipc_get);
+
+/**
+ * qcom_apcs_ipc_ring() - ring the doorbell
+ * @bell: doorbell to ring
+ */
+void qcom_apcs_ipc_ring(struct qcom_apcs_ipc_bell *bell)
+{
+ struct qcom_apcs_ipc *apcs = bell->apcs;
+
+ writel(BIT(bell->bit), apcs->base + apcs->offset);
+}
+EXPORT_SYMBOL_GPL(qcom_apcs_ipc_ring);
+
+static int qcom_apcs_ipc_probe(struct platform_device *pdev)
+{
+ struct qcom_apcs_ipc *apcs;
+ struct resource *res;
+
+ apcs = devm_kzalloc(&pdev->dev, sizeof(*apcs), GFP_KERNEL);
+ if (!apcs)
+ return -ENOMEM;
+
+ apcs->dev = &pdev->dev;
+ apcs->offset = (unsigned long)of_device_get_match_data(&pdev->dev);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ apcs->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(apcs->base))
+ return PTR_ERR(apcs->base);
+
+ platform_set_drvdata(pdev, apcs);
+
+ return 0;
+}
+
+static int qcom_apcs_ipc_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+/* .data is the offset of the ipc register within the global block */
+static const struct of_device_id qcom_apcs_ipc_of_match[] = {
+ { .compatible = "qcom,msm8916-apcs-kpss-global", .data = (void *)8 },
+ { .compatible = "qcom,msm8996-apcs-hmss-global", .data = (void *)16 },
+ {}
+};
+MODULE_DEVICE_TABLE(of, qcom_apcs_ipc_of_match);
+
+static struct platform_driver qcom_apcs_ipc_driver = {
+ .probe = qcom_apcs_ipc_probe,
+ .remove = qcom_apcs_ipc_remove,
+ .driver = {
+ .name = "qcom_apcs_ipc",
+ .of_match_table = qcom_apcs_ipc_of_match,
+ },
+};
+
+static int __init qcom_apcs_ipc_init(void)
+{
+ return platform_driver_register(&qcom_apcs_ipc_driver);
+}
+postcore_initcall(qcom_apcs_ipc_init);
+
+static void __exit qcom_apcs_ipc_exit(void)
+{
+ platform_driver_unregister(&qcom_apcs_ipc_driver);
+}
+module_exit(qcom_apcs_ipc_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Qualcomm APCS IPC driver");
diff --git a/include/linux/soc/qcom/apcs_ipc.h b/include/linux/soc/qcom/apcs_ipc.h
new file mode 100644
index 000000000000..72be77555261
--- /dev/null
+++ b/include/linux/soc/qcom/apcs_ipc.h
@@ -0,0 +1,26 @@
+#ifndef __QCOM_APCS_IPC_H__
+#define __QCOM_APCS_IPC_H__
+
+#include <linux/err.h>
+
+struct device;
+struct qcom_apcs_ipc_bell;
+
+#if IS_ENABLED(CONFIG_QCOM_APCS_IPC)
+
+struct qcom_apcs_ipc_bell *devm_qcom_apcs_ipc_get(struct device *dev,
+ const char *id);
+void qcom_apcs_ipc_ring(struct qcom_apcs_ipc_bell *bell);
+
+#else
+
+static inline struct qcom_apcs_ipc_bell *devm_qcom_apcs_ipc_get(struct device *dev,
+ const char *id)
+{
+ return ERR_PTR(-EINVAL);
+}
+
+static inline void qcom_apcs_ipc_ring(struct qcom_apcs_ipc_bell *bell) {}
+
+#endif
+#endif
--
2.12.0
^ permalink raw reply related
* [PATCH v3 1/4] dt-bindings: Introduce doorbell binding
From: Bjorn Andersson @ 2017-05-03 5:29 UTC (permalink / raw)
To: Andy Gross, Rob Herring, Mark Rutland, Ohad Ben-Cohen
Cc: linux-arm-msm, linux-soc, devicetree, linux-kernel,
linux-remoteproc
Introduce the generic doorbell binding as well as a binding for the
Qualcomm APCS Global block. This is used to expose doorbell-like devices
in the system.
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
---
Changes since v2:
- New binding
.../devicetree/bindings/doorbell/doorbell.txt | 31 +++++++++++++++
.../bindings/doorbell/qcom,apcs-kpss-global.txt | 45 ++++++++++++++++++++++
2 files changed, 76 insertions(+)
create mode 100644 Documentation/devicetree/bindings/doorbell/doorbell.txt
create mode 100644 Documentation/devicetree/bindings/doorbell/qcom,apcs-kpss-global.txt
diff --git a/Documentation/devicetree/bindings/doorbell/doorbell.txt b/Documentation/devicetree/bindings/doorbell/doorbell.txt
new file mode 100644
index 000000000000..8fd814898c3f
--- /dev/null
+++ b/Documentation/devicetree/bindings/doorbell/doorbell.txt
@@ -0,0 +1,31 @@
+Doorbell binding
+============================================
+
+The doorbell binding is used to describe a set of doorbells for client blocks
+to ring.
+
+1) Doorbell controller
+----------------------
+
+A doorbell controller is a device that exposes a number of doorbells, that can
+client devices can ring to signal some event to some piece of hardware.
+
+- #doorbell-cells:
+ Usage: required
+ Value type: <u32>
+ Definition: should be 0 for single-doorbell controllers and 1 for
+ multi-doorbell controllers
+
+2) Doorbell user
+----------------
+
+- doorbells:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: list of doorbell references
+
+- doorbell-names:
+ Usage: optional
+ Value type: <stringlist>
+ Definition: list of strings identifying each entry in the doorbells
+ property
diff --git a/Documentation/devicetree/bindings/doorbell/qcom,apcs-kpss-global.txt b/Documentation/devicetree/bindings/doorbell/qcom,apcs-kpss-global.txt
new file mode 100644
index 000000000000..6320e1a355cb
--- /dev/null
+++ b/Documentation/devicetree/bindings/doorbell/qcom,apcs-kpss-global.txt
@@ -0,0 +1,45 @@
+Binding for the Qualcomm APCS global block
+==========================================
+
+This binding describes the APCS "global" block found in various Qualcomm
+platforms.
+
+- compatible:
+ Usage: required
+ Value type: <string>
+ Definition: must be one of:
+ "qcom,msm8916-apcs-kpss-global",
+ "qcom,msm8996-apcs-hmss-global"
+
+- reg:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: must specify the base address and size of the global block
+
+- #doorbell-cells:
+ Usage: required
+ Value type: <u32>
+ Definition: as described in doorbell.txt, must be 1
+
+
+= EXAMPLE
+The following example describes the APCS HMSS found in MSM8996 and part of the
+GLINK RPM referencing the "rpm_hlos" doorbell therein.
+
+ apcs_glb: apcs-glb@9820000 {
+ compatible = "qcom,msm8996-apcs-hmss-global";
+ reg = <0x9820000 0x1000>;
+
+ #doorbell-cells = <1>;
+ };
+
+ rpm-glink {
+ compatible = "qcom,glink-rpm";
+
+ interrupts = <GIC_SPI 168 IRQ_TYPE_EDGE_RISING>;
+
+ qcom,rpm-msg-ram = <&rpm_msg_ram>;
+
+ doorbells = <&apcs_glb 0>;
+ };
+
--
2.12.0
^ permalink raw reply related
* Re: [PATCH v6 0/4] Broadcom SBA RAID support
From: Anup Patel @ 2017-05-03 5:09 UTC (permalink / raw)
To: Vinod Koul
Cc: Jassi Brar, Rob Herring, Mark Rutland, Herbert Xu,
David S . Miller, Dan Williams, Ray Jui, Scott Branden, Jon Mason,
Rob Rice, BCM Kernel Feedback, dmaengine, Device Tree,
Linux ARM Kernel, Linux Kernel, linux-crypto, linux-raid
In-Reply-To: <20170503045926.GV6263@localhost>
On Wed, May 3, 2017 at 10:29 AM, Vinod Koul <vinod.koul@intel.com> wrote:
> On Wed, May 03, 2017 at 09:15:20AM +0530, Anup Patel wrote:
>> Hi Vinod,
>>
>> The Broadcom FlexRM patchset have been
>> merged in v4.11.
>>
>> I think you now can take this patchset in next
>> merge window. Right??
>
> Sure, please rebase and resend after -rc1 is out
Sure, I will do that.
Regards,
Anup
^ permalink raw reply
* Re: [PATCH 3/3] PCI/of fix of_dma_get_range; get PCI specific dma-ranges
From: Oza Oza via iommu @ 2017-05-03 5:07 UTC (permalink / raw)
To: Joerg Roedel, Robin Murphy
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, Oza Pawandeep,
linux-pci-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA, Linux IOMMU,
BCM Kernel Feedback,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1493786795-28153-3-git-send-email-oza.oza-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
I will send v2 after removing GERRIT details from
commit message. My apologies for the noise.
Regards,
Oza
^ permalink raw reply
* Re: [PATCH 2/3] iommu/pci: reserve iova for PCI masters
From: Oza Oza via iommu @ 2017-05-03 5:07 UTC (permalink / raw)
To: Joerg Roedel, Robin Murphy
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, Oza Pawandeep,
linux-pci-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA, Linux IOMMU,
BCM Kernel Feedback,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1493786795-28153-2-git-send-email-oza.oza-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
I will send v2 after removing GERRIT details from
commit message. My apologies for the noise.
Regards,
Oza
^ permalink raw reply
* Re: [PATCH 1/3] of/pci/dma: fix DMA configuration for PCI masters
From: Oza Oza via iommu @ 2017-05-03 5:06 UTC (permalink / raw)
To: Joerg Roedel, Robin Murphy
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, Oza Pawandeep,
linux-pci-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA, Linux IOMMU,
BCM Kernel Feedback,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1493786795-28153-1-git-send-email-oza.oza-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
I will send v2 after removing GERRIT details from
commit message. My apologies for the noise.
Regards,
Oza
^ permalink raw reply
* Re: [PATCH v6 0/4] Broadcom SBA RAID support
From: Vinod Koul @ 2017-05-03 4:59 UTC (permalink / raw)
To: Anup Patel
Cc: Jassi Brar, Rob Herring, Mark Rutland, Herbert Xu,
David S . Miller, Dan Williams, Ray Jui, Scott Branden, Jon Mason,
Rob Rice, BCM Kernel Feedback, dmaengine, Device Tree,
Linux ARM Kernel, Linux Kernel, linux-crypto, linux-raid
In-Reply-To: <CAALAos8hVtCqzpv3nvPAGnm=WKMfcXeBEFm5Yb0gWwYGLE4SeA@mail.gmail.com>
On Wed, May 03, 2017 at 09:15:20AM +0530, Anup Patel wrote:
> Hi Vinod,
>
> The Broadcom FlexRM patchset have been
> merged in v4.11.
>
> I think you now can take this patchset in next
> merge window. Right??
Sure, please rebase and resend after -rc1 is out
--
~Vinod
^ permalink raw reply
* Re: [RFC PATH] of/pci/dma: fix DMA configruation for PCI masters
From: Oza Oza @ 2017-05-03 4:51 UTC (permalink / raw)
To: Rob Herring
Cc: Joerg Roedel, Robin Murphy, devicetree@vger.kernel.org,
Oza Pawandeep, linux-pci@vger.kernel.org,
linux-kernel@vger.kernel.org, Linux IOMMU,
bcm-kernel-feedback-list@broadcom.com,
linux-arm-kernel@lists.infradead.org
In-Reply-To: <CAL_JsqKg9YuDd21eaoDXKfLUqyD05nWz7=SqxTKAL-Dcpp6MZw@mail.gmail.com>
On Mon, Apr 24, 2017 at 7:50 PM, Rob Herring <robh@kernel.org> wrote:
> On Sat, Apr 22, 2017 at 3:08 AM, Oza Pawandeep <oza.oza@broadcom.com> wrote:
>> current device frmework and of framework integration assumes dma-ranges
>> in a way where memory-mapped devices define their dma-ranges.
>> dma-ranges: (child-bus-address, parent-bus-address, length).
>>
>> but iproc based SOCs and other SOCs(suc as rcar) have PCI world dma-ranges.
>> dma-ranges = <0x43000000 0x00 0x00 0x00 0x00 0x80 0x00>;
>>
>> of_dma_configure is specifically witten to take care of memory mapped devices.
>> but no implementation exists for pci to take care of pcie based memory ranges.
>> in fact pci world doesnt seem to define standard dma-ranges
>>
>> this patch served following purposes
>>
>> 1) exposes intrface to the pci host driver for thir inbound memory ranges
>>
>> 2) provide an interface to callers such as of_dma_get_ranges.
>> so then the returned size get best possible (largest) dma_mask.
>> for e.g.
>> dma-ranges = <0x43000000 0x00 0x00 0x00 0x00 0x80 0x00>;
>> we should get dev->coherent_dma_mask=0x7fffffffff.
>>
>> 3) this patch handles multiple inbound windows and dma-ranges.
>> it is left to the caller, how it wants to use them.
>> the new function returns the resources in a standard and unform way
>>
>> 4) this way the callers of of_dma_get_ranges does not need to change.
>> and
>>
>> 5) leaves scope of adding PCI flag handling for inbound memory
>> by the new function.
>>
>> Signed-off-by: Oza Pawandeep <oza.oza@broadcom.com>
>>
>> diff --git a/drivers/of/address.c b/drivers/of/address.c
>> index 02b2903..ec21191 100644
>> --- a/drivers/of/address.c
>> +++ b/drivers/of/address.c
>> @@ -6,6 +6,7 @@
>> #include <linux/ioport.h>
>> #include <linux/module.h>
>> #include <linux/of_address.h>
>> +#include <linux/of_pci.h>
>> #include <linux/pci.h>
>> #include <linux/pci_regs.h>
>> #include <linux/sizes.h>
>> @@ -829,10 +830,30 @@ int of_dma_get_range(struct device_node *np, u64 *dma_addr, u64 *paddr, u64 *siz
>> int len, naddr, nsize, pna;
>> int ret = 0;
>> u64 dmaaddr;
>> + struct resource_entry *window;
>> + LIST_HEAD(res);
>>
>> if (!node)
>> return -EINVAL;
>>
>> + if (strcmp(np->name, "pci")) {
>
> Using the name is not reliable though I did recently add a dtc check
> for this. Of course, 'pcie' is valid too (and probably should be used
> for what you are testing). type is what you want to use here. We
> already have bus matching function and bus specific handlers in
> address.c. Whatever solution you come up with should be integrated
> with the existing bus specific handlers.
>
> Rob
Hi Rob,
I have addressed your comments.
now I have pushed 3 patchsets, which completely solves the problem for our SOC.
[PATCH 1/3] of/pci/dma: fix DMA configuration for PCI masters.
Regards,
Oza.
^ permalink raw reply
* [PATCH 3/3] PCI/of fix of_dma_get_range; get PCI specific dma-ranges
From: Oza Pawandeep via iommu @ 2017-05-03 4:46 UTC (permalink / raw)
To: Joerg Roedel, Robin Murphy
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, Oza Pawandeep,
linux-pci-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
iommu-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA,
bcm-kernel-feedback-list-dY08KVG/lbpWk0Htik3J/w,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1493786795-28153-1-git-send-email-oza.oza-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
current device framework and of framework integration assumes
dma-ranges in a way where memory-mapped devices define their
dma-ranges. (child-bus-address, parent-bus-address, length).
of_dma_configure is specifically written to take care of memory
mapped devices. but no implementation exists for pci to take
care of pcie based memory ranges.
for e.g. iproc based SOCs and other SOCs(suc as rcar) have PCI
world dma-ranges.
dma-ranges = <0x43000000 0x00 0x00 0x00 0x00 0x80 0x00>;
this patch fixes this patch fixes the bug in of_dma_get_range,
which with as is, parses the PCI memory ranges and return wrong
size as 0.
in order to get largest possible dma_mask. this patch also
retuns the largest possible size based on dma-ranges,
for e.g.
dma-ranges = <0x43000000 0x00 0x00 0x00 0x00 0x80 0x00>;
we should get dev->coherent_dma_mask=0x7fffffffff.
based on which iova allocation space will honour PCI host
bridge limitations.
Bug: SOC-5216
Change-Id: I4c534bdd17e70c6b27327d39d1656e8ed0cf56d6
Signed-off-by: Oza Pawandeep <oza.oza-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
Reviewed-on: http://gerrit-ccxsw.broadcom.net/40762
Reviewed-by: vpx_checkpatch status <vpx_checkpatch-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
Reviewed-by: CCXSW <ccxswbuild-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
Reviewed-by: Scott Branden <scott.branden-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
Tested-by: vpx_autobuild status <vpx_autobuild-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
Tested-by: vpx_smoketest status <vpx_smoketest-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
diff --git a/drivers/of/address.c b/drivers/of/address.c
index 02b2903..f7734fc 100644
--- a/drivers/of/address.c
+++ b/drivers/of/address.c
@@ -6,6 +6,7 @@
#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/of_address.h>
+#include <linux/of_pci.h>
#include <linux/pci.h>
#include <linux/pci_regs.h>
#include <linux/sizes.h>
@@ -830,6 +831,54 @@ int of_dma_get_range(struct device_node *np, u64 *dma_addr, u64 *paddr, u64 *siz
int ret = 0;
u64 dmaaddr;
+#ifdef CONFIG_PCI
+ struct resource_entry *window;
+ LIST_HEAD(res);
+
+ if (!node)
+ return -EINVAL;
+
+ if (of_bus_pci_match(np)) {
+ *size = 0;
+ /*
+ * PCI dma-ranges is not mandatory property.
+ * many devices do no need to have it, since
+ * host bridge does not require inbound memory
+ * configuration or rather have design limitations.
+ * so we look for dma-ranges, if missing we
+ * just return the caller full size, and also
+ * no dma-ranges suggests that, host bridge allows
+ * whatever comes in, so we set dma_addr to 0.
+ */
+ ret = of_pci_get_dma_ranges(np, &res);
+ if (!ret) {
+ resource_list_for_each_entry(window, &res) {
+ struct resource *res_dma = window->res;
+
+ if (*size < resource_size(res_dma)) {
+ *dma_addr = res_dma->start - window->offset;
+ *paddr = res_dma->start;
+ *size = resource_size(res_dma);
+ }
+ }
+ }
+ pci_free_resource_list(&res);
+
+ /* ignore the empty ranges. */
+ if (*size == 0) {
+ pr_debug("empty/zero size dma-ranges found for node(%s)\n",
+ np->full_name);
+ *size = DMA_BIT_MASK(sizeof(dma_addr_t) * 8);
+ *dma_addr = *paddr = 0;
+ ret = 0;
+ }
+
+ pr_err("dma_addr(%llx) cpu_addr(%llx) size(%llx)\n",
+ *dma_addr, *paddr, *size);
+ goto out;
+ }
+#endif
+
if (!node)
return -EINVAL;
--
1.9.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox