Devicetree
 help / color / mirror / Atom feed
* Re: [PATCH v6 2/8] MFD: add STM32 Timers driver
From: Jonathan Cameron @ 2016-12-30 20:38 UTC (permalink / raw)
  To: Benjamin Gaignard, lee.jones, robh+dt, mark.rutland,
	alexandre.torgue, devicetree, linux-kernel, thierry.reding,
	linux-pwm, knaack.h, lars, pmeerw, linux-iio, linux-arm-kernel
  Cc: fabrice.gasnier, gerald.baeza, arnaud.pouliquen, linus.walleij,
	linaro-kernel, Benjamin Gaignard
In-Reply-To: <1481292919-26587-3-git-send-email-benjamin.gaignard@st.com>

On 09/12/16 14:15, Benjamin Gaignard wrote:
> This hardware block could at used at same time for PWM generation
> and IIO timers.
> PWM and IIO timer configuration are mixed in the same registers
> so we need a multi fonction driver to be able to share those registers.
fonction -> function
> 
> version 6:
> - rename files to stm32-timers
> - rename functions to stm32_timers_xxx
> 
> version 5:
> - fix Lee comments about detect function
> - add missing dependency on REGMAP_MMIO
> 
> version 4:
> - add a function to detect Auto Reload Register (ARR) size
> - rename the structure shared with other drivers
> 
> version 2:
> - rename driver "stm32-gptimer" to be align with SoC documentation
> - only keep one compatible
> - use of_platform_populate() instead of devm_mfd_add_devices()
> 
> Signed-off-by: Benjamin Gaignard <benjamin.gaignard@st.com>
> ---
>  drivers/mfd/Kconfig              | 11 ++++++
>  drivers/mfd/Makefile             |  2 +
>  drivers/mfd/stm32-timers.c       | 80 ++++++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/stm32-timers.h | 71 +++++++++++++++++++++++++++++++++++
>  4 files changed, 164 insertions(+)
>  create mode 100644 drivers/mfd/stm32-timers.c
>  create mode 100644 include/linux/mfd/stm32-timers.h
> 
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index c6df644..4ec1906 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -1607,6 +1607,17 @@ config MFD_STW481X
>  	  in various ST Microelectronics and ST-Ericsson embedded
>  	  Nomadik series.
>  
> +config MFD_STM32_TIMERS
> +	tristate "Support for STM32 Timers"
> +	depends on (ARCH_STM32 && OF) || COMPILE_TEST
> +	select MFD_CORE
> +	select REGMAP
> +	select REGMAP_MMIO
> +	help
> +	  Select this option to enable STM32 timers driver used
> +	  for PWM and IIO Timer. This driver allow to share the
> +	  registers between the others drivers.
> +
>  menu "Multimedia Capabilities Port drivers"
>  	depends on ARCH_SA1100
>  
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 9834e66..11a52f8 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -211,3 +211,5 @@ obj-$(CONFIG_INTEL_SOC_PMIC)	+= intel-soc-pmic.o
>  obj-$(CONFIG_MFD_MT6397)	+= mt6397-core.o
>  
>  obj-$(CONFIG_MFD_ALTERA_A10SR)	+= altera-a10sr.o
> +
> +obj-$(CONFIG_MFD_STM32_TIMERS) 	+= stm32-timers.o
> diff --git a/drivers/mfd/stm32-timers.c b/drivers/mfd/stm32-timers.c
> new file mode 100644
> index 0000000..68d115e
> --- /dev/null
> +++ b/drivers/mfd/stm32-timers.c
> @@ -0,0 +1,80 @@
> +/*
> + * Copyright (C) STMicroelectronics 2016
> + *
> + * Author: Benjamin Gaignard <benjamin.gaignard@st.com>
> + *
> + * License terms:  GNU General Public License (GPL), version 2
> + */
> +
> +#include <linux/mfd/stm32-timers.h>
> +#include <linux/module.h>
> +#include <linux/of_platform.h>
> +#include <linux/reset.h>
> +
> +static const struct regmap_config stm32_timers_regmap_cfg = {
> +	.reg_bits = 32,
> +	.val_bits = 32,
> +	.reg_stride = sizeof(u32),
> +	.max_register = 0x400,
> +};
> +
> +static void stm32_timers_get_arr_size(struct stm32_timers *ddata)
> +{
> +	/*
> +	 * Only the available bits will be written so when readback
> +	 * we get the maximum value of auto reload register
> +	 */
> +	regmap_write(ddata->regmap, TIM_ARR, ~0L);
> +	regmap_read(ddata->regmap, TIM_ARR, &ddata->max_arr);
> +	regmap_write(ddata->regmap, TIM_ARR, 0x0);
> +}
> +
> +static int stm32_timers_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct stm32_timers *ddata;
> +	struct resource *res;
> +	void __iomem *mmio;
> +
> +	ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
> +	if (!ddata)
> +		return -ENOMEM;
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	mmio = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(mmio))
> +		return PTR_ERR(mmio);
> +
> +	ddata->regmap = devm_regmap_init_mmio_clk(dev, "clk_int", mmio,
> +						  &stm32_timers_regmap_cfg);
> +	if (IS_ERR(ddata->regmap))
> +		return PTR_ERR(ddata->regmap);
> +
> +	ddata->clk = devm_clk_get(dev, NULL);
> +	if (IS_ERR(ddata->clk))
> +		return PTR_ERR(ddata->clk);
> +
> +	stm32_timers_get_arr_size(ddata);
> +
> +	platform_set_drvdata(pdev, ddata);
> +
> +	return of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
> +}
> +
> +static const struct of_device_id stm32_timers_of_match[] = {
> +	{ .compatible = "st,stm32-timers", },
> +	{ /* end node */ },
> +};
> +MODULE_DEVICE_TABLE(of, stm32_timers_of_match);
> +
> +static struct platform_driver stm32_timers_driver = {
> +	.probe = stm32_timers_probe,
> +	.driver	= {
> +		.name = "stm32-timers",
> +		.of_match_table = stm32_timers_of_match,
> +	},
> +};
> +module_platform_driver(stm32_timers_driver);
> +
> +MODULE_DESCRIPTION("STMicroelectronics STM32 Timers");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/mfd/stm32-timers.h b/include/linux/mfd/stm32-timers.h
> new file mode 100644
> index 0000000..d030004
> --- /dev/null
> +++ b/include/linux/mfd/stm32-timers.h
> @@ -0,0 +1,71 @@
> +/*
> + * Copyright (C) STMicroelectronics 2016
> + *
> + * Author: Benjamin Gaignard <benjamin.gaignard@st.com>
> + *
> + * License terms:  GNU General Public License (GPL), version 2
> + */
> +
> +#ifndef _LINUX_STM32_GPTIMER_H_
> +#define _LINUX_STM32_GPTIMER_H_
> +
> +#include <linux/clk.h>
> +#include <linux/regmap.h>
> +
> +#define TIM_CR1		0x00	/* Control Register 1      */
> +#define TIM_CR2		0x04	/* Control Register 2      */
> +#define TIM_SMCR	0x08	/* Slave mode control reg  */
> +#define TIM_DIER	0x0C	/* DMA/interrupt register  */
> +#define TIM_SR		0x10	/* Status register	   */
> +#define TIM_EGR		0x14	/* Event Generation Reg    */
> +#define TIM_CCMR1	0x18	/* Capt/Comp 1 Mode Reg    */
> +#define TIM_CCMR2	0x1C	/* Capt/Comp 2 Mode Reg    */
> +#define TIM_CCER	0x20	/* Capt/Comp Enable Reg    */
> +#define TIM_PSC		0x28	/* Prescaler               */
> +#define TIM_ARR		0x2c	/* Auto-Reload Register    */
> +#define TIM_CCR1	0x34	/* Capt/Comp Register 1    */
> +#define TIM_CCR2	0x38	/* Capt/Comp Register 2    */
> +#define TIM_CCR3	0x3C	/* Capt/Comp Register 3    */
> +#define TIM_CCR4	0x40	/* Capt/Comp Register 4    */
> +#define TIM_BDTR	0x44	/* Break and Dead-Time Reg */
> +
> +#define TIM_CR1_CEN	BIT(0)	/* Counter Enable	   */
> +#define TIM_CR1_ARPE	BIT(7)	/* Auto-reload Preload Ena */
> +#define TIM_CR2_MMS	(BIT(4) | BIT(5) | BIT(6)) /* Master mode selection */
> +#define TIM_SMCR_SMS	(BIT(0) | BIT(1) | BIT(2)) /* Slave mode selection */
> +#define TIM_SMCR_TS	(BIT(4) | BIT(5) | BIT(6)) /* Trigger selection */
> +#define TIM_DIER_UIE	BIT(0)	/* Update interrupt	   */
> +#define TIM_SR_UIF	BIT(0)	/* Update interrupt flag   */
> +#define TIM_EGR_UG	BIT(0)	/* Update Generation       */
> +#define TIM_CCMR_PE	BIT(3)	/* Channel Preload Enable  */
> +#define TIM_CCMR_M1	(BIT(6) | BIT(5))  /* Channel PWM Mode 1 */
> +#define TIM_CCER_CC1E	BIT(0)	/* Capt/Comp 1  out Ena    */
> +#define TIM_CCER_CC1P	BIT(1)	/* Capt/Comp 1  Polarity   */
> +#define TIM_CCER_CC1NE	BIT(2)	/* Capt/Comp 1N out Ena    */
> +#define TIM_CCER_CC1NP	BIT(3)	/* Capt/Comp 1N Polarity   */
> +#define TIM_CCER_CC2E	BIT(4)	/* Capt/Comp 2  out Ena    */
> +#define TIM_CCER_CC3E	BIT(8)	/* Capt/Comp 3  out Ena    */
> +#define TIM_CCER_CC4E	BIT(12)	/* Capt/Comp 4  out Ena    */
> +#define TIM_CCER_CCXE	(BIT(0) | BIT(4) | BIT(8) | BIT(12))
> +#define TIM_BDTR_BKE	BIT(12) /* Break input enable	   */
> +#define TIM_BDTR_BKP	BIT(13) /* Break input polarity	   */
> +#define TIM_BDTR_AOE	BIT(14)	/* Automatic Output Enable */
> +#define TIM_BDTR_MOE	BIT(15)	/* Main Output Enable      */
> +#define TIM_BDTR_BKF	(BIT(16) | BIT(17) | BIT(18) | BIT(19))
> +#define TIM_BDTR_BK2F	(BIT(20) | BIT(21) | BIT(22) | BIT(23))
> +#define TIM_BDTR_BK2E	BIT(24) /* Break 2 input enable	   */
> +#define TIM_BDTR_BK2P	BIT(25) /* Break 2 input polarity  */
> +
> +#define MAX_TIM_PSC		0xFFFF
> +#define TIM_CR2_MMS_SHIFT	4
> +#define TIM_SMCR_TS_SHIFT	4
> +#define TIM_BDTR_BKF_MASK	0xF
> +#define TIM_BDTR_BKF_SHIFT	16
> +#define TIM_BDTR_BK2F_SHIFT	20
> +
> +struct stm32_timers {
> +	struct clk *clk;
> +	struct regmap *regmap;
> +	u32 max_arr;
> +};
> +#endif
> 

^ permalink raw reply

* Re: [PATCH v9 8/8] drivers:input:tsc2007: add iio interface to read external ADC input and temperature
From: Jonathan Cameron @ 2016-12-30 20:36 UTC (permalink / raw)
  To: H. Nikolaus Schaller, Sebastian Reichel, Dmitry Torokhov,
	Mark Rutland, Benoît Cousson, Tony Lindgren, Russell King,
	Arnd Bergmann, Michael Welling, Mika Penttilä,
	Javier Martinez Canillas, Igor Grinberg, Andrew F. Davis,
	Mark Brown, Rob Herring, Alexander Stein, Eric Engestrom,
	Hans de Goede
  Cc: linux-input-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-omap-u79uwXL29TY76Z2rM5mHXA,
	letux-kernel-S0jZdbWzriLCfDggNXIi3w,
	linux-iio-u79uwXL29TY76Z2rM5mHXA,
	kernel-Jl6IXVxNIMRxAtABVqVhTwC/G2K4zDHf
In-Reply-To: <17a94568ffb91abedc9d12896b602022abb5f7e8.1482936802.git.hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org>

On 28/12/16 14:53, H. Nikolaus Schaller wrote:
> The tsc2007 chip not only has a resistive touch screen controller but
> also an external AUX adc imput which can be used for an ambient
> light sensor, battery voltage monitoring or any general purpose.
> 
> Additionally it can measure the chip temperature.
> 
> This extension provides an iio interface for these adc channels.
> 
> Since it is not wasting much resources and is very straightforward,
> we simply provide all other adc channels as optional iio interfaces
> as weel. This can be used for debugging or special applications.
> 
> This patch also splits the tsc2007 driver in several source files:
> tsc2007.h -- constants, structs and stubs
> tsc2007_core.c -- functional parts of the original driver
> tsc2007_iio.c -- the optional iio stuff
> 
> Makefile magic allows to conditionally link the iio stuff
> if CONFIG_IIO=y or =m in a way that it works with
> CONFIG_TOUCHSCREEN_TSC2007=m.
> 
> Signed-off-by: H. Nikolaus Schaller <hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org>
> Reviewed-by: Jonathan Cameron <jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Just for the record, I'm happy with the few changes from earlier versions.

Jonathan
> ---
>  drivers/input/touchscreen/Kconfig                  |  10 ++
>  drivers/input/touchscreen/Makefile                 |   2 +
>  drivers/input/touchscreen/tsc2007.h                | 116 ++++++++++++++++
>  .../touchscreen/{tsc2007.c => tsc2007_core.c}      |  95 +++----------
>  drivers/input/touchscreen/tsc2007_iio.c            | 150 +++++++++++++++++++++
>  5 files changed, 299 insertions(+), 74 deletions(-)
>  create mode 100644 drivers/input/touchscreen/tsc2007.h
>  rename drivers/input/touchscreen/{tsc2007.c => tsc2007_core.c} (86%)
>  create mode 100644 drivers/input/touchscreen/tsc2007_iio.c
> 
> diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
> index efca013..1616a8d 100644
> --- a/drivers/input/touchscreen/Kconfig
> +++ b/drivers/input/touchscreen/Kconfig
> @@ -1035,6 +1035,16 @@ config TOUCHSCREEN_TSC2007
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called tsc2007.
>  
> +config TOUCHSCREEN_TSC2007_IIO
> +	bool "IIO interface for external ADC input and temperature"
> +	depends on TOUCHSCREEN_TSC2007
> +	depends on IIO=y || IIO=TOUCHSCREEN_TSC2007
> +	help
> +	  Saying Y here adds an iio interface to the tsc2007 which
> +	  provides values for the AUX input (used for e.g. battery
> +	  or ambient light monitoring), temperature and raw input
> +	  values.
> +
>  config TOUCHSCREEN_W90X900
>  	tristate "W90P910 touchscreen driver"
>  	depends on ARCH_W90X900
> diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> index 81b8645..05d1cc8 100644
> --- a/drivers/input/touchscreen/Makefile
> +++ b/drivers/input/touchscreen/Makefile
> @@ -80,6 +80,8 @@ obj-$(CONFIG_TOUCHSCREEN_TSC_SERIO)	+= tsc40.o
>  obj-$(CONFIG_TOUCHSCREEN_TSC200X_CORE)	+= tsc200x-core.o
>  obj-$(CONFIG_TOUCHSCREEN_TSC2004)	+= tsc2004.o
>  obj-$(CONFIG_TOUCHSCREEN_TSC2005)	+= tsc2005.o
> +tsc2007-y := tsc2007_core.o
> +tsc2007-$(CONFIG_TOUCHSCREEN_TSC2007_IIO)	+= tsc2007_iio.o
>  obj-$(CONFIG_TOUCHSCREEN_TSC2007)	+= tsc2007.o
>  obj-$(CONFIG_TOUCHSCREEN_UCB1400)	+= ucb1400_ts.o
>  obj-$(CONFIG_TOUCHSCREEN_WACOM_W8001)	+= wacom_w8001.o
> diff --git a/drivers/input/touchscreen/tsc2007.h b/drivers/input/touchscreen/tsc2007.h
> new file mode 100644
> index 0000000..16efb60
> --- /dev/null
> +++ b/drivers/input/touchscreen/tsc2007.h
> @@ -0,0 +1,116 @@
> +/*
> + * Copyright (c) 2008 MtekVision Co., Ltd.
> + *	Kwangwoo Lee <kwlee-ec7hoAtq5SbSUeElwK9/Pw@public.gmane.org>
> + *
> + * Using code from:
> + *  - ads7846.c
> + *	Copyright (c) 2005 David Brownell
> + *	Copyright (c) 2006 Nokia Corporation
> + *  - corgi_ts.c
> + *	Copyright (C) 2004-2005 Richard Purdie
> + *  - omap_ts.[hc], ads7846.h, ts_osk.c
> + *	Copyright (C) 2002 MontaVista Software
> + *	Copyright (C) 2004 Texas Instruments
> + *	Copyright (C) 2005 Dirk Behme
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License version 2 as
> + *  published by the Free Software Foundation.
> + */
> +
> +#include <linux/input/touchscreen.h>
> +
> +#define TSC2007_MEASURE_TEMP0		(0x0 << 4)
> +#define TSC2007_MEASURE_AUX		(0x2 << 4)
> +#define TSC2007_MEASURE_TEMP1		(0x4 << 4)
> +#define TSC2007_ACTIVATE_XN		(0x8 << 4)
> +#define TSC2007_ACTIVATE_YN		(0x9 << 4)
> +#define TSC2007_ACTIVATE_YP_XN		(0xa << 4)
> +#define TSC2007_SETUP			(0xb << 4)
> +#define TSC2007_MEASURE_X		(0xc << 4)
> +#define TSC2007_MEASURE_Y		(0xd << 4)
> +#define TSC2007_MEASURE_Z1		(0xe << 4)
> +#define TSC2007_MEASURE_Z2		(0xf << 4)
> +
> +#define TSC2007_POWER_OFF_IRQ_EN	(0x0 << 2)
> +#define TSC2007_ADC_ON_IRQ_DIS0		(0x1 << 2)
> +#define TSC2007_ADC_OFF_IRQ_EN		(0x2 << 2)
> +#define TSC2007_ADC_ON_IRQ_DIS1		(0x3 << 2)
> +
> +#define TSC2007_12BIT			(0x0 << 1)
> +#define TSC2007_8BIT			(0x1 << 1)
> +
> +#define	MAX_12BIT			((1 << 12) - 1)
> +
> +#define ADC_ON_12BIT	(TSC2007_12BIT | TSC2007_ADC_ON_IRQ_DIS0)
> +
> +#define READ_Y		(ADC_ON_12BIT | TSC2007_MEASURE_Y)
> +#define READ_Z1		(ADC_ON_12BIT | TSC2007_MEASURE_Z1)
> +#define READ_Z2		(ADC_ON_12BIT | TSC2007_MEASURE_Z2)
> +#define READ_X		(ADC_ON_12BIT | TSC2007_MEASURE_X)
> +#define PWRDOWN		(TSC2007_12BIT | TSC2007_POWER_OFF_IRQ_EN)
> +
> +struct ts_event {
> +	u16	x;
> +	u16	y;
> +	u16	z1, z2;
> +};
> +
> +struct tsc2007 {
> +	struct input_dev	*input;
> +	char			phys[32];
> +
> +	struct i2c_client	*client;
> +
> +	u16			model;
> +	u16			x_plate_ohms;
> +
> +	struct touchscreen_properties prop;
> +
> +	bool			report_resistance;
> +	u16			min_x;
> +	u16			min_y;
> +	u16			max_x;
> +	u16			max_y;
> +	u16			max_rt;
> +	unsigned long		poll_period; /* in jiffies */
> +	int			fuzzx;
> +	int			fuzzy;
> +	int			fuzzz;
> +
> +	unsigned int		gpio;
> +	int			irq;
> +
> +	wait_queue_head_t	wait;
> +	bool			stopped;
> +	bool			pendown;
> +
> +	int			(*get_pendown_state)(struct device *);
> +	void			(*clear_penirq)(void);
> +
> +	struct mutex		mlock;
> +	struct iio_dev		*iio_dev;	/* optional */
> +};
> +
> +int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd);
> +u32 tsc2007_calculate_resistance(struct tsc2007 *tsc,
> +					struct ts_event *tc);
> +bool tsc2007_is_pen_down(struct tsc2007 *ts);
> +
> +#if IS_ENABLED(CONFIG_TOUCHSCREEN_TSC2007_IIO)
> +
> +/* defined in tsc2007_iio.c */
> +int tsc2007_iio_configure(struct tsc2007 *ts);
> +void tsc2007_iio_unconfigure(struct tsc2007 *ts);
> +
> +#else /* CONFIG_TOUCHSCREEN_TSC2007_IIO */
> +
> +static inline int tsc2007_iio_configure(struct tsc2007 *ts)
> +{
> +	return 0;
> +}
> +static inline void tsc2007_iio_unconfigure(struct tsc2007 *ts)
> +{
> +}
> +
> +#endif /* CONFIG_TOUCHSCREEN_TSC2007_IIO */
> diff --git a/drivers/input/touchscreen/tsc2007.c b/drivers/input/touchscreen/tsc2007_core.c
> similarity index 86%
> rename from drivers/input/touchscreen/tsc2007.c
> rename to drivers/input/touchscreen/tsc2007_core.c
> index 76b462b..812ded8 100644
> --- a/drivers/input/touchscreen/tsc2007.c
> +++ b/drivers/input/touchscreen/tsc2007_core.c
> @@ -27,79 +27,11 @@
>  #include <linux/i2c.h>
>  #include <linux/i2c/tsc2007.h>
>  #include <linux/of_device.h>
> -#include <linux/of.h>
>  #include <linux/of_gpio.h>
> -#include <linux/input/touchscreen.h>
> -
> -#define TSC2007_MEASURE_TEMP0		(0x0 << 4)
> -#define TSC2007_MEASURE_AUX		(0x2 << 4)
> -#define TSC2007_MEASURE_TEMP1		(0x4 << 4)
> -#define TSC2007_ACTIVATE_XN		(0x8 << 4)
> -#define TSC2007_ACTIVATE_YN		(0x9 << 4)
> -#define TSC2007_ACTIVATE_YP_XN		(0xa << 4)
> -#define TSC2007_SETUP			(0xb << 4)
> -#define TSC2007_MEASURE_X		(0xc << 4)
> -#define TSC2007_MEASURE_Y		(0xd << 4)
> -#define TSC2007_MEASURE_Z1		(0xe << 4)
> -#define TSC2007_MEASURE_Z2		(0xf << 4)
> -
> -#define TSC2007_POWER_OFF_IRQ_EN	(0x0 << 2)
> -#define TSC2007_ADC_ON_IRQ_DIS0		(0x1 << 2)
> -#define TSC2007_ADC_OFF_IRQ_EN		(0x2 << 2)
> -#define TSC2007_ADC_ON_IRQ_DIS1		(0x3 << 2)
> -
> -#define TSC2007_12BIT			(0x0 << 1)
> -#define TSC2007_8BIT			(0x1 << 1)
> -
> -#define	MAX_12BIT			((1 << 12) - 1)
> -
> -#define ADC_ON_12BIT	(TSC2007_12BIT | TSC2007_ADC_ON_IRQ_DIS0)
> -
> -#define READ_Y		(ADC_ON_12BIT | TSC2007_MEASURE_Y)
> -#define READ_Z1		(ADC_ON_12BIT | TSC2007_MEASURE_Z1)
> -#define READ_Z2		(ADC_ON_12BIT | TSC2007_MEASURE_Z2)
> -#define READ_X		(ADC_ON_12BIT | TSC2007_MEASURE_X)
> -#define PWRDOWN		(TSC2007_12BIT | TSC2007_POWER_OFF_IRQ_EN)
> -
> -struct ts_event {
> -	u16	x;
> -	u16	y;
> -	u16	z1, z2;
> -};
> -
> -struct tsc2007 {
> -	struct input_dev	*input;
> -	char			phys[32];
> -
> -	struct i2c_client	*client;
> -
> -	u16			model;
> -	u16			x_plate_ohms;
> -
> -	struct touchscreen_properties prop;
> -
> -	bool			report_resistance;
> -	u16			min_x;
> -	u16			min_y;
> -	u16			max_x;
> -	u16			max_y;
> -	u16			max_rt;
> -	unsigned long		poll_period; /* in jiffies */
> -	int			fuzzx;
> -	int			fuzzy;
> -	int			fuzzz;
> -
> -	unsigned		gpio;
> -	int			irq;
> -
> -	wait_queue_head_t	wait;
> -	bool			stopped;
> +#include "tsc2007.h"
>  
> -	int			(*get_pendown_state)(struct device *);
> -	void			(*clear_penirq)(void);
> -};
>  
> -static inline int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd)
> +int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd)
>  {
>  	s32 data;
>  	u16 val;
> @@ -137,7 +69,7 @@ static void tsc2007_read_values(struct tsc2007 *tsc, struct ts_event *tc)
>  	tsc2007_xfer(tsc, PWRDOWN);
>  }
>  
> -static u32 tsc2007_calculate_resistance(struct tsc2007 *tsc,
> +u32 tsc2007_calculate_resistance(struct tsc2007 *tsc,
>  					struct ts_event *tc)
>  {
>  	u32 rt = 0;
> @@ -158,7 +90,7 @@ static u32 tsc2007_calculate_resistance(struct tsc2007 *tsc,
>  	return rt;
>  }
>  
> -static bool tsc2007_is_pen_down(struct tsc2007 *ts)
> +bool tsc2007_is_pen_down(struct tsc2007 *ts)
>  {
>  	/*
>  	 * NOTE: We can't rely on the pressure to determine the pen down
> @@ -191,7 +123,10 @@ static irqreturn_t tsc2007_soft_irq(int irq, void *handle)
>  	while (!ts->stopped && tsc2007_is_pen_down(ts)) {
>  
>  		/* pen is down, continue with the measurement */
> +
> +		mutex_lock(&ts->mlock);
>  		tsc2007_read_values(ts, &tc);
> +		mutex_unlock(&ts->mlock);
>  
>  		rt = tsc2007_calculate_resistance(ts, &tc);
>  
> @@ -441,7 +376,8 @@ static void tsc2007_call_exit_platform_hw(void *data)
>  static int tsc2007_probe(struct i2c_client *client,
>  			 const struct i2c_device_id *id)
>  {
> -	const struct tsc2007_platform_data *pdata = dev_get_platdata(&client->dev);
> +	const struct tsc2007_platform_data *pdata =
> +		dev_get_platdata(&client->dev);
>  	struct tsc2007 *ts;
>  	struct input_dev *input_dev;
>  	int err;
> @@ -463,7 +399,9 @@ static int tsc2007_probe(struct i2c_client *client,
>  	ts->client = client;
>  	ts->irq = client->irq;
>  	ts->input = input_dev;
> +
>  	init_waitqueue_head(&ts->wait);
> +	mutex_init(&ts->mlock);
>  
>  	snprintf(ts->phys, sizeof(ts->phys),
>  		 "%s/input0", dev_name(&client->dev));
> @@ -534,7 +472,7 @@ static int tsc2007_probe(struct i2c_client *client,
>  	if (err < 0) {
>  		dev_err(&client->dev,
>  			"Failed to setup chip: %d\n", err);
> -		return err;	/* usually, chip does not respond */
> +		return err;	/* chip does not respond */
>  	}
>  
>  	err = input_register_device(input_dev);
> @@ -544,6 +482,14 @@ static int tsc2007_probe(struct i2c_client *client,
>  		return err;
>  	}
>  
> +	return tsc2007_iio_configure(ts);
> +}
> +
> +static int tsc2007_remove(struct i2c_client *client)
> +{
> +	struct tsc2007 *ts = i2c_get_clientdata(client);
> +
> +	tsc2007_iio_unconfigure(ts);
>  	return 0;
>  }
>  
> @@ -569,6 +515,7 @@ static struct i2c_driver tsc2007_driver = {
>  	},
>  	.id_table	= tsc2007_idtable,
>  	.probe		= tsc2007_probe,
> +	.remove		= tsc2007_remove,
>  };
>  
>  module_i2c_driver(tsc2007_driver);
> diff --git a/drivers/input/touchscreen/tsc2007_iio.c b/drivers/input/touchscreen/tsc2007_iio.c
> new file mode 100644
> index 0000000..ed79944
> --- /dev/null
> +++ b/drivers/input/touchscreen/tsc2007_iio.c
> @@ -0,0 +1,150 @@
> +/*
> + * Copyright (c) 2016 Golden Delicious Comp. GmbH&Co. KG
> + *	Nikolaus Schaller <hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org>
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License version 2 as
> + *  published by the Free Software Foundation.
> + */
> +
> +#include <linux/i2c.h>
> +#include <linux/iio/iio.h>
> +#include "tsc2007.h"
> +
> +struct tsc2007_iio {
> +	struct tsc2007 *ts;
> +};
> +
> +#define TSC2007_CHAN_IIO(_chan, _name, _type, _chan_info) \
> +{ \
> +	.datasheet_name = _name, \
> +	.type = _type, \
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |	\
> +			BIT(_chan_info), \
> +	.indexed = 1, \
> +	.channel = _chan, \
> +}
> +
> +static const struct iio_chan_spec tsc2007_iio_channel[] = {
> +	TSC2007_CHAN_IIO(0, "x", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
> +	TSC2007_CHAN_IIO(1, "y", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
> +	TSC2007_CHAN_IIO(2, "z1", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
> +	TSC2007_CHAN_IIO(3, "z2", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
> +	TSC2007_CHAN_IIO(4, "adc", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
> +	TSC2007_CHAN_IIO(5, "rt", IIO_VOLTAGE, IIO_CHAN_INFO_RAW), /* Ohms? */
> +	TSC2007_CHAN_IIO(6, "pen", IIO_PRESSURE, IIO_CHAN_INFO_RAW),
> +	TSC2007_CHAN_IIO(7, "temp0", IIO_TEMP, IIO_CHAN_INFO_RAW),
> +	TSC2007_CHAN_IIO(8, "temp1", IIO_TEMP, IIO_CHAN_INFO_RAW),
> +};
> +
> +static int tsc2007_read_raw(struct iio_dev *indio_dev,
> +	struct iio_chan_spec const *chan, int *val, int *val2, long mask)
> +{
> +	struct tsc2007_iio *iio = iio_priv(indio_dev);
> +	struct tsc2007 *tsc = iio->ts;
> +	int adc_chan = chan->channel;
> +	int ret = 0;
> +
> +	if (adc_chan >= ARRAY_SIZE(tsc2007_iio_channel))
> +		return -EINVAL;
> +
> +	if (mask != IIO_CHAN_INFO_RAW)
> +		return -EINVAL;
> +
> +	mutex_lock(&tsc->mlock);
> +
> +	switch (chan->channel) {
> +	case 0:
> +		*val = tsc2007_xfer(tsc, READ_X);
> +		break;
> +	case 1:
> +		*val = tsc2007_xfer(tsc, READ_Y);
> +		break;
> +	case 2:
> +		*val = tsc2007_xfer(tsc, READ_Z1);
> +		break;
> +	case 3:
> +		*val = tsc2007_xfer(tsc, READ_Z2);
> +		break;
> +	case 4:
> +		*val = tsc2007_xfer(tsc, (ADC_ON_12BIT | TSC2007_MEASURE_AUX));
> +		break;
> +	case 5: {
> +		struct ts_event tc;
> +
> +		tc.x = tsc2007_xfer(tsc, READ_X);
> +		tc.z1 = tsc2007_xfer(tsc, READ_Z1);
> +		tc.z2 = tsc2007_xfer(tsc, READ_Z2);
> +		*val = tsc2007_calculate_resistance(tsc, &tc);
> +		break;
> +	}
> +	case 6:
> +		*val = tsc2007_is_pen_down(tsc);
> +		break;
> +	case 7:
> +		*val = tsc2007_xfer(tsc,
> +				    (ADC_ON_12BIT | TSC2007_MEASURE_TEMP0));
> +		break;
> +	case 8:
> +		*val = tsc2007_xfer(tsc,
> +				    (ADC_ON_12BIT | TSC2007_MEASURE_TEMP1));
> +		break;
> +	}
> +
> +	/* Prepare for next touch reading - power down ADC, enable PENIRQ */
> +	tsc2007_xfer(tsc, PWRDOWN);
> +
> +	mutex_unlock(&tsc->mlock);
> +
> +	ret = IIO_VAL_INT;
> +
> +	return ret;
> +}
> +
> +static const struct iio_info tsc2007_iio_info = {
> +	.read_raw = tsc2007_read_raw,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +int tsc2007_iio_configure(struct tsc2007 *ts)
> +{
> +	int err;
> +	struct iio_dev *indio_dev;
> +	struct tsc2007_iio *iio;
> +
> +	indio_dev = devm_iio_device_alloc(&ts->client->dev,
> +		sizeof(struct tsc2007_iio));
> +	if (!indio_dev) {
> +		dev_err(&ts->client->dev, "iio_device_alloc failed\n");
> +		return -ENOMEM;
> +	}
> +
> +	iio = iio_priv(indio_dev);
> +	iio->ts = ts;
> +	ts->iio_dev = (void *) indio_dev;
> +
> +	indio_dev->name = "tsc2007";
> +	indio_dev->dev.parent = &ts->client->dev;
> +	indio_dev->info = &tsc2007_iio_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->channels = tsc2007_iio_channel;
> +	indio_dev->num_channels = ARRAY_SIZE(tsc2007_iio_channel);
> +
> +	err = iio_device_register(indio_dev);
> +	if (err < 0) {
> +		dev_err(&ts->client->dev, "iio_device_register() failed: %d\n",
> +			err);
> +		return err;
> +	}
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(tsc2007_iio_configure);
> +
> +void tsc2007_iio_unconfigure(struct tsc2007 *ts)
> +{
> +	struct iio_dev *indio_dev = ts->iio_dev;
> +
> +	iio_device_unregister(indio_dev);
> +}
> +EXPORT_SYMBOL(tsc2007_iio_unconfigure);
> 

--
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 3/4] ARM64: dts: exynos5433: use macros for pinctrl configuration on Exynos5433
From: Andi Shyti @ 2016-12-30 20:28 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Andi Shyti, Chanwoo Choi, Tomasz Figa, Krzysztof Kozlowski,
	Sylwester Nawrocki, Rob Herring, Mark Rutland, Catalin Marinas,
	Will Deacon, Kukjin Kim, Javier Martinez Canillas,
	devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
	linux-samsung-soc, linux-kernel@vger.kernel.org, stable,
	Andi Shyti
In-Reply-To: <CACRpkdaz9AyFJs1Th_SVexqKaP-=iPF0G-1YXOCoSmrQH8ZHDA@mail.gmail.com>

Hi Linus,

> > Use the macros defined in include/dt-bindings/pinctrl/samsung.h
> > instead of hardcoded values.
> >
> > Signed-off-by: Andi Shyti <andi.shyti@samsung.com>
> 
> These look fine, but that this and the other DTS patch through ARM SoC.
> 
> If you also need the headerfile patch (patch 2) to go through ARM SoC
> that is fine,
> I can take it out of pinctrl if you want.

yes, sure... no problem from my side :)

Thanks,
Andi

^ permalink raw reply

* Re: [PATCH v2 1/2] devicetree: add Garmin vendor prefix
From: Jonathan Cameron @ 2016-12-30 20:26 UTC (permalink / raw)
  To: Matt Ranostay, linux-iio-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Rob Herring
In-Reply-To: <20161229051306.28547-1-matt-sk+viVC6FLCDq+mSdOJa79kegs52MxvZ@public.gmane.org>

On 29/12/16 05:13, Matt Ranostay wrote:
> Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> Signed-off-by: Matt Ranostay <matt-sk+viVC6FLCDq+mSdOJa79kegs52MxvZ@public.gmane.org>
Rob, just to check - is this the right way to go with prefixes?
It's awfully ugly in this case ;)

Jonathan
> ---
> Changes from v1:
> * switch to stock ticker for Garmin Limited
> 
>  Documentation/devicetree/bindings/vendor-prefixes.txt | 1 +
>  1 file changed, 1 insertion(+)
> 
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
> index 16d3b5e7f5d1..5749bfc5fc5b 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.txt
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
> @@ -107,6 +107,7 @@ firefly	Firefly
>  focaltech	FocalTech Systems Co.,Ltd
>  friendlyarm	Guangzhou FriendlyARM Computer Tech Co., Ltd
>  fsl	Freescale Semiconductor
> +grmn	Garmin Limited
>  ge	General Electric Company
>  geekbuying	GeekBuying
>  gef	GE Fanuc Intelligent Platforms Embedded Systems, Inc.
> 

--
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 2/2] cfg80211: reg: support ieee80211-(min|max)-center-freq DT properties
From: Arend van Spriel @ 2016-12-30 20:20 UTC (permalink / raw)
  To: Rafał Miłecki
  Cc: Kalle Valo,
	linux-wireless-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	Martin Blumenstingl, Felix Fietkau, Arnd Bergmann,
	devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	Rafał Miłecki
In-Reply-To: <CACna6rwaUWEjpBdfXS6uJSxKXH_mCP7YMGd1KaJropNQgVS7PA-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>

On 29-12-16 10:43, Rafał Miłecki wrote:
> On 29 December 2016 at 09:57, Arend van Spriel
> <arend.vanspriel-dY08KVG/lbpWk0Htik3J/w@public.gmane.org> wrote:
>> On 28-12-16 22:30, Rafał Miłecki wrote:
>>> On 28 December 2016 at 22:28, Rafał Miłecki <zajec5-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
>>>> On 28 December 2016 at 22:07, Arend van Spriel
>>>> <arend.vanspriel-dY08KVG/lbpWk0Htik3J/w@public.gmane.org> wrote:
>>>>> On 28-12-16 16:59, Rafał Miłecki wrote:
>>>>>> From: Rafał Miłecki <rafal-g1n6cQUeyibVItvQsEIGlw@public.gmane.org>
>>>>>>
>>>>>> They allow specifying hardware limitations of supported channels. This
>>>>>> may be useful for specifying single band devices or devices that support
>>>>>> only some part of the whole band.
>>>>>> E.g. some tri-band routers have separated radios for lower and higher
>>>>>> part of 5 GHz band.
>>>>>>
>>>>>> Signed-off-by: Rafał Miłecki <rafal-g1n6cQUeyibVItvQsEIGlw@public.gmane.org>
>>>>>> ---
>>>>>>  net/wireless/reg.c | 34 ++++++++++++++++++++++++++++++++++
>>>>>>  1 file changed, 34 insertions(+)
>>>>>>
>>>>>> diff --git a/net/wireless/reg.c b/net/wireless/reg.c
>>>>>> index 5dbac37..35ba5c7 100644
>>>>>> --- a/net/wireless/reg.c
>>>>>> +++ b/net/wireless/reg.c
>>>>>> @@ -1123,6 +1123,26 @@ const char *reg_initiator_name(enum nl80211_reg_initiator initiator)
>>>>>>  }
>>>>>>  EXPORT_SYMBOL(reg_initiator_name);
>>>>>>
>>>>>> +static bool reg_center_freq_of_valid(struct wiphy *wiphy,
>>>>>> +                                  struct ieee80211_channel *chan)
>>>>>> +{
>>>>>> +     struct device_node *np = wiphy_dev(wiphy)->of_node;
>>>>>> +     u32 val;
>>>>>> +
>>>>>> +     if (!np)
>>>>>> +             return true;
>>>>>> +
>>>>>> +     if (!of_property_read_u32(np, "ieee80211-min-center-freq", &val) &&
>>>>>> +         chan->center_freq < KHZ_TO_MHZ(val))
>>>>>> +             return false;
>>>>>> +
>>>>>> +     if (!of_property_read_u32(np, "ieee80211-max-center-freq", &val) &&
>>>>>> +         chan->center_freq > KHZ_TO_MHZ(val))
>>>>>> +             return false;
>>>>>
>>>>> I suspect these functions rely on CONFIG_OF. So might not build for
>>>>> !CONFIG_OF.
>>>>
>>>> I compiled it with
>>>> # CONFIG_OF is not set
>>>>
>>>> Can you test it and provide a non-working config if you see a
>>>> compilation error, please?
>>>
>>> include/linux/of.h provides a lot of dummy static inline functions if
>>> CONFIG_OF is not used (they also allow compiler to drop most of the
>>> code).
>>
>> of_propeirty_read_u32 is static inline in of.h calling
>> of_property_read_u32_array, which has a dummy variant in of.h returning
>> -ENOSYS so -38. Pretty sure that is not what you want. At least it does
>> not allow the compiler to drop any code so probably better to do:
>>
>> if (!IS_ENABLED(CONFIG_OF) || !np)
>>         return true;
> 
> Please verify that using a compiler. If there is a problem I'll be
> happy to work on it, but I need a proof it exists.

I am on vacation right now so not having much more than email and web
browser to use as review reference.

> If compilers sees a:
> if (!-ENOSYS && chan->center_freq < KHZ_TO_MHZ(val))
> condition, it's pretty clear it can be dropped. With both conditional
> blocks dropped function always returns "true" and... can be dropped.
> 
> Let me see if I can convince you with the following test:

No need to convince me. I made a mistake reviewing the code. Thanks for
clarifying it.

> $ grep -m 1 CONFIG_OF .config
> # CONFIG_OF is not set
> $ objdump --syms net/wireless/reg.o | grep -c reg_center_freq_of_valid
> 0
> 
> $ grep -m 1 CONFIG_OF .config
> CONFIG_OF=y
> $ objdump --syms net/wireless/reg.o | grep -c reg_center_freq_of_valid
> 1
> 
> 
>> So with this patch you change the channel to DISABLED. I am not very
>> familiar with reg.c so do you know if this is done before or after
>> calling regulatory notifier in the driver. brcmfmac will enable channels
>> querying the device upon regulatory notifier call, which may undo the
>> behavior introduced by your patch.
> 
> I'm not regulatory export, so I hope someone will review this patch.
> So far I can say it works for me after trying it on SR400ac with
> BCM43602.

But you probably do not have a mapping table for mapping country code
received in notifier to firmware regulatory code/revision. Only if you
have that, brcmfmac will update the channels in the bands.

Giving this some more consideration I am not sure if this is the proper
place to handle this. ieee80211-(min|max)-center-freq is platform
specific configuration allowing multiple cards to be used in different
(sub)bands. This has nothing to do with regulatory. So probably better
to move it to core.c or chan.c.

Regards,
Arend
--
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 v8 3/8] drivers:input:tsc2007: add iio interface to read external ADC input and temperature
From: Jonathan Cameron @ 2016-12-30 20:17 UTC (permalink / raw)
  To: H. Nikolaus Schaller, Dmitry Torokhov
  Cc: Jonathan Cameron, Sebastian Reichel, Mark Rutland,
	Benoît Cousson, Tony Lindgren, Russell King, Arnd Bergmann,
	Michael Welling, Mika Penttilä, Javier Martinez Canillas,
	Igor Grinberg, Andrew F. Davis, Mark Brown, Rob Herring,
	Alexander Stein, Eric Engestrom, Hans de Goede,
	Benjamin Tissoires
In-Reply-To: <FA2666F0-0C5B-458E-97B5-83243F26D0DE@goldelico.com>

On 28/12/16 14:52, H. Nikolaus Schaller wrote:
> Hi Dmitry,
> 
>> Am 27.12.2016 um 22:54 schrieb Dmitry Torokhov <dmitry.torokhov@gmail.com>:
>>
>> On Mon, Dec 12, 2016 at 10:21:25PM +0100, H. Nikolaus Schaller wrote:
>>> Hi,
>>>
>>>
>>>> Am 27.11.2016 um 16:47 schrieb H. Nikolaus Schaller <hns@goldelico.com>:
>>>>
>>>> Hi Jonathan,
>>>>
>>>>> Am 27.11.2016 um 12:02 schrieb Jonathan Cameron <jic23@kernel.org>:
>>>>>
>>>>> On 24/11/16 18:05, H. Nikolaus Schaller wrote:
>>>>>>
>>>>>>> Am 24.11.2016 um 18:38 schrieb Jonathan Cameron <jic23@jic23.retrosnub.co.uk>:
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> On 22 November 2016 14:02:30 GMT+00:00, "H. Nikolaus Schaller" <hns@goldelico.com> wrote:
>>>
>>>>
>>>>> - hence cc'd Yann and the Kbuild list
>>>>> to see if they can offer some advices.
>>>
>>> no response / advice so far.
>>
>> Since you are saying that IIO stuff is optional, add it to Kconfig
>> explicitly:
>>
>> config "TOUCHSCREEN_TSC2007_IIO"
>> 	bool "IIO interface for external ADC input and temperature"
>> 	depends on TOUCHSCREEN_TSC2007
>> 	depends on IIO=y || IIO=TOUCHSCREEN_TSC2007
>> 	help
>> 	  ...
>>
>> and use this symbols in makefile:
>>
>> and in Makefile:
>>
>> obj-$(CONFIG_TOUCHSCREEN_TSC2007) += tsc2007.o
>> tsc2007-y := tsc2007-core.o ...
>> tsc2007-$(CONFIG_TOUCHSCREEN_TSC2007_IIO) += tsc2007_iio.o
> 
> Ah, ok. Well, we tried to make it without an explicit config option
> but it does not hurt to have one.
> 
> Your proposal works fine for 3 out of the 4 possible combinations.
> 
> I think it is unlikely that the combination TOUCHSCREEN_TSC2007=y IIO=m
> is needed at all (we configure as much as possible as =m in our kernel).
Indeed - that shouldn't work and is blocked by the above which is good.
> 
> Patch v9 will come next (where I have also moved this iio driver
> patch to be 8/8).
Wise move ;)
> 
> BR and thanks,
> Nikolaus
> 


^ permalink raw reply

* Re: [PATCH v4 1/5] devicetree: mfd: Add binding for the TI LM3533
From: Jonathan Cameron @ 2016-12-30 19:50 UTC (permalink / raw)
  To: Bjorn Andersson, Rob Herring, Mark Rutland
  Cc: Lee Jones, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald-Stadler, Richard Purdie, Jacek Anaszewski,
	Pavel Machek, Jingoo Han, devicetree, linux-kernel, linux-iio,
	linux-leds, Bjorn Andersson
In-Reply-To: <20161226181153.11271-1-bjorn.andersson@linaro.org>

On 26/12/16 18:11, Bjorn Andersson wrote:
> From: Bjorn Andersson <bjorn.andersson@sonymobile.com>
> 
> Add the binding for the Texas Instruments LM3533 lighting power
> solution.
> 
> Signed-off-by: Bjorn Andersson <bjorn.andersson@sonymobile.com>
> Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
> ---
> 
> I had Acks from Jonathan and Rob on v3, but dropped these due to the added
> compatible properties.
Looks sensible to me

Acked-by: Jonathan Cameron <jic23@kernel.org>
> 
> Changes since v3:
> - Added compatible to sub-nodes, per Lee's requested to treat them as separate
>   pieces.
> 
>  Documentation/devicetree/bindings/mfd/lm3533.txt | 205 +++++++++++++++++++++++
>  1 file changed, 205 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/mfd/lm3533.txt
> 
> diff --git a/Documentation/devicetree/bindings/mfd/lm3533.txt b/Documentation/devicetree/bindings/mfd/lm3533.txt
> new file mode 100644
> index 000000000000..909281096ba2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mfd/lm3533.txt
> @@ -0,0 +1,205 @@
> +Texas Instruments LM3533 binding
> +
> +This binding describes the Texas Instruments LM3533, a lighting power solution
> +for smartphone handsets. The common properties are described directly in the
> +node, while each individual component are described in an optional subnode.
> +
> +- compatible:
> +	Usage: required
> +	Value type: <stringlist>
> +	Definition: must be:
> +		    "ti,lm3533"
> +
> +- reg:
> +	Usage: required
> +	Value type: <u32>
> +	Definition: i2c address of the LM3533 chip
> +
> +- als-supply:
> +	Usage: optional
> +	Value type: <prop-encoded-array>
> +	Definition: reference to regulator powering the V_als input; as
> +		    specified in "../regulator/regulator.txt"
> +
> +- hwen-gpios:
> +	Usage: required
> +	Value type: <prop-encoded-array>
> +	Definition: reference to gpio pin connected to the HWEN input; as
> +		    specified in "../gpio/gpio.txt"
> +
> +- ti,boost-freq-hz:
> +	Usage: required
> +	Value type: <u32>
> +	Definition: switch-frequency of the boost converter, must be either:
> +		    500000 or 1000000
> +
> +- ti,boost-ovp-mv:
> +	Usage: required
> +	Value type: <u32>
> +	Definition: over-voltage protection limit, in mV. Must be one of:
> +		    16000, 24000, 32000 or 40000
> +
> +- #address-cells:
> +	Usage: required
> +	Value type: <u32>
> +	Definition: must be 1
> +
> +- #size-cells:
> +	Usage: required
> +	Value type: <u32>
> +	Definition: must be 0
> +
> += ALS SUBNODE
> +The ambient light sensor subnode carrying the light sensor related properties.
> +
> +- compatible:
> +	Usage: required
> +	Value type: <stringlist>
> +	Definition: must be:
> +		    "ti,lm3533-als"
> +
> +- ti,als-resistance-ohm:
> +	Usage: required (unless ti,pwm-mode is specified)
> +	Value type: <u32>
> +	Definition: specifies the resistor value (R_als), in Ohm. Valid values
> +		    ranges from 200000 to 1574 Ohm.
> +
> +- ti,pwm-mode:
> +	Usage: optional
> +	Value type: <empty>
> +	Definition: specifies, if present, that the als should operate in PWM
> +		    mode - rather than analog mode
> +
> += BACKLIGHT NODES
> +Backlight subnodes carrying the backlight related properties.
> +
> +- compatible:
> +	Usage: required
> +	Value type: <stringlist>
> +	Definition: must be:
> +		    "ti,lm3533-backlight"
> +
> +- reg:
> +	Usage: required
> +	Value type: <u32>
> +	Definition: specifies which of the two backlights this node corresponds
> +		    to
> +
> +- default-brightness:
> +	Usage: optional
> +	Value type: <32>
> +	Definition: specifies the default brightness for the backlight, in
> +		    units of brightness [0-255]
> +
> +- label:
> +	Usage: required
> +	Value type: <string>
> +	Definition: specifies a name of this backlight
> +
> +- led-max-microamp:
> +	Usage: required
> +	Value type: <u32>
> +	Definition: specifies the max current for this backlight, in uA, as
> +		    described in "../leds/common.txt"
> +
> +- ti,pwm-zones:
> +	Usage: optional
> +	Value type: <u32 list>
> +	Definition: lists the ALS zones to be PWM controlled for this backlight,
> +		    the values in the list are in the range [0 - 4]
> +
> += LED NODES
> +LED subnodes carrying the LED related properties.
> +
> +- compatible:
> +	Usage: required
> +	Value type: <stringlist>
> +	Definition: must be:
> +		    "ti,lm3533-led"
> +
> +- reg:
> +	Usage: required
> +	Value type: <u32>
> +	Definition: specifies which of the four LEDs this node corresponds to
> +
> +- linux,default-trigger:
> +	Usage: optional
> +	Value type: <string>
> +	Definition: specifies the default trigger for the LED, as described in
> +		    "../leds/common.txt"
> +
> +- label:
> +	Usage: required
> +	Value type: <string>
> +	Definition: specifies a name of this LED, as described in
> +		    "../leds/common.txt"
> +
> +- led-max-microamp:
> +	Usage: required
> +	Value type: <u32>
> +	Definition: specifies the max current for this LED, in uA, as described
> +		    in "../leds/common.txt"
> +
> +- ti,pwm-zones:
> +	Usage: optional
> +	Value type: <u32 list>
> +	Definition: lists the ALS zones to be PWM controlled for this LED, the
> +		    values in the list are in the range [0 - 4]
> +
> += EXAMPLE
> +
> +i2c@12460000 {
> +	compatible = "qcom,i2c-qup-v1.1.1";
> +	...
> +
> +	lm3533@36 {
> +		compatible = "ti,lm3533";
> +		reg = <0x36>;
> +
> +		als-supply = <&pm8921_l11>;
> +		hwen-gpios = <&pm8921_gpio 26 GPIO_ACTIVE_HIGH>;
> +
> +		ti,boost-freq-hz = <500000>;
> +		ti,boost-ovp-mv = <24000>;
> +
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +
> +		als {
> +			compatible = "ti,lm3533-als";
> +			ti,als-resistance-ohm = <200000>;
> +		};
> +
> +		backlight@0 {
> +			compatible = "ti,lm3533-backlight";
> +			reg = <0>;
> +			label = "backlight";
> +
> +			led-max-microamp = <20200>;
> +		};
> +
> +		led@0 {
> +			compatible = "ti,lm3533-led";
> +			reg = <0>;
> +			label = "red";
> +
> +			led-max-microamp = <5000>;
> +		};
> +
> +		led@1 {
> +			compatible = "ti,lm3533-led";
> +			reg = <1>;
> +			label = "green";
> +
> +			led-max-microamp = <5000>;
> +		};
> +
> +		led@2 {
> +			compatible = "ti,lm3533-led";
> +			reg = <2>;
> +			label = "blue";
> +
> +			led-max-microamp = <5000>;
> +		};
> +	};
> +
> 

^ permalink raw reply

* Re: [PATCH] iio: adc: Add Renesas GyroADC driver
From: Peter Meerwald-Stadler @ 2016-12-30 19:50 UTC (permalink / raw)
  To: Marek Vasut
  Cc: linux-iio-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Geert Uytterhoeven,
	Simon Horman
In-Reply-To: <20161230191800.2532-1-marek.vasut-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>


> Add IIO driver for the Renesas RCar GyroADC block. This block is a
> simple 4/8-channel ADC which samples 12/15/24 bits of data every
> cycle from all channels.

comments below
 
> Signed-off-by: Marek Vasut <marek.vasut-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> Cc: Geert Uytterhoeven <geert+renesas-gXvu3+zWzMSzQB+pC5nmwQ@public.gmane.org>
> Cc: Simon Horman <horms+renesas-/R6kz+dDXgpPR4JQBCEnsQ@public.gmane.org>
> ---
>  .../bindings/iio/adc/renesas,gyroadc.txt           |  38 +++
>  MAINTAINERS                                        |   6 +
>  drivers/iio/adc/Kconfig                            |  10 +
>  drivers/iio/adc/Makefile                           |   1 +
>  drivers/iio/adc/rcar_gyro_adc.c                    | 379 +++++++++++++++++++++
>  5 files changed, 434 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/iio/adc/renesas,gyroadc.txt
>  create mode 100644 drivers/iio/adc/rcar_gyro_adc.c
> 
> diff --git a/Documentation/devicetree/bindings/iio/adc/renesas,gyroadc.txt b/Documentation/devicetree/bindings/iio/adc/renesas,gyroadc.txt
> new file mode 100644
> index 0000000..3fd5f57
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/renesas,gyroadc.txt
> @@ -0,0 +1,38 @@
> +* Renesas RCar GyroADC device driver
> +
> +Required properties:
> +- compatible:	Should be "renesas,rcar-gyroadc" for regular GyroADC or
> +		"renesas,rcar-gyroadc-r8a7792" for GyroADC without interrupt
> +		block found in R8A7792.
> +- reg:		Address and length of the register set for the device
> +- clocks:	References to all the clocks specified in the clock-names
> +		property as specified in
> +		Documentation/devicetree/bindings/clock/clock-bindings.txt.
> +- clock-names:	Shall contain "fck" and "if". The "fck" are the GyroADC block

"fck" is...

> +		clock, the "if" are the interface clock.

"if" is ...

> +		power-domains = <&sysc R8A7791_PD_ALWAYS_ON>;

this is just an example and not appropriate here?

> +- power-domains: Must contain a reference to the PM domain, if available.
> +- renesas,gyroadc-mode:	GyroADC mode of operation, must be either of:
> +			1 - MB88101A mode, 12bit sampling, 4 channels
> +			2 - ADCS7476 mode, 15bit sampling, 8 channels
> +			3 - MAX1162 mode,  16bit sampling, 8 channels
> +- renesas,gyroadc-vref:	Array of reference voltage values for each input to
> +			the GyroADC, in uV. Array must have 4 elemenets for

elements

> +			mode 1 and 8 elements for mode 2 and 3.
> +
> +Example:
> +	&adc {
> +		compatible = "renesas,rcar-gyroadc";
> +		reg = <0 0xe6e54000 0 64>;
> +		clocks = <&mstp9_clks R8A7791_CLK_GYROADC>, <&clk_65m>;
> +		clock-names = "fck", "if";
> +		power-domains = <&sysc R8A7791_PD_ALWAYS_ON>;
> +
> +		pinctrl-0 = <&adc_pins>;
> +		pinctrl-names = "default";
> +
> +		renesas,gyroadc-vref = <4096000 4096000 4096000 4096000
> +					4096000 4096000 4096000 4096000>;
> +		renesas,gyroadc-mode = <3>;
> +		status = "okay";
> +	};
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 162d904..751e760 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -10271,6 +10271,12 @@ L:	linux-renesas-soc-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
>  F:	drivers/net/ethernet/renesas/
>  F:	include/linux/sh_eth.h
>  
> +RENESAS RCAR GYROADC DRIVER
> +M:	Marek Vasut <marek.vasut-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> +L:	linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> +S:	Supported
> +F:	drivers/iio/adc/rcar_gyro_adc.c
> +
>  RENESAS USB2 PHY DRIVER
>  M:	Yoshihiro Shimoda <yoshihiro.shimoda.uh-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>
>  L:	linux-renesas-soc-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 99c0514..4a4cac7 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -408,6 +408,16 @@ config QCOM_SPMI_VADC
>  	  To compile this driver as a module, choose M here: the module will
>  	  be called qcom-spmi-vadc.
>  
> +config RCAR_GYRO_ADC
> +	tristate "Renesas RCAR GyroADC driver"
> +	depends on ARCH_RCAR_GEN2 || (ARM && COMPILE_TEST)
> +	help
> +	  Say yes here to build support for the GyroADC found in Renesas
> +	  RCar Gen2 SoCs.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called rcar-gyroadc.

called rcar_gyro_adc?

> +
>  config ROCKCHIP_SARADC
>  	tristate "Rockchip SARADC driver"
>  	depends on ARCH_ROCKCHIP || (ARM && COMPILE_TEST)
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index 7a40c04..253aeb2 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -39,6 +39,7 @@ obj-$(CONFIG_NAU7802) += nau7802.o
>  obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
>  obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
>  obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
> +obj-$(CONFIG_RCAR_GYRO_ADC) += rcar_gyro_adc.o
>  obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
>  obj-$(CONFIG_STX104) += stx104.o
>  obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
> diff --git a/drivers/iio/adc/rcar_gyro_adc.c b/drivers/iio/adc/rcar_gyro_adc.c
> new file mode 100644
> index 0000000..a74b148
> --- /dev/null
> +++ b/drivers/iio/adc/rcar_gyro_adc.c
> @@ -0,0 +1,379 @@
> +/*
> + * Renesas RCar GyroADC driver
> + *
> + * Copyright 2016 Marek Vasut <marek.vasut-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> + *
> + * 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/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/clk.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/of_platform.h>
> +#include <linux/err.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/sysfs.h>
> +#include <linux/iio/trigger.h>
> +
> +/* GyroADC registers. */
> +#define RCAR_GYROADC_MODE_SELECT		0x00
> +#define RCAR_GYROADC_MODE_SELECT_1_MB88101A	0x0
> +#define RCAR_GYROADC_MODE_SELECT_2_ADCS7476	0x1
> +#define RCAR_GYROADC_MODE_SELECT_3_MAX1162	0x3
> +
> +#define RCAR_GYROADC_START_STOP			0x04
> +#define RCAR_GYROADC_START_STOP_START		BIT(0)
> +
> +#define RCAR_GYROADC_CLOCK_LENGTH		0x08
> +#define RCAR_GYROADC_1_25MS_LENGTH		0x0c
> +
> +#define RCAR_GYROADC_REALTIME_DATA(ch)		(0x10 + ((ch) * 4))
> +#define RCAR_GYROADC_100MS_ADDED_DATA(ch)	(0x30 + ((ch) * 4))
> +#define RCAR_GYROADC_10MS_AVG_DATA(ch)		(0x50 + ((ch) * 4))
> +
> +#define RCAR_GYROADC_FIFO_STATUS		0x70
> +#define RCAR_GYROADC_FIFO_STATUS_EMPTY(ch)	BIT(0 + (4 * (ch)))

FIFO_STATUS_... is not used (yet)
4*ch looks suspicious for ch==8??

> +#define RCAR_GYROADC_FIFO_STATUS_FULL(ch)	BIT(1 + (4 * (ch)))
> +#define RCAR_GYROADC_FIFO_STATUS_ERROR(ch)	BIT(2 + (4 * (ch)))
> +
> +#define RCAR_GYROADC_INTR			0x74
> +#define RCAR_GYROADC_INTR_INT			BIT(0)
> +
> +#define RCAR_GYROADC_INTENR			0x78
> +#define RCAR_GYROADC_INTENR_INTEN		BIT(0)
> +
> +#define RCAR_GYROADC_SAMPLE_RATE		800	/* Hz */
> +
> +enum rcar_gyroadc_model {
> +	RCAR_GYROADC_MODEL_DEFAULT,
> +	RCAR_GYROADC_MODEL_R8A7792,
> +};
> +
> +struct rcar_gyroadc {
> +	struct device		*dev;
> +	void __iomem		*regs;
> +	struct clk		*fclk;
> +	struct clk		*clk;
> +	enum rcar_gyroadc_model	model;
> +	unsigned int		mode;
> +	u32			vref_uv[8];
> +	u32			buffer[8];
> +};
> +
> +static void rcar_gyroadc_hw_init(struct rcar_gyroadc *priv)
> +{
> +	unsigned long clk_mhz = clk_get_rate(priv->clk) / 1000000;
> +
> +	/* Stop the GyroADC. */
> +	writel(0, priv->regs + RCAR_GYROADC_START_STOP);
> +
> +	/* Disable IRQ, except on V2H. */
> +	if (priv->model != RCAR_GYROADC_MODEL_R8A7792)
> +		writel(0, priv->regs + RCAR_GYROADC_INTENR);
> +
> +	/* Set mode and timing. */
> +	writel(priv->mode, priv->regs + RCAR_GYROADC_MODE_SELECT);
> +
> +	if (priv->mode == RCAR_GYROADC_MODE_SELECT_1_MB88101A)
> +		writel(clk_mhz * 10, priv->regs + RCAR_GYROADC_CLOCK_LENGTH);
> +	else if (priv->mode == RCAR_GYROADC_MODE_SELECT_2_ADCS7476)
> +		writel(clk_mhz * 5, priv->regs + RCAR_GYROADC_CLOCK_LENGTH);
> +	else if (priv->mode == RCAR_GYROADC_MODE_SELECT_3_MAX1162)
> +		writel(clk_mhz * 5, priv->regs + RCAR_GYROADC_CLOCK_LENGTH);
> +	writel(clk_mhz * 1250, priv->regs + RCAR_GYROADC_1_25MS_LENGTH);
> +
> +	/*
> +	 * We can possibly turn the sampling on/off on-demand to reduce power
> +	 * consumption, but for the sake of quick availability of samples, we
> +	 * don't do it now.
> +	 */
> +	writel(RCAR_GYROADC_START_STOP_START,
> +	       priv->regs + RCAR_GYROADC_START_STOP);
> +
> +	/* Wait for the first conversion to complete. */
> +	udelay(1250);
> +}
> +
> +#define RCAR_GYROADC_CHAN(_idx, _chan_type, _realbits) {	\
> +	.type			= (_chan_type),			\

_chan_type is IIO_VOLTAGE always?

> +	.indexed		= 1,				\
> +	.channel		= (_idx),			\
> +	.info_mask_separate	= BIT(IIO_CHAN_INFO_RAW),	\
> +	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) |	\
> +				    BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> +	.scan_index		= (_idx),			\

no buffered mode yet, so strictly no need for a scan_index and scan_type

> +	.scan_type	= {					\
> +		.sign		= 'u',				\
> +		.realbits	= (_realbits),			\
> +		.storagebits	= 16,				\
> +	},							\
> +}
> +
> +static const struct iio_chan_spec rcar_gyroadc_iio_channels_1[] = {
> +	RCAR_GYROADC_CHAN(0, IIO_VOLTAGE, 12),
> +	RCAR_GYROADC_CHAN(1, IIO_VOLTAGE, 12),
> +	RCAR_GYROADC_CHAN(2, IIO_VOLTAGE, 12),
> +	RCAR_GYROADC_CHAN(3, IIO_VOLTAGE, 12),
> +};
> +
> +static const struct iio_chan_spec rcar_gyroadc_iio_channels_2[] = {
> +	RCAR_GYROADC_CHAN(0, IIO_VOLTAGE, 15),
> +	RCAR_GYROADC_CHAN(1, IIO_VOLTAGE, 15),
> +	RCAR_GYROADC_CHAN(2, IIO_VOLTAGE, 15),
> +	RCAR_GYROADC_CHAN(3, IIO_VOLTAGE, 15),
> +	RCAR_GYROADC_CHAN(4, IIO_VOLTAGE, 15),
> +	RCAR_GYROADC_CHAN(5, IIO_VOLTAGE, 15),
> +	RCAR_GYROADC_CHAN(6, IIO_VOLTAGE, 15),
> +	RCAR_GYROADC_CHAN(7, IIO_VOLTAGE, 15),
> +};
> +
> +/*
> + * NOTE: The data we receive in mode 3 from MAX1162 have MSByte = 0,
> + *       therefore we only use 16bit realbits here instead of 24.
> + */
> +static const struct iio_chan_spec rcar_gyroadc_iio_channels_3[] = {
> +	RCAR_GYROADC_CHAN(0, IIO_VOLTAGE, 16),
> +	RCAR_GYROADC_CHAN(1, IIO_VOLTAGE, 16),
> +	RCAR_GYROADC_CHAN(2, IIO_VOLTAGE, 16),
> +	RCAR_GYROADC_CHAN(3, IIO_VOLTAGE, 16),
> +	RCAR_GYROADC_CHAN(4, IIO_VOLTAGE, 16),
> +	RCAR_GYROADC_CHAN(5, IIO_VOLTAGE, 16),
> +	RCAR_GYROADC_CHAN(6, IIO_VOLTAGE, 16),
> +	RCAR_GYROADC_CHAN(7, IIO_VOLTAGE, 16),
> +};
> +
> +static int rcar_gyroadc_read_raw(struct iio_dev *indio_dev,
> +				 struct iio_chan_spec const *chan,
> +				 int *val, int *val2, long mask)
> +{
> +	struct rcar_gyroadc *priv = iio_priv(indio_dev);
> +	unsigned int datareg = RCAR_GYROADC_REALTIME_DATA(chan->channel);
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +		if (chan->type != IIO_VOLTAGE)
> +			return -EINVAL;
> +
> +		if (iio_buffer_enabled(indio_dev))
> +			return -EBUSY;

use iio_device_claim_direct_mode()

> +
> +		*val = readl(priv->regs + datareg);
> +		*val &= BIT(chan->scan_type.realbits) - 1;
> +
> +		return IIO_VAL_INT;
> +	case IIO_CHAN_INFO_SCALE:
> +		*val = 0;
> +		*val2 = (priv->vref_uv[chan->channel] * 1000) / 0x10000;
> +		return IIO_VAL_INT_PLUS_NANO;
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		*val = RCAR_GYROADC_SAMPLE_RATE;
> +		*val2 = 0;

*val2 = 0 not needed

> +		return IIO_VAL_INT;
> +	default:
> +		break;

return -EINVAL;
here directly

> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int rcar_gyroadc_reg_access(struct iio_dev *indio_dev,
> +				   unsigned int reg, unsigned int writeval,
> +				   unsigned int *readval)
> +{
> +	struct rcar_gyroadc *priv = iio_priv(indio_dev);
> +	unsigned int maxreg = RCAR_GYROADC_INTENR;
> +
> +	if (readval == NULL)
> +		return -EINVAL;
> +
> +	if (reg % 4)
> +		return -EINVAL;
> +
> +	/* Handle the V2H case with missing interrupt block. */
> +	if (priv->model == RCAR_GYROADC_MODEL_R8A7792)
> +		maxreg = RCAR_GYROADC_FIFO_STATUS;
> +
> +	if (reg > maxreg)
> +		return -EINVAL;
> +
> +	*readval = readl(priv->regs + reg);
> +
> +	return 0;
> +}
> +
> +static const struct iio_info rcar_gyroadc_iio_info = {
> +	.driver_module		= THIS_MODULE,
> +	.read_raw		= rcar_gyroadc_read_raw,
> +	.debugfs_reg_access	= rcar_gyroadc_reg_access,
> +};
> +
> +static const struct of_device_id rcar_gyroadc_match[] = {
> +	{
> +		/* RCar Gen2 compatible GyroADC */
> +		.compatible	= "renesas,rcar-gyroadc",
> +		.data		= (void *)RCAR_GYROADC_MODEL_DEFAULT,
> +	}, {
> +		/* RCar V2H specialty without interrupt registers. */
> +		.compatible	= "renesas,rcar-gyroadc-r8a7792",
> +		.data		= (void *)RCAR_GYROADC_MODEL_R8A7792,
> +	}, {
> +		/* sentinel */
> +	}
> +};
> +
> +MODULE_DEVICE_TABLE(of, rcar_gyroadc_match);
> +
> +static int rcar_gyroadc_probe(struct platform_device *pdev)
> +{
> +	const struct of_device_id *of_id =
> +		of_match_device(rcar_gyroadc_match, &pdev->dev);
> +	struct device *dev = &pdev->dev;
> +	struct rcar_gyroadc *priv;
> +	struct iio_dev *indio_dev;
> +	struct resource *mem;
> +	int ret, mode;
> +
> +	indio_dev = devm_iio_device_alloc(dev, sizeof(*priv));
> +	if (!indio_dev) {
> +		dev_err(dev, "Failed to allocate IIO device.\n");
> +		return -ENOMEM;
> +	}
> +
> +	priv = iio_priv(indio_dev);
> +	priv->dev = dev;
> +
> +	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	priv->regs = devm_ioremap_resource(dev, mem);
> +	if (IS_ERR(priv->regs))
> +		return PTR_ERR(priv->regs);
> +
> +	priv->fclk = devm_clk_get(dev, "fck");
> +	if (IS_ERR(priv->fclk)) {
> +		ret = PTR_ERR(priv->fclk);
> +		dev_err(dev, "Failed to get FCK clock (ret=%i)\n", ret);
> +		return ret;
> +	}
> +
> +	priv->clk = devm_clk_get(dev, "if");
> +	if (IS_ERR(priv->clk)) {
> +		ret = PTR_ERR(priv->clk);
> +		dev_err(dev, "Failed to get IF clock (ret=%i)\n", ret);
> +		return ret;
> +	}
> +
> +	ret = of_property_read_u32(pdev->dev.of_node, "renesas,gyroadc-mode",
> +				   &mode);
> +	if (ret || (mode < 1) || (mode > 3)) {

the mode check could be a simple 'else' below?

> +		dev_err(dev, "Failed to get GyroADC mode (ret=%i)\n", ret);
> +		return ret;
> +	}
> +
> +	if (mode == 1)
> +		priv->mode = RCAR_GYROADC_MODE_SELECT_1_MB88101A;
> +	else if (mode == 2)
> +		priv->mode = RCAR_GYROADC_MODE_SELECT_2_ADCS7476;
> +	else if (mode == 3)
> +		priv->mode = RCAR_GYROADC_MODE_SELECT_3_MAX1162;
> +
> +	of_property_read_u32_array(pdev->dev.of_node, "renesas,gyroadc-vref",
> +				   priv->vref_uv, (mode == 1) ? 4 : 8);
> +
> +	priv->model = (enum rcar_gyroadc_model)of_id->data;
> +
> +	platform_set_drvdata(pdev, indio_dev);
> +
> +	indio_dev->name = dev_name(dev);
> +	indio_dev->dev.parent = dev;
> +	indio_dev->dev.of_node = pdev->dev.of_node;
> +	indio_dev->info = &rcar_gyroadc_iio_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	if (mode == 1) {

maybe do the mode differentiation only once, any with a switch?

> +		indio_dev->channels = rcar_gyroadc_iio_channels_1;
> +		indio_dev->num_channels =
> +			ARRAY_SIZE(rcar_gyroadc_iio_channels_1);
> +	} else if (mode == 2) {
> +		indio_dev->channels = rcar_gyroadc_iio_channels_2;
> +		indio_dev->num_channels =
> +			ARRAY_SIZE(rcar_gyroadc_iio_channels_2);
> +	} else if (mode == 3) {
> +		indio_dev->channels = rcar_gyroadc_iio_channels_3;
> +		indio_dev->num_channels =
> +			ARRAY_SIZE(rcar_gyroadc_iio_channels_3);
> +	}
> +
> +	ret = clk_prepare_enable(priv->fclk);
> +	if (ret) {
> +		dev_err(dev, "Could not prepare or enable the FCK clock.\n");
> +		return ret;
> +	}
> +
> +	ret = clk_prepare_enable(priv->clk);
> +	if (ret) {
> +		dev_err(dev, "Could not prepare or enable the IF clock.\n");
> +		goto error_clk_if_enable;
> +	}
> +
> +	rcar_gyroadc_hw_init(priv);
> +
> +	ret = iio_device_register(indio_dev);
> +	if (ret) {
> +		dev_err(dev, "Couldn't register IIO device.\n");
> +		goto error_iio_device_register;
> +	}
> +
> +	return 0;
> +
> +error_iio_device_register:
> +	clk_disable_unprepare(priv->clk);
> +error_clk_if_enable:
> +	clk_disable_unprepare(priv->fclk);
> +	return ret;
> +}
> +
> +static int rcar_gyroadc_remove(struct platform_device *pdev)
> +{
> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> +	struct rcar_gyroadc *priv = iio_priv(indio_dev);
> +
> +	/* Stop sampling */
> +	writel(0, priv->regs + RCAR_GYROADC_START_STOP);
> +
> +	iio_device_unregister(indio_dev);
> +	clk_disable_unprepare(priv->clk);
> +	clk_disable_unprepare(priv->fclk);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver rcar_gyroadc_driver = {
> +	.probe          = rcar_gyroadc_probe,
> +	.remove         = rcar_gyroadc_remove,
> +	.driver         = {
> +		.name		= "rcar-gyroadc",
> +		.of_match_table	= rcar_gyroadc_match,
> +	},
> +};
> +
> +module_platform_driver(rcar_gyroadc_driver);
> +
> +MODULE_AUTHOR("Marek Vasut <marek.vasut-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>");
> +MODULE_DESCRIPTION("Renesas RCAR GyroADC driver");
> +MODULE_LICENSE("GPL v2");
> 

-- 

Peter Meerwald-Stadler
+43-664-2444418 (mobile)
--
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 linux 2/6] hwmon: occ: Add sysfs interface
From: Guenter Roeck @ 2016-12-30 19:34 UTC (permalink / raw)
  To: eajames.ibm
  Cc: jdelvare, corbet, mark.rutland, robh+dt, wsa, andrew, joel,
	devicetree, linux-doc, linux-hwmon, linux-i2c, linux-kernel,
	Edward A. James
In-Reply-To: <1483120568-21082-3-git-send-email-eajames.ibm@gmail.com>

On Fri, Dec 30, 2016 at 11:56:04AM -0600, eajames.ibm@gmail.com wrote:
> From: "Edward A. James" <eajames@us.ibm.com>
> 
> Add a generic mechanism to expose the sensors provided by the OCC in
> sysfs.
> 
> Signed-off-by: Edward A. James <eajames@us.ibm.com>
> Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
> Reviewed-by: Andrew Jeffery <andrew@aj.id.au>
> ---
>  Documentation/hwmon/occ       |  48 +++++
>  drivers/hwmon/occ/Makefile    |   2 +-
>  drivers/hwmon/occ/occ_sysfs.c | 492 ++++++++++++++++++++++++++++++++++++++++++
>  drivers/hwmon/occ/occ_sysfs.h |  52 +++++
>  4 files changed, 593 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/hwmon/occ/occ_sysfs.c
>  create mode 100644 drivers/hwmon/occ/occ_sysfs.h
> 
> diff --git a/Documentation/hwmon/occ b/Documentation/hwmon/occ
> index 79d1642..1ee8689 100644
> --- a/Documentation/hwmon/occ
> +++ b/Documentation/hwmon/occ
> @@ -25,6 +25,54 @@ Currently, all versions of the OCC support four types of sensor data: power,
>  temperature, frequency, and "caps," which indicate limits and thresholds used
>  internally on the OCC.
>  
> +sysfs Entries
> +-------------
> +
> +The OCC driver uses the hwmon sysfs framework to provide data to userspace.
> +
> +The driver exports two sysfs files for each frequency, temperature, and power
> +sensor. These are "input" and "label". The input file contains the value of the
> +sensor. The label file contains the sensor id. The sensor id is the unique
> +internal OCC identifier. Sensor ids may be provided by the OCC specification.
> +The names of these files will be in the following format:
> +	<sensor type><sensor index>_input
> +	<sensor type><sensor index>_label
> +Sensor types will be one of "temp", "freq", or "power". The sensor index is
> +an index to differentiate different sensor files. For example, a single
> +temperature sensor will have two sysfs files: temp1_input and temp1_label.
> +
> +Caps sensors are exported differently. For each caps sensor, the driver will
> +export 6 entries:
> +	curr_powercap - current power cap in watts
> +	curr_powerreading - current power output in watts
> +	norm_powercap - power cap without redundant power
> +	max_powercap - maximum power cap that can be set in watts
> +	min_powercap - minimum power cap that can be set in watts
> +	user_powerlimit - power limit specified by the user in watts
> +In addition, the OCC driver for P9 will export a 7th entry:
> +	user_powerlimit_source - can be one of two values depending on who set
> +		the user_powerlimit. 0x1 - out of band from BMC or host. 0x2 -
> +		in band from other source.
> +The format for these files is caps<sensor index>_<entry type>. For example,
> +caps1_curr_powercap.
> +
> +The driver also provides a number of sysfs entries through hwmon to better
> +control the driver and monitor the OCC.
> +	powercap - read or write the OCC user power limit in watts.
> +	name - read the name of the driver
> +	update_interval - read or write the minimum interval for polling the
> +		OCC.
> +
> +The driver also exports a single sysfs file through the communication protocol
> +device (see BMC - Host Communications). The filename is "online" and represents
> +the status of the OCC with respect to the driver. The OCC can be in one of two
> +states: OCC polling enabled or OCC polling disabled. The purpose of this file
> +is to control the behavior of the driver and it's hwmon sysfs entries, not to
> +infer any information about the state of the physical OCC. Reading the file
> +returns either a 0 (polling disabled) or 1 (polling enabled). Writing 1 to the
> +file enables OCC polling in the driver if communications can be established
> +with the OCC. Writing a 0 to the driver disables OCC polling.
> +
>  BMC - Host Communications
>  -------------------------
>  
> diff --git a/drivers/hwmon/occ/Makefile b/drivers/hwmon/occ/Makefile
> index 93cb52f..a6881f9 100644
> --- a/drivers/hwmon/occ/Makefile
> +++ b/drivers/hwmon/occ/Makefile
> @@ -1 +1 @@
> -obj-$(CONFIG_SENSORS_PPC_OCC) += occ.o
> +obj-$(CONFIG_SENSORS_PPC_OCC) += occ.o occ_sysfs.o
> diff --git a/drivers/hwmon/occ/occ_sysfs.c b/drivers/hwmon/occ/occ_sysfs.c
> new file mode 100644
> index 0000000..b0e063da
> --- /dev/null
> +++ b/drivers/hwmon/occ/occ_sysfs.c
> @@ -0,0 +1,492 @@
> +/*
> + * occ_sysfs.c - OCC sysfs interface
> + *
> + * This file contains the methods and data structures for implementing the OCC
> + * hwmon sysfs entries.
> + *
> + * Copyright 2016 IBM Corp.
> + *
> + * 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/module.h>
> +#include <linux/init.h>
> +#include <linux/slab.h>
> +#include <linux/jiffies.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/err.h>
> +#include <linux/mutex.h>
> +#include <linux/delay.h>
> +#include <linux/kernel.h>
> +#include <linux/device.h>
> +
> +#include "occ_sysfs.h"
> +
> +#define MAX_SENSOR_ATTR_LEN	32
> +
> +#define RESP_RETURN_CMD_INVAL	0x13
> +
> +struct sensor_attr_data {
> +	enum sensor_type type;
> +	u32 hwmon_index;
> +	u32 attr_id;
> +	char name[MAX_SENSOR_ATTR_LEN];
> +	struct device_attribute dev_attr;
> +};
> +
> +static ssize_t show_input(struct device *dev,
> +			  struct device_attribute *attr, char *buf)
> +{
> +	int val;
> +	struct sensor_attr_data *sdata = container_of(attr,
> +						      struct sensor_attr_data,
> +						      dev_attr);
> +	struct occ_sysfs *driver = dev_get_drvdata(dev);
> +
> +	val = occ_get_sensor_value(driver->occ, sdata->type,
> +				   sdata->hwmon_index - 1);
> +	if (sdata->type == TEMP)
> +		val *= 1000;	/* in millidegree Celsius */
> +
> +	return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
> +}
> +
> +/* show_label provides the OCC sensor id. The sensor id will be either a
> + * 2-byte (for P8) or 4-byte (for P9) value. The sensor id is a way to
> + * identify what each sensor represents, according to the OCC specification.
> + */
> +static ssize_t show_label(struct device *dev,
> +			  struct device_attribute *attr, char *buf)
> +{
> +	int val;
> +	struct sensor_attr_data *sdata = container_of(attr,
> +						      struct sensor_attr_data,
> +						      dev_attr);
> +	struct occ_sysfs *driver = dev_get_drvdata(dev);
> +
> +	val = occ_get_sensor_id(driver->occ, sdata->type,
> +				sdata->hwmon_index - 1);
> +
> +	return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
> +}
> +
> +static ssize_t show_caps(struct device *dev,
> +			 struct device_attribute *attr, char *buf)
> +{
> +	int val;
> +	struct caps_sensor *sensor;
> +	struct sensor_attr_data *sdata = container_of(attr,
> +						      struct sensor_attr_data,
> +						      dev_attr);
> +	struct occ_sysfs *driver = dev_get_drvdata(dev);
> +
> +	sensor = occ_get_sensor(driver->occ, CAPS);
> +	if (!sensor) {
> +		val = -1;
> +		return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
> +	}
> +
> +	val = occ_get_caps_value(driver->occ, sensor, sdata->hwmon_index - 1,
> +				 sdata->attr_id);
> +
> +	return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
> +}
> +
> +static ssize_t show_update_interval(struct device *dev,
> +				    struct device_attribute *attr, char *buf)
> +{
> +	struct occ_sysfs *driver = dev_get_drvdata(dev);
> +
> +	return snprintf(buf, PAGE_SIZE - 1, "%lu\n", driver->update_interval);
> +}
> +
> +static ssize_t store_update_interval(struct device *dev,
> +				     struct device_attribute *attr,
> +				     const char *buf, size_t count)
> +{
> +	struct occ_sysfs *driver = dev_get_drvdata(dev);
> +	unsigned long val;
> +	int rc;
> +
> +	rc = kstrtoul(buf, 10, &val);
> +	if (rc)
> +		return rc;
> +
> +	driver->update_interval = val;
> +	occ_set_update_interval(driver->occ, val);
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR(update_interval, S_IWUSR | S_IRUGO, show_update_interval,
> +		   store_update_interval);
> +
> +static ssize_t show_name(struct device *dev,
> +			 struct device_attribute *attr, char *buf)
> +{
> +	return snprintf(buf, PAGE_SIZE - 1, "occ\n");
> +}
> +
> +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
> +
> +static ssize_t show_user_powercap(struct device *dev,
> +				  struct device_attribute *attr, char *buf)
> +{
> +	struct occ_sysfs *driver = dev_get_drvdata(dev);
> +
> +	return snprintf(buf, PAGE_SIZE - 1, "%u\n", driver->user_powercap);
> +}
> +
> +static ssize_t store_user_powercap(struct device *dev,
> +				   struct device_attribute *attr,
> +				   const char *buf, size_t count)
> +{
> +	struct occ_sysfs *driver = dev_get_drvdata(dev);
> +	u16 val;
> +	int rc;
> +
> +	rc = kstrtou16(buf, 10, &val);
> +	if (rc)
> +		return rc;
> +
> +	dev_dbg(dev, "set user powercap to: %d\n", val);
> +	rc = occ_set_user_powercap(driver->occ, val);
> +	if (rc) {
> +		dev_err(dev, "set user powercap failed: 0x%x\n", rc);
> +		if (rc == RESP_RETURN_CMD_INVAL) {
> +			dev_err(dev, "set invalid powercap value: %d\n", val);
> +			return -EINVAL;
> +		}
> +
> +		return rc;
> +	}
> +
> +	driver->user_powercap = val;
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR(user_powercap, S_IWUSR | S_IRUGO, show_user_powercap,
> +		   store_user_powercap);
> +
> +static void deinit_sensor_groups(struct device *dev,
> +				 struct sensor_group *sensor_groups)
> +{
> +	int i;
> +
> +	for (i = 0; i < MAX_OCC_SENSOR_TYPE; i++) {
> +		if (sensor_groups[i].group.attrs)
> +			devm_kfree(dev, sensor_groups[i].group.attrs);
> +		if (sensor_groups[i].sattr)
> +			devm_kfree(dev, sensor_groups[i].sattr);
> +		sensor_groups[i].group.attrs = NULL;
> +		sensor_groups[i].sattr = NULL;
> +	}
> +}
> +
> +static void sensor_attr_init(struct sensor_attr_data *sdata,
> +			     char *sensor_group_name,
> +			     char *attr_name,
> +			     ssize_t (*show)(struct device *dev,
> +					     struct device_attribute *attr,
> +					     char *buf))
> +{
> +	sysfs_attr_init(&sdata->dev_attr.attr);
> +
> +	snprintf(sdata->name, MAX_SENSOR_ATTR_LEN, "%s%d_%s",
> +		 sensor_group_name, sdata->hwmon_index, attr_name);
> +	sdata->dev_attr.attr.name = sdata->name;
> +	sdata->dev_attr.attr.mode = S_IRUGO;
> +	sdata->dev_attr.show = show;
> +}
> +
> +static int create_sensor_group(struct occ_sysfs *driver,
> +			       enum sensor_type type, int sensor_num)
> +{
> +	struct device *dev = driver->dev;
> +	struct sensor_group *sensor_groups = driver->sensor_groups;
> +	struct sensor_attr_data *sdata;
> +	int rc, i;
> +
> +	/* each sensor has 'label' and 'input' attributes */
> +	sensor_groups[type].group.attrs =
> +		devm_kzalloc(dev, sizeof(struct attribute *) *
> +			     sensor_num * 2 + 1, GFP_KERNEL);
> +	if (!sensor_groups[type].group.attrs) {
> +		rc = -ENOMEM;
> +		goto err;
> +	}
> +
> +	sensor_groups[type].sattr =
> +		devm_kzalloc(dev, sizeof(struct sensor_attr_data) *
> +			     sensor_num * 2, GFP_KERNEL);
> +	if (!sensor_groups[type].sattr) {
> +		rc = -ENOMEM;
> +		goto err;
> +	}
> +
> +	for (i = 0; i < sensor_num; i++) {
> +		sdata = &sensor_groups[type].sattr[i];
> +		/* hwmon attributes index starts from 1 */
> +		sdata->hwmon_index = i + 1;
> +		sdata->type = type;
> +		sensor_attr_init(sdata, sensor_groups[type].name, "input",
> +				 show_input);
> +		sensor_groups[type].group.attrs[i] = &sdata->dev_attr.attr;
> +
> +		sdata = &sensor_groups[type].sattr[i + sensor_num];
> +		sdata->hwmon_index = i + 1;
> +		sdata->type = type;
> +		sensor_attr_init(sdata, sensor_groups[type].name, "label",
> +				 show_label);
> +		sensor_groups[type].group.attrs[i + sensor_num] =
> +			&sdata->dev_attr.attr;
> +	}
> +
> +	rc = sysfs_create_group(&dev->kobj, &sensor_groups[type].group);
> +	if (rc)
> +		goto err;
> +
> +	return 0;
> +err:
> +	deinit_sensor_groups(dev, sensor_groups);
> +	return rc;
> +}
> +
> +static void caps_sensor_attr_init(struct sensor_attr_data *sdata,
> +				  char *attr_name, uint32_t hwmon_index,
> +				  uint32_t attr_id)
> +{
> +	sdata->type = CAPS;
> +	sdata->hwmon_index = hwmon_index;
> +	sdata->attr_id = attr_id;
> +
> +	snprintf(sdata->name, MAX_SENSOR_ATTR_LEN, "%s%d_%s",
> +		 "caps", sdata->hwmon_index, attr_name);
> +
> +	sysfs_attr_init(&sdata->dev_attr.attr);
> +	sdata->dev_attr.attr.name = sdata->name;
> +	sdata->dev_attr.attr.mode = S_IRUGO;
> +	sdata->dev_attr.show = show_caps;
> +}
> +
> +static int create_caps_sensor_group(struct occ_sysfs *driver, int sensor_num)
> +{
> +	struct device *dev = driver->dev;
> +	struct sensor_group *sensor_groups = driver->sensor_groups;
> +	int field_num = driver->num_caps_fields;
> +	struct sensor_attr_data *sdata;
> +	int i, j, rc;
> +
> +	sensor_groups[CAPS].group.attrs =
> +		devm_kzalloc(dev, sizeof(struct attribute *) * sensor_num *
> +			     field_num + 1, GFP_KERNEL);
> +	if (!sensor_groups[CAPS].group.attrs) {
> +		rc = -ENOMEM;
> +		goto err;
> +	}
> +
> +	sensor_groups[CAPS].sattr =
> +		devm_kzalloc(dev, sizeof(struct sensor_attr_data) *
> +			     sensor_num * field_num, GFP_KERNEL);
> +	if (!sensor_groups[CAPS].sattr) {
> +		rc = -ENOMEM;
> +		goto err;
> +	}
> +
> +	for (j = 0; j < sensor_num; ++j) {
> +		for (i = 0; i < field_num; ++i) {
> +			sdata = &sensor_groups[CAPS].sattr[j * field_num + i];
> +			caps_sensor_attr_init(sdata,
> +					      driver->caps_names[i], j + 1, i);
> +			sensor_groups[CAPS].group.attrs[j * field_num + i] =
> +				&sdata->dev_attr.attr;
> +		}
> +	}
> +
> +	rc = sysfs_create_group(&dev->kobj, &sensor_groups[CAPS].group);
> +	if (rc)
> +		goto err;
> +
> +	return rc;
> +err:
> +	deinit_sensor_groups(dev, sensor_groups);
> +	return rc;
> +}
> +
> +static void occ_remove_hwmon_attrs(struct occ_sysfs *driver)
> +{
> +	struct device *dev = driver->dev;
> +
> +	device_remove_file(dev, &dev_attr_user_powercap);
> +	device_remove_file(dev, &dev_attr_update_interval);
> +	device_remove_file(dev, &dev_attr_name);
> +}
> +
> +static int occ_create_hwmon_attrs(struct occ_sysfs *driver)
> +{
> +	int i, rc, id, sensor_num;
> +	struct device *dev = driver->dev;
> +	struct sensor_group *sensor_groups = driver->sensor_groups;
> +	struct occ_blocks *resp = NULL;
> +
> +	occ_get_response_blocks(driver->occ, &resp);
> +
> +	for (i = 0; i < MAX_OCC_SENSOR_TYPE; ++i)
> +		resp->sensor_block_id[i] = -1;
> +
> +	/* read sensor data from occ */
> +	rc = occ_update_device(driver->occ);
> +	if (rc) {
> +		dev_err(dev, "cannot get occ sensor data: %d\n", rc);
> +		return rc;
> +	}
> +	if (!resp->blocks)
> +		return -ENOMEM;
> +
> +	rc = device_create_file(dev, &dev_attr_name);
> +	if (rc)
> +		goto error;
> +
> +	rc = device_create_file(dev, &dev_attr_update_interval);
> +	if (rc)
> +		goto error;
> +
> +	if (resp->sensor_block_id[CAPS] >= 0) {
> +		/* user powercap: only for master OCC */
> +		rc = device_create_file(dev, &dev_attr_user_powercap);
> +		if (rc)
> +			goto error;
> +	}
> +
> +	sensor_groups[FREQ].name = "freq";
> +	sensor_groups[TEMP].name = "temp";
> +	sensor_groups[POWER].name = "power";
> +	sensor_groups[CAPS].name = "caps";
> +
> +	for (i = 0; i < MAX_OCC_SENSOR_TYPE; i++) {
> +		id = resp->sensor_block_id[i];
> +		if (id < 0)
> +			continue;
> +
> +		sensor_num = resp->blocks[id].header.sensor_num;
> +		if (i == CAPS)
> +			rc = create_caps_sensor_group(driver, sensor_num);
> +		else
> +			rc = create_sensor_group(driver, i, sensor_num);
> +		if (rc)
> +			goto error;
> +	}
> +
> +	return 0;
> +
> +error:
> +	dev_err(dev, "cannot create hwmon attributes: %d\n", rc);
> +	occ_remove_hwmon_attrs(driver);
> +	return rc;
> +}
> +
> +static ssize_t show_occ_online(struct device *dev,
> +			       struct device_attribute *attr, char *buf)
> +{
> +	struct occ_sysfs *driver = dev_get_drvdata(dev);
> +
> +	return snprintf(buf, PAGE_SIZE - 1, "%u\n", driver->occ_online);
> +}
> +
> +static ssize_t store_occ_online(struct device *dev,
> +				struct device_attribute *attr,
> +				const char *buf, size_t count)
> +{
> +	struct occ_sysfs *driver = dev_get_drvdata(dev);
> +	unsigned long val;
> +	int rc;
> +
> +	rc = kstrtoul(buf, 10, &val);
> +	if (rc)
> +		return rc;
> +
> +	if (val == 1) {
> +		if (driver->occ_online)
> +			return count;
> +
> +		driver->dev = hwmon_device_register(dev);

hwmon_device_register() is deprecated. Please consider using
devm_hwmon_device_register_with_info() or at least
hwmon_device_register_with_info().

Uuh ... and registering a hwmon device based on writing into a sysfs attribute
is completely out of the question.

Thanks,
Guenter

> +		if (IS_ERR(driver->dev))
> +			return PTR_ERR(driver->dev);
> +
> +		dev_set_drvdata(driver->dev, driver);
> +
> +		rc = occ_create_hwmon_attrs(driver);
> +		if (rc) {
> +			hwmon_device_unregister(driver->dev);
> +			driver->dev = NULL;
> +			return rc;
> +		}
> +	} else if (val == 0) {
> +		if (!driver->occ_online)
> +			return count;
> +
> +		occ_remove_hwmon_attrs(driver);
> +		hwmon_device_unregister(driver->dev);
> +		driver->dev = NULL;
> +	} else
> +		return -EINVAL;
> +
> +	driver->occ_online = val;
> +	return count;
> +}
> +
> +static DEVICE_ATTR(online, S_IWUSR | S_IRUGO, show_occ_online,
> +		   store_occ_online);
> +
> +struct occ_sysfs *occ_sysfs_start(struct device *dev, struct occ *occ,
> +				  struct occ_sysfs_config *config)
> +{
> +	struct occ_sysfs *hwmon = devm_kzalloc(dev, sizeof(struct occ_sysfs),
> +					       GFP_KERNEL);
> +	int rc;
> +
> +	if (!hwmon)
> +		return ERR_PTR(-ENOMEM);
> +
> +	hwmon->occ = occ;
> +	hwmon->num_caps_fields = config->num_caps_fields;
> +	hwmon->caps_names = config->caps_names;
> +
> +	dev_set_drvdata(dev, hwmon);
> +
> +	rc = device_create_file(dev, &dev_attr_online);
> +	if (rc)
> +		return ERR_PTR(rc);
> +
> +	return hwmon;
> +}
> +EXPORT_SYMBOL(occ_sysfs_start);
> +
> +int occ_sysfs_stop(struct device *dev, struct occ_sysfs *driver)
> +{
> +	if (driver->dev) {
> +		occ_remove_hwmon_attrs(driver);
> +		hwmon_device_unregister(driver->dev);
> +	}
> +
> +	device_remove_file(driver->dev, &dev_attr_online);
> +
> +	devm_kfree(dev, driver);

Thw point of using devm_ functions is not to require remove/free functions.
Something is completely wrong here if you need that call.

Overall, this is architectually completely wrong. One does not register
or instantiate drivers based on writing into sysfs attributes. Please
reconsider your approach.

> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(occ_sysfs_stop);
> +
> +MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>");
> +MODULE_DESCRIPTION("OCC sysfs driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/hwmon/occ/occ_sysfs.h b/drivers/hwmon/occ/occ_sysfs.h
> new file mode 100644
> index 0000000..2a8044f
> --- /dev/null
> +++ b/drivers/hwmon/occ/occ_sysfs.h
> @@ -0,0 +1,52 @@
> +/*
> + * occ_sysfs.h - OCC sysfs interface
> + *
> + * This file contains the data structures and function prototypes for the OCC
> + * hwmon sysfs entries.
> + *
> + * Copyright 2016 IBM Corp.
> + *
> + * 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.
> + */
> +
> +#ifndef __OCC_SYSFS_H__
> +#define __OCC_SYSFS_H__
> +
> +#include "occ.h"
> +
> +struct sensor_group {
> +	char *name;
> +	struct sensor_attr_data *sattr;
> +	struct attribute_group group;
> +};
> +
> +struct occ_sysfs_config {
> +	unsigned int num_caps_fields;
> +	char **caps_names;
> +};
> +
> +struct occ_sysfs {
> +	struct device *dev;
> +	struct occ *occ;
> +
> +	u16 user_powercap;
> +	bool occ_online;
> +	struct sensor_group sensor_groups[MAX_OCC_SENSOR_TYPE];
> +	unsigned long update_interval;
> +	unsigned int num_caps_fields;
> +	char **caps_names;
> +};
> +
> +struct occ_sysfs *occ_sysfs_start(struct device *dev, struct occ *occ,
> +				  struct occ_sysfs_config *config);
> +int occ_sysfs_stop(struct device *dev, struct occ_sysfs *driver);
> +
> +#endif /* __OCC_SYSFS_H__ */
> -- 
> 1.9.1
> 

^ permalink raw reply

* Re: [PATCH linux 1/6] hwmon: Add core On-Chip Controller support for POWER CPUs
From: kbuild test robot @ 2016-12-30 19:18 UTC (permalink / raw)
  To: eajames.ibm-Re5JQEeQqe8AvxtiuMwx3w
  Cc: kbuild-all-JC7UmRfGjtg, linux-0h96xk9xTtrk1uMJSBkQmQ,
	jdelvare-IBi9RG/b67k, corbet-T1hC0tSOHrs,
	mark.rutland-5wv7dgnIgG8, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
	wsa-z923LK4zBo2bacvFa/9K2g, andrew-zrmu5oMJ5Fs,
	joel-U3u1mxZcP9KHXe+LvDLADg, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-doc-u79uwXL29TY76Z2rM5mHXA,
	linux-hwmon-u79uwXL29TY76Z2rM5mHXA,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Edward A. James
In-Reply-To: <1483120568-21082-2-git-send-email-eajames.ibm-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>

[-- Attachment #1: Type: text/plain, Size: 2420 bytes --]

Hi Edward,

[auto build test WARNING on hwmon/hwmon-next]
[also build test WARNING on v4.10-rc1 next-20161224]
[cannot apply to linux/master]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url:    https://github.com/0day-ci/linux/commits/eajames-ibm-gmail-com/drivers-hwmon-Add-On-Chip-Controller-driver/20161231-021324
base:   https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
config: ia64-allmodconfig (attached as .config)
compiler: ia64-linux-gcc (GCC) 6.2.0
reproduce:
        wget https://git.kernel.org/cgit/linux/kernel/git/wfg/lkp-tests.git/plain/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        make.cross ARCH=ia64 

All warnings (new ones prefixed by >>):

   drivers/hwmon/occ/occ.c: In function 'occ_get_all':
>> drivers/hwmon/occ/occ.c:390:44: warning: passing argument 5 of 'occ_send_cmd' discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers]
     rc = occ_send_cmd(driver, 0, OCC_POLL, 1, &poll_cmd_data, occ_data);
                                               ^
   drivers/hwmon/occ/occ.c:281:11: note: expected 'u8 * {aka unsigned char *}' but argument is of type 'const u8 * {aka const unsigned char *}'
    static u8 occ_send_cmd(struct occ *driver, u8 seq, u8 type, u16 length,
              ^~~~~~~~~~~~

vim +390 drivers/hwmon/occ/occ.c

   374		return rc;
   375	}
   376	
   377	static int occ_get_all(struct occ *driver)
   378	{
   379		int i = 0, rc;
   380		u8 *occ_data;
   381		u16 num_bytes;
   382		const u8 poll_cmd_data = OCC_POLL_STAT_SENSOR;
   383		struct device *dev = driver->dev;
   384		struct occ_response *resp = &driver->response;
   385	
   386		occ_data = devm_kzalloc(dev, OCC_DATA_MAX, GFP_KERNEL);
   387		if (!occ_data)
   388			return -ENOMEM;
   389	
 > 390		rc = occ_send_cmd(driver, 0, OCC_POLL, 1, &poll_cmd_data, occ_data);
   391		if (rc) {
   392			dev_err(dev, "OCC poll failed: %d\n", rc);
   393			goto out;
   394		}
   395	
   396		num_bytes = get_unaligned((u16 *)&occ_data[RESP_DATA_LENGTH]);
   397		num_bytes = be16_to_cpu(num_bytes);
   398		dev_dbg(dev, "OCC data length: %d\n", num_bytes);

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation

[-- Attachment #2: .config.gz --]
[-- Type: application/gzip, Size: 45853 bytes --]

^ permalink raw reply

* [PATCH] iio: adc: Add Renesas GyroADC driver
From: Marek Vasut @ 2016-12-30 19:18 UTC (permalink / raw)
  To: linux-iio-u79uwXL29TY76Z2rM5mHXA
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, Marek Vasut,
	Geert Uytterhoeven, Simon Horman

Add IIO driver for the Renesas RCar GyroADC block. This block is a
simple 4/8-channel ADC which samples 12/15/24 bits of data every
cycle from all channels.

Signed-off-by: Marek Vasut <marek.vasut-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Cc: Geert Uytterhoeven <geert+renesas-gXvu3+zWzMSzQB+pC5nmwQ@public.gmane.org>
Cc: Simon Horman <horms+renesas-/R6kz+dDXgpPR4JQBCEnsQ@public.gmane.org>
---
 .../bindings/iio/adc/renesas,gyroadc.txt           |  38 +++
 MAINTAINERS                                        |   6 +
 drivers/iio/adc/Kconfig                            |  10 +
 drivers/iio/adc/Makefile                           |   1 +
 drivers/iio/adc/rcar_gyro_adc.c                    | 379 +++++++++++++++++++++
 5 files changed, 434 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/renesas,gyroadc.txt
 create mode 100644 drivers/iio/adc/rcar_gyro_adc.c

diff --git a/Documentation/devicetree/bindings/iio/adc/renesas,gyroadc.txt b/Documentation/devicetree/bindings/iio/adc/renesas,gyroadc.txt
new file mode 100644
index 0000000..3fd5f57
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/renesas,gyroadc.txt
@@ -0,0 +1,38 @@
+* Renesas RCar GyroADC device driver
+
+Required properties:
+- compatible:	Should be "renesas,rcar-gyroadc" for regular GyroADC or
+		"renesas,rcar-gyroadc-r8a7792" for GyroADC without interrupt
+		block found in R8A7792.
+- reg:		Address and length of the register set for the device
+- clocks:	References to all the clocks specified in the clock-names
+		property as specified in
+		Documentation/devicetree/bindings/clock/clock-bindings.txt.
+- clock-names:	Shall contain "fck" and "if". The "fck" are the GyroADC block
+		clock, the "if" are the interface clock.
+		power-domains = <&sysc R8A7791_PD_ALWAYS_ON>;
+- power-domains: Must contain a reference to the PM domain, if available.
+- renesas,gyroadc-mode:	GyroADC mode of operation, must be either of:
+			1 - MB88101A mode, 12bit sampling, 4 channels
+			2 - ADCS7476 mode, 15bit sampling, 8 channels
+			3 - MAX1162 mode,  16bit sampling, 8 channels
+- renesas,gyroadc-vref:	Array of reference voltage values for each input to
+			the GyroADC, in uV. Array must have 4 elemenets for
+			mode 1 and 8 elements for mode 2 and 3.
+
+Example:
+	&adc {
+		compatible = "renesas,rcar-gyroadc";
+		reg = <0 0xe6e54000 0 64>;
+		clocks = <&mstp9_clks R8A7791_CLK_GYROADC>, <&clk_65m>;
+		clock-names = "fck", "if";
+		power-domains = <&sysc R8A7791_PD_ALWAYS_ON>;
+
+		pinctrl-0 = <&adc_pins>;
+		pinctrl-names = "default";
+
+		renesas,gyroadc-vref = <4096000 4096000 4096000 4096000
+					4096000 4096000 4096000 4096000>;
+		renesas,gyroadc-mode = <3>;
+		status = "okay";
+	};
diff --git a/MAINTAINERS b/MAINTAINERS
index 162d904..751e760 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10271,6 +10271,12 @@ L:	linux-renesas-soc-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
 F:	drivers/net/ethernet/renesas/
 F:	include/linux/sh_eth.h
 
+RENESAS RCAR GYROADC DRIVER
+M:	Marek Vasut <marek.vasut-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+L:	linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+S:	Supported
+F:	drivers/iio/adc/rcar_gyro_adc.c
+
 RENESAS USB2 PHY DRIVER
 M:	Yoshihiro Shimoda <yoshihiro.shimoda.uh-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>
 L:	linux-renesas-soc-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 99c0514..4a4cac7 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -408,6 +408,16 @@ config QCOM_SPMI_VADC
 	  To compile this driver as a module, choose M here: the module will
 	  be called qcom-spmi-vadc.
 
+config RCAR_GYRO_ADC
+	tristate "Renesas RCAR GyroADC driver"
+	depends on ARCH_RCAR_GEN2 || (ARM && COMPILE_TEST)
+	help
+	  Say yes here to build support for the GyroADC found in Renesas
+	  RCar Gen2 SoCs.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called rcar-gyroadc.
+
 config ROCKCHIP_SARADC
 	tristate "Rockchip SARADC driver"
 	depends on ARCH_ROCKCHIP || (ARM && COMPILE_TEST)
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 7a40c04..253aeb2 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_NAU7802) += nau7802.o
 obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
 obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
 obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
+obj-$(CONFIG_RCAR_GYRO_ADC) += rcar_gyro_adc.o
 obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
 obj-$(CONFIG_STX104) += stx104.o
 obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
diff --git a/drivers/iio/adc/rcar_gyro_adc.c b/drivers/iio/adc/rcar_gyro_adc.c
new file mode 100644
index 0000000..a74b148
--- /dev/null
+++ b/drivers/iio/adc/rcar_gyro_adc.c
@@ -0,0 +1,379 @@
+/*
+ * Renesas RCar GyroADC driver
+ *
+ * Copyright 2016 Marek Vasut <marek.vasut-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+ *
+ * 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/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of_platform.h>
+#include <linux/err.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+
+/* GyroADC registers. */
+#define RCAR_GYROADC_MODE_SELECT		0x00
+#define RCAR_GYROADC_MODE_SELECT_1_MB88101A	0x0
+#define RCAR_GYROADC_MODE_SELECT_2_ADCS7476	0x1
+#define RCAR_GYROADC_MODE_SELECT_3_MAX1162	0x3
+
+#define RCAR_GYROADC_START_STOP			0x04
+#define RCAR_GYROADC_START_STOP_START		BIT(0)
+
+#define RCAR_GYROADC_CLOCK_LENGTH		0x08
+#define RCAR_GYROADC_1_25MS_LENGTH		0x0c
+
+#define RCAR_GYROADC_REALTIME_DATA(ch)		(0x10 + ((ch) * 4))
+#define RCAR_GYROADC_100MS_ADDED_DATA(ch)	(0x30 + ((ch) * 4))
+#define RCAR_GYROADC_10MS_AVG_DATA(ch)		(0x50 + ((ch) * 4))
+
+#define RCAR_GYROADC_FIFO_STATUS		0x70
+#define RCAR_GYROADC_FIFO_STATUS_EMPTY(ch)	BIT(0 + (4 * (ch)))
+#define RCAR_GYROADC_FIFO_STATUS_FULL(ch)	BIT(1 + (4 * (ch)))
+#define RCAR_GYROADC_FIFO_STATUS_ERROR(ch)	BIT(2 + (4 * (ch)))
+
+#define RCAR_GYROADC_INTR			0x74
+#define RCAR_GYROADC_INTR_INT			BIT(0)
+
+#define RCAR_GYROADC_INTENR			0x78
+#define RCAR_GYROADC_INTENR_INTEN		BIT(0)
+
+#define RCAR_GYROADC_SAMPLE_RATE		800	/* Hz */
+
+enum rcar_gyroadc_model {
+	RCAR_GYROADC_MODEL_DEFAULT,
+	RCAR_GYROADC_MODEL_R8A7792,
+};
+
+struct rcar_gyroadc {
+	struct device		*dev;
+	void __iomem		*regs;
+	struct clk		*fclk;
+	struct clk		*clk;
+	enum rcar_gyroadc_model	model;
+	unsigned int		mode;
+	u32			vref_uv[8];
+	u32			buffer[8];
+};
+
+static void rcar_gyroadc_hw_init(struct rcar_gyroadc *priv)
+{
+	unsigned long clk_mhz = clk_get_rate(priv->clk) / 1000000;
+
+	/* Stop the GyroADC. */
+	writel(0, priv->regs + RCAR_GYROADC_START_STOP);
+
+	/* Disable IRQ, except on V2H. */
+	if (priv->model != RCAR_GYROADC_MODEL_R8A7792)
+		writel(0, priv->regs + RCAR_GYROADC_INTENR);
+
+	/* Set mode and timing. */
+	writel(priv->mode, priv->regs + RCAR_GYROADC_MODE_SELECT);
+
+	if (priv->mode == RCAR_GYROADC_MODE_SELECT_1_MB88101A)
+		writel(clk_mhz * 10, priv->regs + RCAR_GYROADC_CLOCK_LENGTH);
+	else if (priv->mode == RCAR_GYROADC_MODE_SELECT_2_ADCS7476)
+		writel(clk_mhz * 5, priv->regs + RCAR_GYROADC_CLOCK_LENGTH);
+	else if (priv->mode == RCAR_GYROADC_MODE_SELECT_3_MAX1162)
+		writel(clk_mhz * 5, priv->regs + RCAR_GYROADC_CLOCK_LENGTH);
+	writel(clk_mhz * 1250, priv->regs + RCAR_GYROADC_1_25MS_LENGTH);
+
+	/*
+	 * We can possibly turn the sampling on/off on-demand to reduce power
+	 * consumption, but for the sake of quick availability of samples, we
+	 * don't do it now.
+	 */
+	writel(RCAR_GYROADC_START_STOP_START,
+	       priv->regs + RCAR_GYROADC_START_STOP);
+
+	/* Wait for the first conversion to complete. */
+	udelay(1250);
+}
+
+#define RCAR_GYROADC_CHAN(_idx, _chan_type, _realbits) {	\
+	.type			= (_chan_type),			\
+	.indexed		= 1,				\
+	.channel		= (_idx),			\
+	.info_mask_separate	= BIT(IIO_CHAN_INFO_RAW),	\
+	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) |	\
+				    BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+	.scan_index		= (_idx),			\
+	.scan_type	= {					\
+		.sign		= 'u',				\
+		.realbits	= (_realbits),			\
+		.storagebits	= 16,				\
+	},							\
+}
+
+static const struct iio_chan_spec rcar_gyroadc_iio_channels_1[] = {
+	RCAR_GYROADC_CHAN(0, IIO_VOLTAGE, 12),
+	RCAR_GYROADC_CHAN(1, IIO_VOLTAGE, 12),
+	RCAR_GYROADC_CHAN(2, IIO_VOLTAGE, 12),
+	RCAR_GYROADC_CHAN(3, IIO_VOLTAGE, 12),
+};
+
+static const struct iio_chan_spec rcar_gyroadc_iio_channels_2[] = {
+	RCAR_GYROADC_CHAN(0, IIO_VOLTAGE, 15),
+	RCAR_GYROADC_CHAN(1, IIO_VOLTAGE, 15),
+	RCAR_GYROADC_CHAN(2, IIO_VOLTAGE, 15),
+	RCAR_GYROADC_CHAN(3, IIO_VOLTAGE, 15),
+	RCAR_GYROADC_CHAN(4, IIO_VOLTAGE, 15),
+	RCAR_GYROADC_CHAN(5, IIO_VOLTAGE, 15),
+	RCAR_GYROADC_CHAN(6, IIO_VOLTAGE, 15),
+	RCAR_GYROADC_CHAN(7, IIO_VOLTAGE, 15),
+};
+
+/*
+ * NOTE: The data we receive in mode 3 from MAX1162 have MSByte = 0,
+ *       therefore we only use 16bit realbits here instead of 24.
+ */
+static const struct iio_chan_spec rcar_gyroadc_iio_channels_3[] = {
+	RCAR_GYROADC_CHAN(0, IIO_VOLTAGE, 16),
+	RCAR_GYROADC_CHAN(1, IIO_VOLTAGE, 16),
+	RCAR_GYROADC_CHAN(2, IIO_VOLTAGE, 16),
+	RCAR_GYROADC_CHAN(3, IIO_VOLTAGE, 16),
+	RCAR_GYROADC_CHAN(4, IIO_VOLTAGE, 16),
+	RCAR_GYROADC_CHAN(5, IIO_VOLTAGE, 16),
+	RCAR_GYROADC_CHAN(6, IIO_VOLTAGE, 16),
+	RCAR_GYROADC_CHAN(7, IIO_VOLTAGE, 16),
+};
+
+static int rcar_gyroadc_read_raw(struct iio_dev *indio_dev,
+				 struct iio_chan_spec const *chan,
+				 int *val, int *val2, long mask)
+{
+	struct rcar_gyroadc *priv = iio_priv(indio_dev);
+	unsigned int datareg = RCAR_GYROADC_REALTIME_DATA(chan->channel);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		if (chan->type != IIO_VOLTAGE)
+			return -EINVAL;
+
+		if (iio_buffer_enabled(indio_dev))
+			return -EBUSY;
+
+		*val = readl(priv->regs + datareg);
+		*val &= BIT(chan->scan_type.realbits) - 1;
+
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		*val = 0;
+		*val2 = (priv->vref_uv[chan->channel] * 1000) / 0x10000;
+		return IIO_VAL_INT_PLUS_NANO;
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		*val = RCAR_GYROADC_SAMPLE_RATE;
+		*val2 = 0;
+		return IIO_VAL_INT;
+	default:
+		break;
+	}
+
+	return -EINVAL;
+}
+
+static int rcar_gyroadc_reg_access(struct iio_dev *indio_dev,
+				   unsigned int reg, unsigned int writeval,
+				   unsigned int *readval)
+{
+	struct rcar_gyroadc *priv = iio_priv(indio_dev);
+	unsigned int maxreg = RCAR_GYROADC_INTENR;
+
+	if (readval == NULL)
+		return -EINVAL;
+
+	if (reg % 4)
+		return -EINVAL;
+
+	/* Handle the V2H case with missing interrupt block. */
+	if (priv->model == RCAR_GYROADC_MODEL_R8A7792)
+		maxreg = RCAR_GYROADC_FIFO_STATUS;
+
+	if (reg > maxreg)
+		return -EINVAL;
+
+	*readval = readl(priv->regs + reg);
+
+	return 0;
+}
+
+static const struct iio_info rcar_gyroadc_iio_info = {
+	.driver_module		= THIS_MODULE,
+	.read_raw		= rcar_gyroadc_read_raw,
+	.debugfs_reg_access	= rcar_gyroadc_reg_access,
+};
+
+static const struct of_device_id rcar_gyroadc_match[] = {
+	{
+		/* RCar Gen2 compatible GyroADC */
+		.compatible	= "renesas,rcar-gyroadc",
+		.data		= (void *)RCAR_GYROADC_MODEL_DEFAULT,
+	}, {
+		/* RCar V2H specialty without interrupt registers. */
+		.compatible	= "renesas,rcar-gyroadc-r8a7792",
+		.data		= (void *)RCAR_GYROADC_MODEL_R8A7792,
+	}, {
+		/* sentinel */
+	}
+};
+
+MODULE_DEVICE_TABLE(of, rcar_gyroadc_match);
+
+static int rcar_gyroadc_probe(struct platform_device *pdev)
+{
+	const struct of_device_id *of_id =
+		of_match_device(rcar_gyroadc_match, &pdev->dev);
+	struct device *dev = &pdev->dev;
+	struct rcar_gyroadc *priv;
+	struct iio_dev *indio_dev;
+	struct resource *mem;
+	int ret, mode;
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*priv));
+	if (!indio_dev) {
+		dev_err(dev, "Failed to allocate IIO device.\n");
+		return -ENOMEM;
+	}
+
+	priv = iio_priv(indio_dev);
+	priv->dev = dev;
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	priv->regs = devm_ioremap_resource(dev, mem);
+	if (IS_ERR(priv->regs))
+		return PTR_ERR(priv->regs);
+
+	priv->fclk = devm_clk_get(dev, "fck");
+	if (IS_ERR(priv->fclk)) {
+		ret = PTR_ERR(priv->fclk);
+		dev_err(dev, "Failed to get FCK clock (ret=%i)\n", ret);
+		return ret;
+	}
+
+	priv->clk = devm_clk_get(dev, "if");
+	if (IS_ERR(priv->clk)) {
+		ret = PTR_ERR(priv->clk);
+		dev_err(dev, "Failed to get IF clock (ret=%i)\n", ret);
+		return ret;
+	}
+
+	ret = of_property_read_u32(pdev->dev.of_node, "renesas,gyroadc-mode",
+				   &mode);
+	if (ret || (mode < 1) || (mode > 3)) {
+		dev_err(dev, "Failed to get GyroADC mode (ret=%i)\n", ret);
+		return ret;
+	}
+
+	if (mode == 1)
+		priv->mode = RCAR_GYROADC_MODE_SELECT_1_MB88101A;
+	else if (mode == 2)
+		priv->mode = RCAR_GYROADC_MODE_SELECT_2_ADCS7476;
+	else if (mode == 3)
+		priv->mode = RCAR_GYROADC_MODE_SELECT_3_MAX1162;
+
+	of_property_read_u32_array(pdev->dev.of_node, "renesas,gyroadc-vref",
+				   priv->vref_uv, (mode == 1) ? 4 : 8);
+
+	priv->model = (enum rcar_gyroadc_model)of_id->data;
+
+	platform_set_drvdata(pdev, indio_dev);
+
+	indio_dev->name = dev_name(dev);
+	indio_dev->dev.parent = dev;
+	indio_dev->dev.of_node = pdev->dev.of_node;
+	indio_dev->info = &rcar_gyroadc_iio_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	if (mode == 1) {
+		indio_dev->channels = rcar_gyroadc_iio_channels_1;
+		indio_dev->num_channels =
+			ARRAY_SIZE(rcar_gyroadc_iio_channels_1);
+	} else if (mode == 2) {
+		indio_dev->channels = rcar_gyroadc_iio_channels_2;
+		indio_dev->num_channels =
+			ARRAY_SIZE(rcar_gyroadc_iio_channels_2);
+	} else if (mode == 3) {
+		indio_dev->channels = rcar_gyroadc_iio_channels_3;
+		indio_dev->num_channels =
+			ARRAY_SIZE(rcar_gyroadc_iio_channels_3);
+	}
+
+	ret = clk_prepare_enable(priv->fclk);
+	if (ret) {
+		dev_err(dev, "Could not prepare or enable the FCK clock.\n");
+		return ret;
+	}
+
+	ret = clk_prepare_enable(priv->clk);
+	if (ret) {
+		dev_err(dev, "Could not prepare or enable the IF clock.\n");
+		goto error_clk_if_enable;
+	}
+
+	rcar_gyroadc_hw_init(priv);
+
+	ret = iio_device_register(indio_dev);
+	if (ret) {
+		dev_err(dev, "Couldn't register IIO device.\n");
+		goto error_iio_device_register;
+	}
+
+	return 0;
+
+error_iio_device_register:
+	clk_disable_unprepare(priv->clk);
+error_clk_if_enable:
+	clk_disable_unprepare(priv->fclk);
+	return ret;
+}
+
+static int rcar_gyroadc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+	struct rcar_gyroadc *priv = iio_priv(indio_dev);
+
+	/* Stop sampling */
+	writel(0, priv->regs + RCAR_GYROADC_START_STOP);
+
+	iio_device_unregister(indio_dev);
+	clk_disable_unprepare(priv->clk);
+	clk_disable_unprepare(priv->fclk);
+
+	return 0;
+}
+
+static struct platform_driver rcar_gyroadc_driver = {
+	.probe          = rcar_gyroadc_probe,
+	.remove         = rcar_gyroadc_remove,
+	.driver         = {
+		.name		= "rcar-gyroadc",
+		.of_match_table	= rcar_gyroadc_match,
+	},
+};
+
+module_platform_driver(rcar_gyroadc_driver);
+
+MODULE_AUTHOR("Marek Vasut <marek.vasut-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>");
+MODULE_DESCRIPTION("Renesas RCAR GyroADC driver");
+MODULE_LICENSE("GPL v2");
-- 
2.10.2

^ permalink raw reply related

* Re: [PATCH] ARM: dts: am572x-idk: Add gpios property to control PCIE_RESETn
From: Tony Lindgren @ 2016-12-30 19:00 UTC (permalink / raw)
  To: Kishon Vijay Abraham I
  Cc: Benoît Cousson, linux-omap-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Rob Herring, Mark Rutland,
	Russell King, nsekhar-l0cyMroinI0
In-Reply-To: <1483085240-16102-1-git-send-email-kishon-l0cyMroinI0@public.gmane.org>

* Kishon Vijay Abraham I <kishon-l0cyMroinI0@public.gmane.org> [161230 00:08]:
> Add 'gpios' property to pcie1 dt node and populate it with
> GPIO3_23 in order to drive PCIE_RESETn high.
> 
> This gets PCIe cards to be detected in AM572X IDK board.
> 
> Signed-off-by: Kishon Vijay Abraham I <kishon-l0cyMroinI0@public.gmane.org>
> ---
>  arch/arm/boot/dts/am572x-idk.dts |    4 ++++
>  1 file changed, 4 insertions(+)
> 
> diff --git a/arch/arm/boot/dts/am572x-idk.dts b/arch/arm/boot/dts/am572x-idk.dts
> index 27d9149..1540f7a 100644
> --- a/arch/arm/boot/dts/am572x-idk.dts
> +++ b/arch/arm/boot/dts/am572x-idk.dts
> @@ -87,3 +87,7 @@
>  &sn65hvs882 {
>  	load-gpios = <&gpio3 19 GPIO_ACTIVE_LOW>;
>  };
> +
> +&pcie1 {
> +	gpios = <&gpio3 23 GPIO_ACTIVE_HIGH>;
> +};

I'll apply this into omap-for-v4.10/fixes thanks.

Tony
--
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

* [PATCH] ARM: dts: Add dra7 iodelay configuration and use it for MMC
From: Tony Lindgren @ 2016-12-30 18:49 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Gary Bisson, Grygorii Strashko, Mark Rutland, Nishanth Menon,
	Rob Herring, devicetree, linux-gpio, linux-omap, Lokesh Vutla
In-Reply-To: <20161230183732.5595-3-tony@atomide.com>

Add dra7 iodelay configuration and use it for MMC.

Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 arch/arm/boot/dts/am57xx-beagle-x15-common.dtsi | 21 ++++++++++++++++++++-
 arch/arm/boot/dts/dra7.dtsi                     |  8 ++++++++
 include/dt-bindings/pinctrl/dra.h               |  4 ++++
 3 files changed, 32 insertions(+), 1 deletion(-)
---

And ere are the related dts changes.

diff --git a/arch/arm/boot/dts/am57xx-beagle-x15-common.dtsi b/arch/arm/boot/dts/am57xx-beagle-x15-common.dtsi
--- a/arch/arm/boot/dts/am57xx-beagle-x15-common.dtsi
+++ b/arch/arm/boot/dts/am57xx-beagle-x15-common.dtsi
@@ -190,6 +190,25 @@
 		>;
 	};
 };
+
+&dra7_iodelay_core
+{
+	mmc2_iodelay_3v3_conf: mmc2_iodelay_3v3_conf {
+		pinctrl-pin-array = <
+			0x18c A_DELAY_PS(0) G_DELAY_PS(120)	/* CFG_GPMC_A19_IN */
+			0x1a4 A_DELAY_PS(265) G_DELAY_PS(360)	/* CFG_GPMC_A20_IN */
+			0x1b0 A_DELAY_PS(0) G_DELAY_PS(120)	/* CFG_GPMC_A21_IN */
+			0x1bc A_DELAY_PS(0) G_DELAY_PS(120)	/* CFG_GPMC_A22_IN */
+			0x1c8 A_DELAY_PS(287) G_DELAY_PS(420)	/* CFG_GPMC_A23_IN */
+			0x1d4 A_DELAY_PS(144) G_DELAY_PS(240)	/* CFG_GPMC_A24_IN */
+			0x1e0 A_DELAY_PS(0) G_DELAY_PS(0)	/* CFG_GPMC_A25_IN */
+			0x1ec A_DELAY_PS(120) G_DELAY_PS(0)	/* CFG_GPMC_A26_IN */
+			0x1f8 A_DELAY_PS(120) G_DELAY_PS(180)	/* CFG_GPMC_A27_IN */
+			0x360 A_DELAY_PS(0) G_DELAY_PS(0)	/* CFG_GPMC_CS1_IN */
+		>;
+	};
+};
+
 &i2c1 {
 	status = "okay";
 	clock-frequency = <400000>;
@@ -452,7 +471,7 @@
 	status = "okay";
 
 	pinctrl-names = "default";
-	pinctrl-0 = <&mmc2_pins_default>;
+	pinctrl-0 = <&mmc2_pins_default &mmc2_iodelay_3v3_conf>;
 
 	vmmc-supply = <&vdd_3v3>;
 	bus-width = <8>;
diff --git a/arch/arm/boot/dts/dra7.dtsi b/arch/arm/boot/dts/dra7.dtsi
--- a/arch/arm/boot/dts/dra7.dtsi
+++ b/arch/arm/boot/dts/dra7.dtsi
@@ -401,6 +401,14 @@
 			reg = <0x40d00000 0x100>;
 		};
 
+		dra7_iodelay_core: padconf@4844a000 {
+			compatible = "ti,dra7-iodelay";
+			reg = <0x4844a000 0x0d1c>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+			#pinctrl-cells = <2>;
+		};
+
 		sdma: dma-controller@4a056000 {
 			compatible = "ti,omap4430-sdma";
 			reg = <0x4a056000 0x1000>;
diff --git a/include/dt-bindings/pinctrl/dra.h b/include/dt-bindings/pinctrl/dra.h
--- a/include/dt-bindings/pinctrl/dra.h
+++ b/include/dt-bindings/pinctrl/dra.h
@@ -73,5 +73,9 @@
  */
 #define DRA7XX_CORE_IOPAD(pa, val)	(((pa) & 0xffff) - 0x3400) (val)
 
+/* DRA7 IODELAY configuration parameters */
+#define A_DELAY_PS(val)		((val) & 0xffff)
+#define G_DELAY_PS(val)		((val) & 0xffff)
+
 #endif
 
-- 
2.11.0

^ permalink raw reply

* [PATCH 2/2] pinctrl: Introduce TI IOdelay configuration driver
From: Tony Lindgren @ 2016-12-30 18:37 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Gary Bisson, Grygorii Strashko, Mark Rutland, Nishanth Menon,
	Rob Herring, devicetree, linux-gpio, linux-omap, Lokesh Vutla
In-Reply-To: <20161230183732.5595-1-tony@atomide.com>

From: Nishanth Menon <nm@ti.com>

SoC family such as DRA7 family of processors have, in addition
to the regular muxing of pins (as done by pinctrl-single), a separate
hardware module called IODelay which is also expected to be configured.
The "IODelay" module has it's own register space that is independent
of the control module and the padconf register area.

With recent changes to the pinctrl framework, we can now support
this hardware with a reasonably minimal driver by using #pinctrl-cells,
GENERIC_PINCTRL_GROUPS and GENERIC_PINMUX_FUNCTIONS.

It is advocated strongly in TI's official documentation considering
the existing design of the DRA7 family of processors during mux or
IODelay reconfiguration, there is a potential for a significant glitch
which may cause functional impairment to certain hardware. It is
hence recommended to do as little of muxing as absolutely necessary
without I/O isolation (which can only be done in initial stages of
bootloader).

NOTE: with the system wide I/O isolation scheme present in DRA7 SoC
family, it is not reasonable to do stop all I/O operations for every
such pad configuration scheme. So, we will let it glitch when used in
this mode.

Even with the above limitation, certain functionality such as MMC has
mandatory need for IODelay reconfiguration requirements, depending on
speed of transfer. In these cases, with careful examination of usecase
involved, the expected glitch can be controlled such that it does not
impact functionality.

In short, IODelay module support as a padconf driver being introduced
here is not expected to do SoC wide I/O Isolation and is meant for
a limited subset of IODelay configuration requirements that need to
be dynamic and whose glitchy behavior will not cause functionality
failure for that interface.

IMPORTANT NOTE: we take the approach of keeping LOCK_BITs cleared
to 0x0 at all times, even when configuring Manual IO Timing Modes.
This is done by eliminating the LOCK_BIT=1 setting from Step
of the Manual IO timing Mode configuration procedure. This option
leaves the CFG_* registers unprotected from unintended writes to the
CTRL_CORE_PAD_* registers while Manual IO Timing Modes are configured.

This approach is taken to allow for a generic driver to exist in kernel
world that has to be used carefully in required usecases.

Signed-off-by: Nishanth Menon <nm@ti.com>
Signed-off-by: Lokesh Vutla <lokeshvutla@ti.com>
[tony@atomide.com: updated to use generic pinctrl functions, added
 binding documentation, updated comments]
Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 .../devicetree/bindings/pinctrl/ti,iodelay.txt     |  47 +
 drivers/pinctrl/Kconfig                            |   1 +
 drivers/pinctrl/Makefile                           |   1 +
 drivers/pinctrl/ti/Kconfig                         |  10 +
 drivers/pinctrl/ti/Makefile                        |   1 +
 drivers/pinctrl/ti/pinctrl-ti-iodelay.c            | 945 +++++++++++++++++++++
 6 files changed, 1005 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pinctrl/ti,iodelay.txt
 create mode 100644 drivers/pinctrl/ti/Kconfig
 create mode 100644 drivers/pinctrl/ti/Makefile
 create mode 100644 drivers/pinctrl/ti/pinctrl-ti-iodelay.c

diff --git a/Documentation/devicetree/bindings/pinctrl/ti,iodelay.txt b/Documentation/devicetree/bindings/pinctrl/ti,iodelay.txt
new file mode 100644
--- /dev/null
+++ b/Documentation/devicetree/bindings/pinctrl/ti,iodelay.txt
@@ -0,0 +1,47 @@
+* Pin configuration for TI IODELAY controller
+
+TI dra7 based SoCs such as am57xx have a controller for setting the IO delay
+for each pin. For most part the IO delay values are programmed by the bootloader,
+but some pins need to be configured dynamically by the kernel such as the
+MMC pins.
+
+Required Properties:
+
+  - compatible: Must be "ti,dra7-iodelay"
+  - reg: Base address and length of the memory resource used
+  - #address-cells: Number of address cells
+  - #size-cells: Size of cells
+  - #pinctrl-cells: Number of pinctrl cells, must be 2. See also
+    Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
+
+Example
+-------
+
+In the SoC specific dtsi file:
+
+	dra7_iodelay_core: padconf@4844a000 {
+		compatible = "ti,dra7-iodelay";
+		reg = <0x4844a000 0x0d1c>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#pinctrl-cells = <2>;
+	};
+
+In board-specific file:
+
+&dra7_iodelay_core {
+	mmc2_iodelay_3v3_conf: mmc2_iodelay_3v3_conf {
+		pinctrl-pin-array = <
+		0x18c A_DELAY_PS(0) G_DELAY_PS(120)	/* CFG_GPMC_A19_IN */
+		0x1a4 A_DELAY_PS(265) G_DELAY_PS(360)	/* CFG_GPMC_A20_IN */
+		0x1b0 A_DELAY_PS(0) G_DELAY_PS(120)	/* CFG_GPMC_A21_IN */
+		0x1bc A_DELAY_PS(0) G_DELAY_PS(120)	/* CFG_GPMC_A22_IN */
+		0x1c8 A_DELAY_PS(287) G_DELAY_PS(420)	/* CFG_GPMC_A23_IN */
+		0x1d4 A_DELAY_PS(144) G_DELAY_PS(240)	/* CFG_GPMC_A24_IN */
+		0x1e0 A_DELAY_PS(0) G_DELAY_PS(0)	/* CFG_GPMC_A25_IN */
+		0x1ec A_DELAY_PS(120) G_DELAY_PS(0)	/* CFG_GPMC_A26_IN */
+		0x1f8 A_DELAY_PS(120) G_DELAY_PS(180)	/* CFG_GPMC_A27_IN */
+		0x360 A_DELAY_PS(0) G_DELAY_PS(0)	/* CFG_GPMC_CS1_IN */
+		>;
+	};
+};
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -300,6 +300,7 @@ source "drivers/pinctrl/spear/Kconfig"
 source "drivers/pinctrl/stm32/Kconfig"
 source "drivers/pinctrl/sunxi/Kconfig"
 source "drivers/pinctrl/tegra/Kconfig"
+source "drivers/pinctrl/ti/Kconfig"
 source "drivers/pinctrl/uniphier/Kconfig"
 source "drivers/pinctrl/vt8500/Kconfig"
 source "drivers/pinctrl/mediatek/Kconfig"
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -53,6 +53,7 @@ obj-$(CONFIG_PINCTRL_SH_PFC)	+= sh-pfc/
 obj-$(CONFIG_PINCTRL_SPEAR)	+= spear/
 obj-$(CONFIG_PINCTRL_STM32)	+= stm32/
 obj-$(CONFIG_PINCTRL_SUNXI)	+= sunxi/
+obj-y				+= ti/
 obj-$(CONFIG_PINCTRL_UNIPHIER)	+= uniphier/
 obj-$(CONFIG_ARCH_VT8500)	+= vt8500/
 obj-$(CONFIG_PINCTRL_MTK)	+= mediatek/
diff --git a/drivers/pinctrl/ti/Kconfig b/drivers/pinctrl/ti/Kconfig
new file mode 100644
--- /dev/null
+++ b/drivers/pinctrl/ti/Kconfig
@@ -0,0 +1,10 @@
+config PINCTRL_TI_IODELAY
+	tristate "TI IODelay Module pinconf driver"
+	depends on OF
+	select GENERIC_PINCTRL_GROUPS
+	select GENERIC_PINMUX_FUNCTIONS
+	select GENERIC_PINCONF
+	select REGMAP_MMIO
+	help
+	  Say Y here to support Texas Instruments' IO delay pinconf driver.
+	  IO delay module is used for the DRA7 SoC family.
diff --git a/drivers/pinctrl/ti/Makefile b/drivers/pinctrl/ti/Makefile
new file mode 100644
--- /dev/null
+++ b/drivers/pinctrl/ti/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_PINCTRL_TI_IODELAY)	+= pinctrl-ti-iodelay.o
diff --git a/drivers/pinctrl/ti/pinctrl-ti-iodelay.c b/drivers/pinctrl/ti/pinctrl-ti-iodelay.c
new file mode 100644
--- /dev/null
+++ b/drivers/pinctrl/ti/pinctrl-ti-iodelay.c
@@ -0,0 +1,945 @@
+/*
+ * Support for configuration of IO Delay module found on Texas Instruments SoCs
+ * such as DRA7
+ *
+ * Copyright (C) 2015 Texas Instruments, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include "../core.h"
+#include "../devicetree.h"
+
+#define DRIVER_NAME	"ti-iodelay"
+
+/**
+ * struct ti_iodelay_reg_data - Describes the registers for the iodelay instance
+ * @signature_mask: CONFIG_REG mask for the signature bits (see TRM)
+ * @signature_value: CONFIG_REG signature value to be written (see TRM)
+ * @lock_mask: CONFIG_REG mask for the lock bits (see TRM)
+ * @lock_val: CONFIG_REG lock value for the lock bits (see TRM)
+ * @unlock_val:CONFIG_REG unlock value for the lock bits (see TRM)
+ * @binary_data_coarse_mask: CONFIG_REG coarse mask (see TRM)
+ * @binary_data_fine_mask: CONFIG_REG fine mask (see TRM)
+ * @reg_refclk_offset: Refclk register offset
+ * @refclk_period_mask: Refclk mask
+ * @reg_coarse_offset: Coarse register configuration offset
+ * @coarse_delay_count_mask: Coarse delay count mask
+ * @coarse_ref_count_mask: Coarse ref count mask
+ * @reg_fine_offset: Fine register configuration offset
+ * @fine_delay_count_mask: Fine delay count mask
+ * @fine_ref_count_mask: Fine ref count mask
+ * @reg_global_lock_offset: Global iodelay module lock register offset
+ * @global_lock_mask: Lock mask
+ * @global_unlock_val: Unlock value
+ * @global_lock_val: Lock value
+ * @reg_start_offset: Offset to iodelay registers after the CONFIG_REG_0 to 8
+ * @reg_nr_per_pin: Number of iodelay registers for each pin
+ * @regmap_config: Regmap configuration for the IODelay region
+ */
+struct ti_iodelay_reg_data {
+	u32 signature_mask;
+	u32 signature_value;
+	u32 lock_mask;
+	u32 lock_val;
+	u32 unlock_val;
+	u32 binary_data_coarse_mask;
+	u32 binary_data_fine_mask;
+
+	u32 reg_refclk_offset;
+	u32 refclk_period_mask;
+
+	u32 reg_coarse_offset;
+	u32 coarse_delay_count_mask;
+	u32 coarse_ref_count_mask;
+
+	u32 reg_fine_offset;
+	u32 fine_delay_count_mask;
+	u32 fine_ref_count_mask;
+
+	u32 reg_global_lock_offset;
+	u32 global_lock_mask;
+	u32 global_unlock_val;
+	u32 global_lock_val;
+
+	u32 reg_start_offset;
+	u32 reg_nr_per_pin;
+
+	struct regmap_config *regmap_config;
+};
+
+/**
+ * struct ti_iodelay_reg_values - Computed io_reg configuration values (see TRM)
+ * @coarse_ref_count: Coarse reference count
+ * @coarse_delay_count: Coarse delay count
+ * @fine_ref_count: Fine reference count
+ * @fine_delay_count: Fine Delay count
+ * @ref_clk_period: Reference Clock period
+ * @cdpe: Coarse delay parameter
+ * @fdpe: Fine delay parameter
+ */
+struct ti_iodelay_reg_values {
+	u16 coarse_ref_count;
+	u16 coarse_delay_count;
+
+	u16 fine_ref_count;
+	u16 fine_delay_count;
+
+	u16 ref_clk_period;
+
+	u32 cdpe;
+	u32 fdpe;
+};
+
+/**
+ * struct ti_iodelay_cfg - Description of each configuration parameters
+ * @offset: Configuration register offset
+ * @a_delay: Agnostic Delay (in ps)
+ * @g_delay: Gnostic Delay (in ps)
+ */
+struct ti_iodelay_cfg {
+	u16 offset;
+	u16 a_delay;
+	u16 g_delay;
+};
+
+/**
+ * struct ti_iodelay_pingroup - Structure that describes one group
+ * @name: Name of the group
+ * @map: pinctrl map allocated for the group
+ * @cfg: configuration array for the pin (from dt)
+ * @ncfg: number of configuration values allocated
+ * @config: pinconf "Config" - currently a dummy value
+ */
+struct ti_iodelay_pingroup {
+	struct ti_iodelay_cfg *cfg;
+	int ncfg;
+	unsigned long config;
+};
+
+/**
+ * struct ti_iodelay_device - Represents information for a iodelay instance
+ * @dev: Device pointer
+ * @phys_base: Physical address base of the iodelay device
+ * @reg_base: Virtual address base of the iodelay device
+ * @regmap: Regmap for this iodelay instance
+ * @pctl: Pinctrl device
+ * @desc: pinctrl descriptor for pctl
+ * @pa: pinctrl pin wise description
+ * @names: names of the pins
+ * @reg_data: Register definition data for the IODelay instance
+ * @reg_init_conf_values: Initial configuration values.
+ */
+struct ti_iodelay_device {
+	struct device *dev;
+	unsigned long phys_base;
+	void __iomem *reg_base;
+	struct regmap *regmap;
+
+	struct pinctrl_dev *pctl;
+	struct pinctrl_desc desc;
+	struct pinctrl_pin_desc *pa;
+
+	const struct ti_iodelay_reg_data *reg_data;
+	struct ti_iodelay_reg_values reg_init_conf_values;
+};
+
+/**
+ * ti_iodelay_extract() - extract bits for a field
+ * @val: Register value
+ * @mask: Mask
+ *
+ * Return: extracted value which is appropriately shifted
+ */
+static inline u32 ti_iodelay_extract(u32 val, u32 mask)
+{
+	return (val & mask) >> __ffs(mask);
+}
+
+/**
+ * ti_iodelay_compute_dpe() - Compute equation for delay parameter
+ * @period: Period to use
+ * @ref: Reference Count
+ * @delay: Delay count
+ * @delay_m: Delay multiplier
+ *
+ * Return: Computed delay parameter
+ */
+static inline u32 ti_iodelay_compute_dpe(u16 period, u16 ref, u16 delay,
+					 u16 delay_m)
+{
+	u64 m, d;
+
+	/* Handle overflow conditions */
+	m = 10 * (u64)period * (u64)ref;
+	d = 2 * (u64)delay * (u64)delay_m;
+
+	/* Truncate result back to 32 bits */
+	return div64_u64(m, d);
+}
+
+/**
+ * ti_iodelay_pinconf_set() - Configure the pin configuration
+ * @iod: iodelay device
+ * @val: Configuration value
+ *
+ * Update the configuration register as per TRM and lockup once done.
+ * *IMPORTANT NOTE* SoC TRM does recommend doing iodelay programmation only
+ * while in Isolation. But, then, isolation also implies that every pin
+ * on the SoC (including DDR) will be isolated out. The only benefit being
+ * a glitchless configuration, However, the intent of this driver is purely
+ * to support a "glitchy" configuration where applicable.
+ *
+ * Return: 0 in case of success, else appropriate error value
+ */
+static int ti_iodelay_pinconf_set(struct ti_iodelay_device *iod,
+				  struct ti_iodelay_cfg *cfg)
+{
+	const struct ti_iodelay_reg_data *reg = iod->reg_data;
+	struct ti_iodelay_reg_values *ival = &iod->reg_init_conf_values;
+	struct device *dev = iod->dev;
+	u32 g_delay_coarse, g_delay_fine;
+	u32 a_delay_coarse, a_delay_fine;
+	u32 c_elements, f_elements;
+	u32 total_delay;
+	u32 reg_mask, reg_val, tmp_val;
+	int r;
+
+	/* NOTE: Truncation is expected in all division below */
+	g_delay_coarse = cfg->g_delay / 920;
+	g_delay_fine = ((cfg->g_delay % 920) * 10) / 60;
+
+	a_delay_coarse = cfg->a_delay / ival->cdpe;
+	a_delay_fine = ((cfg->a_delay % ival->cdpe) * 10) / ival->fdpe;
+
+	c_elements = g_delay_coarse + a_delay_coarse;
+	f_elements = (g_delay_fine + a_delay_fine) / 10;
+
+	if (f_elements > 22) {
+		total_delay = c_elements * ival->cdpe + f_elements * ival->fdpe;
+		c_elements = total_delay / ival->cdpe;
+		f_elements = (total_delay % ival->cdpe) / ival->fdpe;
+	}
+
+	reg_mask = reg->signature_mask;
+	reg_val = reg->signature_value << __ffs(reg->signature_mask);
+
+	reg_mask |= reg->binary_data_coarse_mask;
+	tmp_val = c_elements << __ffs(reg->binary_data_coarse_mask);
+	if (tmp_val & ~reg->binary_data_coarse_mask) {
+		dev_err(dev, "Masking overflow of coarse elements %08x\n",
+			tmp_val);
+		tmp_val &= reg->binary_data_coarse_mask;
+	}
+	reg_val |= tmp_val;
+
+	reg_mask |= reg->binary_data_fine_mask;
+	tmp_val = f_elements << __ffs(reg->binary_data_fine_mask);
+	if (tmp_val & ~reg->binary_data_fine_mask) {
+		dev_err(dev, "Masking overflow of fine elements %08x\n",
+			tmp_val);
+		tmp_val &= reg->binary_data_fine_mask;
+	}
+	reg_val |= tmp_val;
+
+	/*
+	 * NOTE: we leave the iodelay values unlocked - this is to work around
+	 * situations such as those found with mmc mode change.
+	 * However, this leaves open any unwarranted changes to padconf register
+	 * impacting iodelay configuration. Use with care!
+	 */
+	reg_mask |= reg->lock_mask;
+	reg_val |= reg->unlock_val << __ffs(reg->lock_mask);
+	r = regmap_update_bits(iod->regmap, cfg->offset, reg_mask, reg_val);
+
+	dev_info(dev, "Set reg 0x%x Delay(a: %d g: %d), Elements(C=%d F=%d)0x%x\n",
+		 cfg->offset, cfg->a_delay, cfg->g_delay, c_elements,
+		 f_elements, reg_val);
+
+	return r;
+}
+
+/**
+ * ti_iodelay_pinconf_init_dev() - Initialize IODelay device
+ * @iod: iodelay device
+ *
+ * Unlocks the iodelay region, computes the common parameters
+ *
+ * Return: 0 in case of success, else appropriate error value
+ */
+static int ti_iodelay_pinconf_init_dev(struct ti_iodelay_device *iod)
+{
+	const struct ti_iodelay_reg_data *reg = iod->reg_data;
+	struct device *dev = iod->dev;
+	struct ti_iodelay_reg_values *ival = &iod->reg_init_conf_values;
+	u32 val;
+	int r;
+
+	/* unlock the iodelay region */
+	r = regmap_update_bits(iod->regmap, reg->reg_global_lock_offset,
+			       reg->global_lock_mask, reg->global_unlock_val);
+	if (r)
+		return r;
+
+	/* Read up Recalibration sequence done by bootloader */
+	r = regmap_read(iod->regmap, reg->reg_refclk_offset, &val);
+	if (r)
+		return r;
+	ival->ref_clk_period = ti_iodelay_extract(val, reg->refclk_period_mask);
+	dev_dbg(dev, "refclk_period=0x%04x\n", ival->ref_clk_period);
+
+	r = regmap_read(iod->regmap, reg->reg_coarse_offset, &val);
+	if (r)
+		return r;
+	ival->coarse_ref_count =
+	    ti_iodelay_extract(val, reg->coarse_ref_count_mask);
+	ival->coarse_delay_count =
+	    ti_iodelay_extract(val, reg->coarse_delay_count_mask);
+	if (!ival->coarse_delay_count) {
+		dev_err(dev, "Invalid Coarse delay count (0) (reg=0x%08x)\n",
+			val);
+		return -EINVAL;
+	}
+	ival->cdpe = ti_iodelay_compute_dpe(ival->ref_clk_period,
+					    ival->coarse_ref_count,
+					    ival->coarse_delay_count, 88);
+	if (!ival->cdpe) {
+		dev_err(dev, "Invalid cdpe computed params = %d %d %d\n",
+			ival->ref_clk_period, ival->coarse_ref_count,
+			ival->coarse_delay_count);
+		return -EINVAL;
+	}
+	dev_dbg(iod->dev, "coarse: ref=0x%04x delay=0x%04x cdpe=0x%08x\n",
+		ival->coarse_ref_count, ival->coarse_delay_count, ival->cdpe);
+
+	r = regmap_read(iod->regmap, reg->reg_fine_offset, &val);
+	if (r)
+		return r;
+	ival->fine_ref_count =
+	    ti_iodelay_extract(val, reg->fine_ref_count_mask);
+	ival->fine_delay_count =
+	    ti_iodelay_extract(val, reg->fine_delay_count_mask);
+	if (!ival->fine_delay_count) {
+		dev_err(dev, "Invalid Fine delay count (0) (reg=0x%08x)\n",
+			val);
+		return -EINVAL;
+	}
+	ival->fdpe = ti_iodelay_compute_dpe(ival->ref_clk_period,
+					    ival->fine_ref_count,
+					    ival->fine_delay_count, 264);
+	if (!ival->fdpe) {
+		dev_err(dev, "Invalid fdpe(0) computed params = %d %d %d\n",
+			ival->ref_clk_period, ival->fine_ref_count,
+			ival->fine_delay_count);
+		return -EINVAL;
+	}
+	dev_dbg(iod->dev, "fine: ref=0x%04x delay=0x%04x fdpe=0x%08x\n",
+		ival->fine_ref_count, ival->fine_delay_count, ival->fdpe);
+
+	return 0;
+}
+
+/**
+ * ti_iodelay_pinconf_deinit_dev() - deinit the iodelay device
+ * @iod:	IODelay device
+ *
+ * Deinitialize the IODelay device (basically just lock the region back up.
+ */
+static void ti_iodelay_pinconf_deinit_dev(struct ti_iodelay_device *iod)
+{
+	const struct ti_iodelay_reg_data *reg = iod->reg_data;
+
+	/* lock the iodelay region back again */
+	regmap_update_bits(iod->regmap, reg->reg_global_lock_offset,
+			   reg->global_lock_mask, reg->global_lock_val);
+}
+
+/**
+ * ti_iodelay_get_pingroup() - Find the group mapped by a group selector
+ * @iod: iodelay device
+ * @selector: Group Selector
+ *
+ * Return: Corresponding group representing group selector
+ */
+static struct ti_iodelay_pingroup *
+ti_iodelay_get_pingroup(struct ti_iodelay_device *iod, unsigned int selector)
+{
+	struct group_desc *g;
+
+	g = pinctrl_generic_get_group(iod->pctl, selector);
+	if (!g) {
+		dev_err(iod->dev, "%s could not find pingroup %i\n", __func__,
+			selector);
+
+		return NULL;
+	}
+
+	return g->data;
+}
+
+/**
+ * ti_iodelay_pin_to_offset() - get pin register offset based on the pin index
+ * @iod: iodelay driver instance
+ * @selector: Pin index
+ */
+static unsigned int ti_iodelay_pin_to_offset(struct ti_iodelay_device *iod,
+					     unsigned int selector)
+{
+	const struct ti_iodelay_reg_data *r = iod->reg_data;
+	unsigned int offset;
+
+	offset = selector * r->regmap_config->reg_stride;
+	offset *= r->reg_nr_per_pin;
+	offset += r->reg_start_offset;
+
+	return offset;
+}
+
+/**
+ * ti_iodelay_offset_to_pin() - get a pin index based on the register offset
+ * @iod: iodelay driver instance
+ * @offset: register offset from the base
+ */
+static int ti_iodelay_offset_to_pin(struct ti_iodelay_device *iod,
+				    unsigned int offset)
+{
+	const struct ti_iodelay_reg_data *r = iod->reg_data;
+	unsigned int index;
+
+	if (offset > r->regmap_config->max_register) {
+		dev_err(iod->dev, "mux offset out of range: 0x%x (0x%x)\n",
+			offset, r->regmap_config->max_register);
+		return -EINVAL;
+	}
+
+	index = (offset - r->reg_start_offset) / r->regmap_config->reg_stride;
+	index /= r->reg_nr_per_pin;
+
+	return index;
+}
+
+/**
+ *
+ * @pctldev: Pin controller driver
+ * @np: Device node
+ * @pinctrl_spec: Parsed arguments from device tree
+ * @pins: Array of pins in the pin group
+ * @pin_index: Pin index in the pin array
+ * @data: Pin controller driver specific data
+ *
+ */
+static int ti_iodelay_node_iterator(struct pinctrl_dev *pctldev,
+				    struct device_node *np,
+				    const struct of_phandle_args *pinctrl_spec,
+				    int *pins, int pin_index, void *data)
+{
+	struct ti_iodelay_device *iod;
+	struct ti_iodelay_cfg *cfg = data;
+	const struct ti_iodelay_reg_data *r;
+	struct pinctrl_pin_desc *pd;
+	int pin;
+
+	iod = pinctrl_dev_get_drvdata(pctldev);
+	if (!iod)
+		return -EINVAL;
+
+	r = iod->reg_data;
+
+	if (pinctrl_spec->args_count < r->reg_nr_per_pin) {
+		dev_err(iod->dev, "invalid args_count for spec: %i\n",
+			pinctrl_spec->args_count);
+
+		return -EINVAL;
+	}
+
+	/* Index plus two value cells */
+	cfg[pin_index].offset = pinctrl_spec->args[0];
+	cfg[pin_index].a_delay = pinctrl_spec->args[1] & 0xffff;
+	cfg[pin_index].g_delay = pinctrl_spec->args[2] & 0xffff;
+
+	pin = ti_iodelay_offset_to_pin(iod, cfg[pin_index].offset);
+	if (pin < 0) {
+		dev_err(iod->dev, "could not add functions for %s %ux\n",
+			np->name, cfg[pin_index].offset);
+		return -ENODEV;
+	}
+	pins[pin_index] = pin;
+
+	pd = &iod->pa[pin];
+	pd->drv_data = &cfg[pin_index];
+
+	dev_dbg(iod->dev, "%s offset=%x a_delay = %d g_delay = %d\n",
+		np->name, cfg[pin_index].offset, cfg[pin_index].a_delay,
+		cfg[pin_index].g_delay);
+
+	return 0;
+}
+
+/**
+ * ti_iodelay_dt_node_to_map() - Map a device tree node to appropriate group
+ * @pctldev: pinctrl device representing IODelay device
+ * @np: Node Pointer (device tree)
+ * @map: Pinctrl Map returned back to pinctrl framework
+ * @num_maps: Number of maps (1)
+ *
+ * Maps the device tree description into a group of configuration parameters
+ * for iodelay block entry.
+ *
+ * Return: 0 in case of success, else appropriate error value
+ */
+static int ti_iodelay_dt_node_to_map(struct pinctrl_dev *pctldev,
+				     struct device_node *np,
+				     struct pinctrl_map **map,
+				     unsigned int *num_maps)
+{
+	struct ti_iodelay_device *iod;
+	struct ti_iodelay_cfg *cfg;
+	struct ti_iodelay_pingroup *g;
+	const char *name = "pinctrl-pin-array";
+	int rows, *pins, error = -EINVAL, i;
+
+	iod = pinctrl_dev_get_drvdata(pctldev);
+	if (!iod)
+		return -EINVAL;
+
+	rows = pinctrl_count_index_with_args(np, name);
+	if (rows == -EINVAL)
+		return rows;
+
+	*map = devm_kzalloc(iod->dev, sizeof(**map), GFP_KERNEL);
+	if (!*map)
+		return -ENOMEM;
+	*num_maps = 0;
+
+	g = devm_kzalloc(iod->dev, sizeof(*g), GFP_KERNEL);
+	if (!g) {
+		error = -ENOMEM;
+		goto free_map;
+	}
+
+	pins = devm_kzalloc(iod->dev, sizeof(*pins) * rows, GFP_KERNEL);
+	if (!pins)
+		goto free_group;
+
+	cfg = devm_kzalloc(iod->dev, sizeof(*cfg) * rows, GFP_KERNEL);
+	if (!cfg) {
+		error = -ENOMEM;
+		goto free_pins;
+	}
+
+	for (i = 0; i < rows; i++) {
+		struct of_phandle_args pinctrl_spec;
+
+		error = pinctrl_parse_index_with_args(np, name, i,
+						      &pinctrl_spec);
+		if (error)
+			goto free_data;
+
+		error = ti_iodelay_node_iterator(pctldev, np, &pinctrl_spec,
+						 pins, i, cfg);
+		if (error)
+			goto free_data;
+	}
+
+	g->cfg = cfg;
+	g->ncfg = i;
+	g->config = PIN_CONFIG_END;
+
+	error = pinctrl_generic_add_group(iod->pctl, np->name, pins, i, g);
+	if (error < 0)
+		goto free_data;
+
+	(*map)->type = PIN_MAP_TYPE_CONFIGS_GROUP;
+	(*map)->data.configs.group_or_pin = np->name;
+	(*map)->data.configs.configs = &g->config;
+	(*map)->data.configs.num_configs = 1;
+	*num_maps = 1;
+
+	return 0;
+
+free_data:
+	devm_kfree(iod->dev, cfg);
+free_pins:
+	devm_kfree(iod->dev, pins);
+free_group:
+	devm_kfree(iod->dev, g);
+free_map:
+	devm_kfree(iod->dev, *map);
+
+	return error;
+}
+
+/**
+ * ti_iodelay_pinconf_group_get() - Get the group configuration
+ * @pctldev: pinctrl device representing IODelay device
+ * @selector: Group selector
+ * @config: Configuration returned
+ *
+ * Return: The configuration if the group is valid, else returns -EINVAL
+ */
+static int ti_iodelay_pinconf_group_get(struct pinctrl_dev *pctldev,
+					unsigned int selector,
+					unsigned long *config)
+{
+	struct ti_iodelay_device *iod;
+	struct device *dev;
+	struct ti_iodelay_pingroup *group;
+
+	iod = pinctrl_dev_get_drvdata(pctldev);
+	dev = iod->dev;
+	group = ti_iodelay_get_pingroup(iod, selector);
+
+	if (!group)
+		return -EINVAL;
+
+	*config = group->config;
+	return 0;
+}
+
+/**
+ * ti_iodelay_pinconf_group_set() - Configure the groups of pins
+ * @pctldev: pinctrl device representing IODelay device
+ * @selector: Group selector
+ * @configs: Configurations
+ * @num_configs: Number of configurations
+ *
+ * Return: 0 if all went fine, else appropriate error value.
+ */
+static int ti_iodelay_pinconf_group_set(struct pinctrl_dev *pctldev,
+					unsigned int selector,
+					unsigned long *configs,
+					unsigned int num_configs)
+{
+	struct ti_iodelay_device *iod;
+	struct device *dev;
+	struct ti_iodelay_pingroup *group;
+	int i;
+
+	iod = pinctrl_dev_get_drvdata(pctldev);
+	dev = iod->dev;
+	group = ti_iodelay_get_pingroup(iod, selector);
+
+	if (num_configs != 1) {
+		dev_err(dev, "Unsupported number of configurations %d\n",
+			num_configs);
+		return -EINVAL;
+	}
+
+	if (*configs != PIN_CONFIG_END) {
+		dev_err(dev, "Unsupported configuration\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < group->ncfg; i++) {
+		if (ti_iodelay_pinconf_set(iod, &group->cfg[i]))
+			return -ENOTSUPP;
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static void ti_iodelay_pin_dbg_show(struct pinctrl_dev *pctldev,
+				    struct seq_file *s,
+				    unsigned int pin)
+{
+	struct ti_iodelay_device *iod;
+	struct pinctrl_pin_desc *pd;
+	struct ti_iodelay_cfg *cfg;
+	const struct ti_iodelay_reg_data *r;
+	unsigned long offset;
+	u32 in, oen, out;
+
+	iod = pinctrl_dev_get_drvdata(pctldev);
+	r = iod->reg_data;
+
+	offset = ti_iodelay_pin_to_offset(iod, pin);
+	if (pin < 0) {
+		dev_err(iod->dev, "invalid pin offset for pin%i\n", pin);
+
+		return;
+	}
+
+	pd = &iod->pa[pin];
+	cfg = pd->drv_data;
+
+	regmap_read(iod->regmap, offset, &in);
+	regmap_read(iod->regmap, offset + r->regmap_config->reg_stride, &oen);
+	regmap_read(iod->regmap, offset + r->regmap_config->reg_stride * 2,
+		    &out);
+
+	seq_printf(s, "%lx a: %i g: %i (%08x %08x %08x) %s ",
+		   iod->phys_base + offset,
+		   cfg ? cfg->a_delay : -1,
+		   cfg ? cfg->g_delay : -1,
+		   in, oen, out, DRIVER_NAME);
+}
+
+/**
+ * ti_iodelay_pinconf_group_dbg_show() - show the group information
+ * @pctldev: Show the group information
+ * @s: Sequence file
+ * @selector: Group selector
+ *
+ * Provide the configuration information of the selected group
+ */
+static void ti_iodelay_pinconf_group_dbg_show(struct pinctrl_dev *pctldev,
+					      struct seq_file *s,
+					      unsigned int selector)
+{
+	struct ti_iodelay_device *iod;
+	struct device *dev;
+	struct ti_iodelay_pingroup *group;
+	int i;
+
+	iod = pinctrl_dev_get_drvdata(pctldev);
+	dev = iod->dev;
+	group = ti_iodelay_get_pingroup(iod, selector);
+	if (!group)
+		return;
+
+	for (i = 0; i < group->ncfg; i++) {
+		struct ti_iodelay_cfg *cfg;
+		u32 reg = 0;
+
+		cfg = &group->cfg[i];
+		regmap_read(iod->regmap, cfg->offset, &reg),
+			seq_printf(s, "\n\t0x%08x = 0x%08x (%3d, %3d)",
+				   cfg->offset, reg, cfg->a_delay,
+				   cfg->g_delay);
+	}
+}
+#endif
+
+static struct pinctrl_ops ti_iodelay_pinctrl_ops = {
+	.get_groups_count = pinctrl_generic_get_group_count,
+	.get_group_name = pinctrl_generic_get_group_name,
+	.get_group_pins = pinctrl_generic_get_group_pins,
+	.pin_dbg_show = ti_iodelay_pin_dbg_show,
+	.dt_node_to_map = ti_iodelay_dt_node_to_map,
+};
+
+static struct pinconf_ops ti_iodelay_pinctrl_pinconf_ops = {
+	.pin_config_group_get = ti_iodelay_pinconf_group_get,
+	.pin_config_group_set = ti_iodelay_pinconf_group_set,
+#ifdef CONFIG_DEBUG_FS
+	.pin_config_group_dbg_show = ti_iodelay_pinconf_group_dbg_show,
+#endif
+};
+
+/**
+ * ti_iodelay_alloc_pins() - Allocate structures needed for pins for iodelay
+ * @dev: Device pointer
+ * @iod: iodelay device
+ * @base_phy: Base Physical Address
+ *
+ * Return: 0 if all went fine, else appropriate error value.
+ */
+static int ti_iodelay_alloc_pins(struct device *dev,
+				 struct ti_iodelay_device *iod, u32 base_phy)
+{
+	const struct ti_iodelay_reg_data *r = iod->reg_data;
+	struct pinctrl_pin_desc *pin;
+	u32 phy_reg;
+	int nr_pins, i;
+
+	nr_pins = ti_iodelay_offset_to_pin(iod, r->regmap_config->max_register);
+	dev_dbg(dev, "Allocating %i pins\n", nr_pins);
+
+	iod->pa = devm_kzalloc(dev, sizeof(*iod->pa) * nr_pins, GFP_KERNEL);
+	if (!iod->pa)
+		return -ENOMEM;
+
+	iod->desc.pins = iod->pa;
+	iod->desc.npins = nr_pins;
+
+	phy_reg = r->reg_start_offset + base_phy;
+
+	for (i = 0; i < nr_pins; i++, phy_reg += 4) {
+		pin = &iod->pa[i];
+		pin->number = i;
+	}
+
+	return 0;
+}
+
+static struct regmap_config dra7_iodelay_regmap_config = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.max_register = 0xd1c,
+};
+
+static struct ti_iodelay_reg_data dra7_iodelay_data = {
+	.signature_mask = 0x0003f000,
+	.signature_value = 0x29,
+	.lock_mask = 0x00000400,
+	.lock_val = 1,
+	.unlock_val = 0,
+	.binary_data_coarse_mask = 0x000003e0,
+	.binary_data_fine_mask = 0x0000001f,
+
+	.reg_refclk_offset = 0x14,
+	.refclk_period_mask = 0xffff,
+
+	.reg_coarse_offset = 0x18,
+	.coarse_delay_count_mask = 0xffff0000,
+	.coarse_ref_count_mask = 0x0000ffff,
+
+	.reg_fine_offset = 0x1C,
+	.fine_delay_count_mask = 0xffff0000,
+	.fine_ref_count_mask = 0x0000ffff,
+
+	.reg_global_lock_offset = 0x2c,
+	.global_lock_mask = 0x0000ffff,
+	.global_unlock_val = 0x0000aaaa,
+	.global_lock_val = 0x0000aaab,
+
+	.reg_start_offset = 0x30,
+	.reg_nr_per_pin = 3,
+	.regmap_config = &dra7_iodelay_regmap_config,
+};
+
+static const struct of_device_id ti_iodelay_of_match[] = {
+	{.compatible = "ti,dra7-iodelay", .data = &dra7_iodelay_data},
+	{ /* Hopefully no more.. */ },
+};
+MODULE_DEVICE_TABLE(of, ti_iodelay_of_match);
+
+/**
+ * ti_iodelay_probe() - Standard probe
+ * @pdev: platform device
+ *
+ * Return: 0 if all went fine, else appropriate error value.
+ */
+static int ti_iodelay_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = of_node_get(dev->of_node);
+	const struct of_device_id *match;
+	struct resource *res;
+	struct ti_iodelay_device *iod;
+	int ret = 0;
+
+	if (!np) {
+		ret = -EINVAL;
+		dev_err(dev, "No OF node\n");
+		goto exit_out;
+	}
+
+	match = of_match_device(ti_iodelay_of_match, dev);
+	if (!match) {
+		ret = -EINVAL;
+		dev_err(dev, "No DATA match\n");
+		goto exit_out;
+	}
+
+	iod = devm_kzalloc(dev, sizeof(*iod), GFP_KERNEL);
+	if (!iod) {
+		ret = -ENOMEM;
+		goto exit_out;
+	}
+	iod->dev = dev;
+	iod->reg_data = match->data;
+
+	/* So far We can assume there is only 1 bank of registers */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev, "Missing MEM resource\n");
+		ret = -ENODEV;
+		goto exit_out;
+	}
+
+	iod->phys_base = res->start;
+	iod->reg_base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(iod->reg_base)) {
+		ret = PTR_ERR(iod->reg_base);
+		goto exit_out;
+	}
+
+	iod->regmap = devm_regmap_init_mmio(dev, iod->reg_base,
+					    iod->reg_data->regmap_config);
+	if (IS_ERR(iod->regmap)) {
+		dev_err(dev, "Regmap MMIO init failed.\n");
+		ret = PTR_ERR(iod->regmap);
+		goto exit_out;
+	}
+
+	if (ti_iodelay_pinconf_init_dev(iod))
+		goto exit_out;
+
+	ret = ti_iodelay_alloc_pins(dev, iod, res->start);
+	if (ret)
+		goto exit_out;
+
+	iod->desc.pctlops = &ti_iodelay_pinctrl_ops;
+	/* no pinmux ops - we are pinconf */
+	iod->desc.confops = &ti_iodelay_pinctrl_pinconf_ops;
+	iod->desc.name = dev_name(dev);
+	iod->desc.owner = THIS_MODULE;
+
+	iod->pctl = pinctrl_register(&iod->desc, dev, iod);
+	if (!iod->pctl) {
+		dev_err(dev, "Failed to register pinctrl\n");
+		ret = -ENODEV;
+		goto exit_out;
+	}
+
+	platform_set_drvdata(pdev, iod);
+
+exit_out:
+	of_node_put(np);
+	return ret;
+}
+
+/**
+ * ti_iodelay_remove() - standard remove
+ * @pdev: platform device
+ *
+ * Return: 0 if all went fine, else appropriate error value.
+ */
+static int ti_iodelay_remove(struct platform_device *pdev)
+{
+	struct ti_iodelay_device *iod = platform_get_drvdata(pdev);
+
+	if (!iod)
+		return 0;
+
+	if (iod->pctl)
+		pinctrl_unregister(iod->pctl);
+
+	ti_iodelay_pinconf_deinit_dev(iod);
+
+	/* Expect other allocations to be freed by devm */
+
+	return 0;
+}
+
+static struct platform_driver ti_iodelay_driver = {
+	.probe = ti_iodelay_probe,
+	.remove = ti_iodelay_remove,
+	.driver = {
+		   .owner = THIS_MODULE,
+		   .name = DRIVER_NAME,
+		   .of_match_table = ti_iodelay_of_match,
+	},
+};
+module_platform_driver(ti_iodelay_driver);
+
+MODULE_AUTHOR("Texas Instruments, Inc.");
+MODULE_DESCRIPTION("Pinconf driver for TI's IO Delay module");
+MODULE_LICENSE("GPL v2");
-- 
2.11.0

^ permalink raw reply

* [PATCH 1/2] pinctrl: core: Make dt_free_map optional
From: Tony Lindgren @ 2016-12-30 18:37 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Gary Bisson, Grygorii Strashko, Mark Rutland, Nishanth Menon,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA,
	linux-omap-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <20161230183732.5595-1-tony-4v6yS6AI5VpBDgjK7y7TUQ@public.gmane.org>

If the pin controller driver is using devm_kzalloc, there may not be
anything to do for dt_free_map. Let's make it optional to avoid
unncessary boilerplate code.

Signed-off-by: Tony Lindgren <tony-4v6yS6AI5VpBDgjK7y7TUQ@public.gmane.org>
---
 drivers/pinctrl/core.c       | 3 ---
 drivers/pinctrl/devicetree.c | 3 ++-
 2 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/drivers/pinctrl/core.c b/drivers/pinctrl/core.c
--- a/drivers/pinctrl/core.c
+++ b/drivers/pinctrl/core.c
@@ -1913,9 +1913,6 @@ static int pinctrl_check_ops(struct pinctrl_dev *pctldev)
 	    !ops->get_group_name)
 		return -EINVAL;
 
-	if (ops->dt_node_to_map && !ops->dt_free_map)
-		return -EINVAL;
-
 	return 0;
 }
 
diff --git a/drivers/pinctrl/devicetree.c b/drivers/pinctrl/devicetree.c
--- a/drivers/pinctrl/devicetree.c
+++ b/drivers/pinctrl/devicetree.c
@@ -42,7 +42,8 @@ static void dt_free_map(struct pinctrl_dev *pctldev,
 {
 	if (pctldev) {
 		const struct pinctrl_ops *ops = pctldev->desc->pctlops;
-		ops->dt_free_map(pctldev, map, num_maps);
+		if (ops->dt_free_map)
+			ops->dt_free_map(pctldev, map, num_maps);
 	} else {
 		/* There is no pctldev for PIN_MAP_TYPE_DUMMY_STATE */
 		kfree(map);
-- 
2.11.0
--
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

* [PATCH 0/2] Add TI iodelay driver using #pinctrl-cells
From: Tony Lindgren @ 2016-12-30 18:37 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Gary Bisson, Grygorii Strashko, Mark Rutland, Nishanth Menon,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA,
	linux-omap-u79uwXL29TY76Z2rM5mHXA

Hi all,

Here's a respin of Nishanth's iodelay driver that I've updated to
use #pinctrl-cells and generic pinctrl group and function code.

Gary, note that this one has an iterator function that you may
be able to use too, and hopefully we can simplify things further
eventually by introducing a generic iterator.

Note that these patches are against the current pinctrl devel
branch at commit b61d1546906d ("pinctrl: single: Use generic
pinmux helpers for managing functions").

Regards,

Tony

Nishanth Menon (1):
  pinctrl: Introduce TI IOdelay configuration driver

Tony Lindgren (1):
  pinctrl: core: Make dt_free_map optional

 .../devicetree/bindings/pinctrl/ti,iodelay.txt     |  47 +
 drivers/pinctrl/Kconfig                            |   1 +
 drivers/pinctrl/Makefile                           |   1 +
 drivers/pinctrl/core.c                             |   3 -
 drivers/pinctrl/devicetree.c                       |   3 +-
 drivers/pinctrl/ti/Kconfig                         |  10 +
 drivers/pinctrl/ti/Makefile                        |   1 +
 drivers/pinctrl/ti/pinctrl-ti-iodelay.c            | 945 +++++++++++++++++++++
 8 files changed, 1007 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/pinctrl/ti,iodelay.txt
 create mode 100644 drivers/pinctrl/ti/Kconfig
 create mode 100644 drivers/pinctrl/ti/Makefile
 create mode 100644 drivers/pinctrl/ti/pinctrl-ti-iodelay.c

-- 
2.11.0
--
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 10/20] gpio: pca953x: Add optional reset gpio control
From: Steve Longerbeam @ 2016-12-30 18:03 UTC (permalink / raw)
  To: Linus Walleij, LW
  Cc: Mark Rutland, Alexandre Courbot, devel@driverdev.osuosl.org,
	Philipp Zabel, devicetree@vger.kernel.org, Greg KH, Russell King,
	linux-kernel@vger.kernel.org, linux-gpio@vger.kernel.org,
	Rob Herring, Sascha Hauer, Fabio Estevam, Mauro Carvalho Chehab,
	Shawn Guo, linux-arm-kernel@lists.infradead.org,
	linux-media@vger.kernel.org
In-Reply-To: <CACRpkdZG2j4_LwP8KUVBTOtXma75YvYtC6CW1QqYwOm-MOZgHg@mail.gmail.com>

Hi Linus, Lothar,


On 12/30/2016 05:17 AM, Linus Walleij wrote:
> On Thu, Dec 29, 2016 at 11:27 PM, Steve Longerbeam
> <slongerbeam@gmail.com> wrote:
>
>> Add optional reset-gpios pin control. If present, de-assert the
>> specified reset gpio pin to bring the chip out of reset.
>>
>> Signed-off-by: Steve Longerbeam <steve_longerbeam@mentor.com>
>> Cc: Linus Walleij <linus.walleij@linaro.org>
>> Cc: Alexandre Courbot <gnurou@gmail.com>
>> Cc: linux-gpio@vger.kernel.org
>> Cc: linux-kernel@vger.kernel.org
> This seems like a separate patch from the other 19 patches so please send it
> separately so I can handle logistics easier in the future.

Ok, I'll send the next version separately.

>
>
>> @@ -133,6 +134,7 @@ struct pca953x_chip {
>>          const char *const *names;
>>          unsigned long driver_data;
>>          struct regulator *regulator;
>> +       struct gpio_desc *reset_gpio;
> Why do you even keep this around in the device state container?
>
> As you only use it in the probe() function, use a local variable.
>
> The descriptor will be free():ed by the devm infrastructure anyways.

I think my reasoning for putting the gpio handle into the device
struct was for possibly using it for run-time reset control at some
point. But that could be done later if needed, so I'll go ahead and
make it local.

>> +               /* see if we need to de-assert a reset pin */
>> +               chip->reset_gpio = devm_gpiod_get_optional(&client->dev,
>> +                                                          "reset",
>> +                                                          GPIOD_OUT_LOW);
>> +               if (IS_ERR(chip->reset_gpio)) {
>> +                       dev_err(&client->dev, "request for reset pin failed\n");
>> +                       return PTR_ERR(chip->reset_gpio);
>> +               }
> Nice.
>
>> +               if (chip->reset_gpio) {
>> +                       /* bring chip out of reset */
>> +                       dev_info(&client->dev, "releasing reset\n");
>> +                       gpiod_set_value(chip->reset_gpio, 0);
>> +               }
> Is this really needed given that you set it low in the
> devm_gpiod_get_optional()?

Yep, this is left over from a previous iteration, but it isn't needed now.
I'll remove it.

Steve

^ permalink raw reply

* [PATCH linux 6/6] hwmon: occ: Add callbacks for parsing P9 OCC datastructures
From: eajames.ibm @ 2016-12-30 17:56 UTC (permalink / raw)
  To: linux
  Cc: jdelvare, corbet, mark.rutland, robh+dt, wsa, andrew, joel,
	devicetree, linux-doc, linux-hwmon, linux-i2c, linux-kernel,
	Edward A. James
In-Reply-To: <1483120568-21082-1-git-send-email-eajames.ibm@gmail.com>

From: "Edward A. James" <eajames@us.ibm.com>

Add functions to parse the data structures that are specific to the OCC on
the POWER9 processor. These are the sensor data structures, including
temperature, frequency, power, and "caps."

Signed-off-by: Edward A. James <eajames@us.ibm.com>
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Reviewed-by: Andrew Jeffery <andrew@aj.id.au>
---
 Documentation/hwmon/occ    |   3 +
 drivers/hwmon/occ/p9_occ.c | 243 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/p9_occ.h |  30 ++++++
 3 files changed, 276 insertions(+)
 create mode 100644 drivers/hwmon/occ/p9_occ.c
 create mode 100644 drivers/hwmon/occ/p9_occ.h

diff --git a/Documentation/hwmon/occ b/Documentation/hwmon/occ
index a6b3dd6..0f17c2f 100644
--- a/Documentation/hwmon/occ
+++ b/Documentation/hwmon/occ
@@ -34,6 +34,9 @@ number of data structures, such as command format, response headers, and the
 like, are also defined in this specification, and are common to both POWER8 and
 POWER9 OCCs.
 
+There is currently no public P9 OCC specification, and the data structures
+defined in the POWER9 OCC driver are subject to change.
+
 sysfs Entries
 -------------
 
diff --git a/drivers/hwmon/occ/p9_occ.c b/drivers/hwmon/occ/p9_occ.c
new file mode 100644
index 0000000..f69b469
--- /dev/null
+++ b/drivers/hwmon/occ/p9_occ.c
@@ -0,0 +1,243 @@
+/*
+ * p9.c - OCC hwmon driver
+ *
+ * This file contains the Power9-specific methods and data structures for
+ * the OCC hwmon driver.
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <asm/unaligned.h>
+
+#include "occ.h"
+#include "occ_p9.h"
+
+/* P9 OCC sensor data format */
+struct p9_temp_sensor {
+	u32 sensor_id;
+	u8 fru_type;
+	u8 value;
+};
+
+struct p9_freq_sensor {
+	u32 sensor_id;
+	u16 value;
+};
+
+struct p9_power_sensor {
+	u32 sensor_id;
+	u8 function_id;
+	u8 apss_channel;
+	u16 reserved;
+	u32 update_tag;
+	u64 accumulator;
+	u16 value;
+};
+
+struct p9_caps_sensor {
+	u16 curr_powercap;
+	u16 curr_powerreading;
+	u16 norm_powercap;
+	u16 max_powercap;
+	u16 min_powercap;
+	u16 user_powerlimit;
+	u8 user_powerlimit_source;
+};
+
+void p9_parse_sensor(u8 *data, void *sensor, int sensor_type, int off,
+		     int snum)
+{
+	switch (sensor_type) {
+	case FREQ:
+	{
+		struct p9_freq_sensor *fs =
+			&(((struct p9_freq_sensor *)sensor)[snum]);
+
+		fs->sensor_id = be32_to_cpu(get_unaligned((u32 *)&data[off]));
+		fs->value = be16_to_cpu(get_unaligned((u16 *)&data[off + 4]));
+	}
+		break;
+	case TEMP:
+	{
+		struct p9_temp_sensor *ts =
+			&(((struct p9_temp_sensor *)sensor)[snum]);
+
+		ts->sensor_id = be32_to_cpu(get_unaligned((u32 *)&data[off]));
+		fs->fru_type = data[off + 4];
+		fs->value = data[off + 5];
+	}
+		break;
+	case POWER:
+	{
+		struct p9_power_sensor *ps =
+			&(((struct p9_power_sensor *)sensor)[snum]);
+
+		ps->sensor_id = be32_to_cpu(get_unaligned((u32 *)&data[off]));
+		ps->function_id = data[off + 4];
+		ps->apss_channel = data[off + 5];
+		ps->update_tag =
+			be32_to_cpu(get_unaligned((u32 *)&data[off + 8]));
+		ps->accumulator =
+			be64_to_cpu(get_unaligned((u64 *)&data[off + 12]));
+		ps->value = be16_to_cpu(get_unaligned((u16 *)&data[off + 20]));
+	}
+		break;
+	case CAPS:
+	{
+		struct p9_caps_sensor *cs =
+			&(((struct p9_caps_sensor *)sensor)[snum]);
+
+		cs->curr_powercap =
+			be16_to_cpu(get_unaligned((u16 *)&data[off]));
+		cs->curr_powerreading =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 2]));
+		cs->norm_powercap =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 4]));
+		cs->max_powercap =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 6]));
+		cs->min_powercap =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 8]));
+		cs->user_powerlimit =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 10]));
+		cs->user_powerlimit_source = data[off + 12];
+	}
+		break;
+	};
+}
+
+void *p9_alloc_sensor(int sensor_type, int num_sensors)
+{
+	switch (sensor_type) {
+	case FREQ:
+		return kcalloc(num_sensors, sizeof(struct p9_freq_sensor),
+			       GFP_KERNEL);
+	case TEMP:
+		return kcalloc(num_sensors, sizeof(struct p9_temp_sensor),
+			       GFP_KERNEL);
+	case POWER:
+		return kcalloc(num_sensors, sizeof(struct p9_power_sensor),
+			       GFP_KERNEL);
+	case CAPS:
+		return kcalloc(num_sensors, sizeof(struct p9_caps_sensor),
+			       GFP_KERNEL);
+	default:
+		return NULL;
+	}
+}
+
+int p9_get_sensor_value(struct occ *driver, int sensor_type, int snum)
+{
+	void *sensor;
+
+	if (sensor_type == CAPS)
+		return -EINVAL;
+
+	sensor = occ_get_sensor(driver, sensor_type);
+	if (!sensor)
+		return -ENODEV;
+
+	switch (sensor_type) {
+	case FREQ:
+		return ((struct p9_freq_sensor *)sensor)[snum].value;
+	case TEMP:
+		return ((struct p9_temp_sensor *)sensor)[snum].value;
+	case POWER:
+		return ((struct p9_power_sensor *)sensor)[snum].value;
+	default:
+		return -EINVAL;
+	}
+}
+
+int p9_get_sensor_id(struct occ *driver, int sensor_type, int snum)
+{
+	void *sensor;
+
+	if (sensor_type == CAPS)
+		return -EINVAL;
+
+	sensor = occ_get_sensor(driver, sensor_type);
+	if (!sensor)
+		return -ENODEV;
+
+	switch (sensor_type) {
+	case FREQ:
+		return ((struct p9_freq_sensor *)sensor)[snum].sensor_id;
+	case TEMP:
+		return ((struct p9_temp_sensor *)sensor)[snum].sensor_id;
+	case POWER:
+		return ((struct p9_power_sensor *)sensor)[snum].sensor_id;
+	default:
+		return -EINVAL;
+	}
+}
+
+int p9_get_caps_value(void *sensor, int snum, int caps_field)
+{
+	struct p9_caps_sensor *caps_sensor = sensor;
+
+	switch (caps_field) {
+	case 0:
+		return caps_sensor[snum].curr_powercap;
+	case 1:
+		return caps_sensor[snum].curr_powerreading;
+	case 2:
+		return caps_sensor[snum].norm_powercap;
+	case 3:
+		return caps_sensor[snum].max_powercap;
+	case 4:
+		return caps_sensor[snum].min_powercap;
+	case 5:
+		return caps_sensor[snum].user_powerlimit;
+	case 6:
+		return caps_sensor[snum].user_powerlimit_source;
+	default:
+		return -EINVAL;
+	}
+}
+static const struct occ_ops p9_ops = {
+	.parse_sensor = p9_parse_sensor,
+	.alloc_sensor = p9_alloc_sensor,
+	.get_sensor_value = p9_get_sensor_value,
+	.get_sensor_id = p9_get_sensor_id,
+	.get_caps_value = p9_get_caps_value,
+};
+
+static const struct occ_config p9_config = {
+	.command_addr = 0xFFFBE000,
+	.response_addr = 0xFFFBF000,
+};
+
+struct occ *p9_occ_start(struct device *dev, void *bus,
+			 struct occ_bus_ops *bus_ops)
+{
+	return occ_start(dev, bus, bus_ops, &p9_ops, &p9_config);
+}
+EXPORT_SYMBOL(occ_p9_start);
+
+int p9_occ_stop(struct occ *occ)
+{
+	return occ_stop(occ);
+}
+EXPORT_SYMBOL(occ_p9_stop);
+
+MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>");
+MODULE_DESCRIPTION("P9 OCC sensors");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/occ/p9_occ.h b/drivers/hwmon/occ/p9_occ.h
new file mode 100644
index 0000000..8130873
--- /dev/null
+++ b/drivers/hwmon/occ/p9_occ.h
@@ -0,0 +1,30 @@
+/*
+ * occ_p9.h - OCC hwmon driver
+ *
+ * This file contains Power9-specific function prototypes
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * 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.
+ */
+
+#ifndef __OCC_P9_H__
+#define __OCC_P9_H__
+
+#include "scom.h"
+
+struct device;
+
+struct occ *p9_occ_start(struct device *dev, void *bus,
+			 struct occ_bus_ops *bus_ops);
+int p9_occ_stop(struct occ *occ);
+
+#endif /* __OCC_P9_H__ */
-- 
1.9.1


^ permalink raw reply related

* [PATCH linux 5/6] hwmon: occ: Add hwmon implementation for the P8 OCC
From: eajames.ibm-Re5JQEeQqe8AvxtiuMwx3w @ 2016-12-30 17:56 UTC (permalink / raw)
  To: linux-0h96xk9xTtrk1uMJSBkQmQ
  Cc: jdelvare-IBi9RG/b67k, corbet-T1hC0tSOHrs,
	mark.rutland-5wv7dgnIgG8, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
	wsa-z923LK4zBo2bacvFa/9K2g, andrew-zrmu5oMJ5Fs,
	joel-U3u1mxZcP9KHXe+LvDLADg, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-doc-u79uwXL29TY76Z2rM5mHXA,
	linux-hwmon-u79uwXL29TY76Z2rM5mHXA,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Edward A. James
In-Reply-To: <1483120568-21082-1-git-send-email-eajames.ibm-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>

From: "Edward A. James" <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>

Add code to tie the hwmon sysfs code and the POWER8 OCC code together, as
well as probe the entire driver from the I2C bus. I2C is the communication
method between the BMC and the P8 OCC.

Signed-off-by: Edward A. James <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>
Signed-off-by: Andrew Jeffery <andrew-zrmu5oMJ5Fs@public.gmane.org>
Reviewed-by: Andrew Jeffery <andrew-zrmu5oMJ5Fs@public.gmane.org>
---
 .../devicetree/bindings/i2c/i2c-ibm-occ.txt        |  13 ++
 drivers/hwmon/occ/Kconfig                          |  14 ++
 drivers/hwmon/occ/Makefile                         |   1 +
 drivers/hwmon/occ/p8_occ_i2c.c                     | 141 +++++++++++++++++++++
 4 files changed, 169 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/i2c/i2c-ibm-occ.txt
 create mode 100644 drivers/hwmon/occ/p8_occ_i2c.c

diff --git a/Documentation/devicetree/bindings/i2c/i2c-ibm-occ.txt b/Documentation/devicetree/bindings/i2c/i2c-ibm-occ.txt
new file mode 100644
index 0000000..b0d2b36
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-ibm-occ.txt
@@ -0,0 +1,13 @@
+HWMON I2C driver for IBM POWER CPU OCC (On Chip Controller)
+
+Required properties:
+ - compatible: must be "ibm,p8-occ-i2c"
+ - reg: physical address
+
+Example:
+i2c3: i2c-bus@100 {
+	occ@50 {
+		compatible = "ibm,p8-occ-i2c";
+		reg = <0x50>;
+	};
+};
diff --git a/drivers/hwmon/occ/Kconfig b/drivers/hwmon/occ/Kconfig
index cdb64a7..3a5188f 100644
--- a/drivers/hwmon/occ/Kconfig
+++ b/drivers/hwmon/occ/Kconfig
@@ -13,3 +13,17 @@ menuconfig SENSORS_PPC_OCC
 
 	  This driver can also be built as a module. If so, the module
 	  will be called occ.
+
+if SENSORS_PPC_OCC
+
+config SENSORS_PPC_OCC_P8_I2C
+	tristate "POWER8 OCC hwmon support"
+	depends on I2C
+	help
+	 Provide a hwmon sysfs interface for the POWER8 On-Chip Controller,
+	 exposing temperature, frequency and power measurements.
+
+	 This driver can also be built as a module. If so, the module will be
+	 called p8-occ-i2c.
+
+endif
diff --git a/drivers/hwmon/occ/Makefile b/drivers/hwmon/occ/Makefile
index a6881f9..9294b58 100644
--- a/drivers/hwmon/occ/Makefile
+++ b/drivers/hwmon/occ/Makefile
@@ -1 +1,2 @@
 obj-$(CONFIG_SENSORS_PPC_OCC) += occ.o occ_sysfs.o
+obj-$(CONFIG_SENSORS_PPC_OCC_P8_I2C) += occ_scom_i2c.o occ_p8.o p8_occ_i2c.o
diff --git a/drivers/hwmon/occ/p8_occ_i2c.c b/drivers/hwmon/occ/p8_occ_i2c.c
new file mode 100644
index 0000000..0c65894
--- /dev/null
+++ b/drivers/hwmon/occ/p8_occ_i2c.c
@@ -0,0 +1,141 @@
+/*
+ * p8_occ_i2c.c - hwmon OCC driver
+ *
+ * This file contains the i2c layer for accessing the P8 OCC over i2c bus.
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * 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/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+
+#include "scom.h"
+#include "occ_scom_i2c.h"
+#include "occ_p8.h"
+#include "occ_sysfs.h"
+
+#define P8_OCC_I2C_NAME	"p8-occ-i2c"
+
+static char *caps_sensor_names[] = {
+	"curr_powercap",
+	"curr_powerreading",
+	"norm_powercap",
+	"max_powercap",
+	"min_powercap",
+	"user_powerlimit"
+};
+
+int p8_i2c_getscom(void *bus, u32 address, u64 *data)
+{
+	/* P8 i2c slave requires address to be shifted by 1 */
+	address = address << 1;
+
+	return occ_i2c_getscom(bus, address, data);
+}
+
+int p8_i2c_putscom(void *bus, u32 address, u32 data0, u32 data1)
+{
+	/* P8 i2c slave requires address to be shifted by 1 */
+	address = address << 1;
+
+	return occ_i2c_putscom(bus, address, data0, data1);
+}
+
+static struct occ_bus_ops p8_bus_ops = {
+	.getscom = p8_i2c_getscom,
+	.putscom = p8_i2c_putscom,
+};
+
+static struct occ_sysfs_config p8_sysfs_config = {
+	.num_caps_fields = ARRAY_SIZE(caps_sensor_names),
+	.caps_names = caps_sensor_names,
+};
+
+static int p8_occ_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct occ *occ;
+	struct occ_sysfs *hwmon;
+
+	occ = p8_occ_start(&client->dev, client, &p8_bus_ops);
+	if (IS_ERR(occ))
+		return PTR_ERR(occ);
+
+	hwmon = occ_sysfs_start(&client->dev, occ, &p8_sysfs_config);
+	if (IS_ERR(hwmon))
+		return PTR_ERR(hwmon);
+
+	i2c_set_clientdata(client, hwmon);
+
+	return 0;
+}
+
+static int p8_occ_remove(struct i2c_client *client)
+{
+	int rc;
+	struct occ_sysfs *hwmon = i2c_get_clientdata(client);
+	struct occ *occ = hwmon->occ;
+
+	rc = occ_sysfs_stop(&client->dev, hwmon);
+
+	rc = rc || p8_occ_stop(occ);
+
+	return rc;
+}
+
+/* used by old-style board info. */
+static const struct i2c_device_id occ_ids[] = {
+	{ P8_OCC_I2C_NAME, 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, occ_ids);
+
+/* used by device table */
+static const struct of_device_id occ_of_match[] = {
+	{ .compatible = "ibm,p8-occ-i2c" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, occ_of_match);
+
+/*
+ * i2c-core uses i2c-detect() to detect device in below address list.
+ * If exists, address will be assigned to client.
+ * It is also possible to read address from device table.
+ */
+static const unsigned short normal_i2c[] = {0x50, 0x51, I2C_CLIENT_END };
+
+static struct i2c_driver p8_occ_driver = {
+	.class = I2C_CLASS_HWMON,
+	.driver = {
+		.name = P8_OCC_I2C_NAME,
+		.pm = NULL,
+		.of_match_table = occ_of_match,
+	},
+	.probe = p8_occ_probe,
+	.remove = p8_occ_remove,
+	.id_table = occ_ids,
+	.address_list = normal_i2c,
+};
+
+module_i2c_driver(p8_occ_driver);
+
+MODULE_AUTHOR("Eddie James <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>");
+MODULE_DESCRIPTION("BMC P8 OCC hwmon driver");
+MODULE_LICENSE("GPL");
-- 
1.9.1

--
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 related

* [PATCH linux 4/6] hwmon: occ: Add callbacks for parsing P8 OCC datastructures
From: eajames.ibm-Re5JQEeQqe8AvxtiuMwx3w @ 2016-12-30 17:56 UTC (permalink / raw)
  To: linux-0h96xk9xTtrk1uMJSBkQmQ
  Cc: jdelvare-IBi9RG/b67k, corbet-T1hC0tSOHrs,
	mark.rutland-5wv7dgnIgG8, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
	wsa-z923LK4zBo2bacvFa/9K2g, andrew-zrmu5oMJ5Fs,
	joel-U3u1mxZcP9KHXe+LvDLADg, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-doc-u79uwXL29TY76Z2rM5mHXA,
	linux-hwmon-u79uwXL29TY76Z2rM5mHXA,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Edward A. James
In-Reply-To: <1483120568-21082-1-git-send-email-eajames.ibm-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>

From: "Edward A. James" <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>

Add functions to parse the data structures that are specific to the OCC on
the POWER8 processor. These are the sensor data structures, including
temperature, frequency, power, and "caps."

Signed-off-by: Edward A. James <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>
Signed-off-by: Andrew Jeffery <andrew-zrmu5oMJ5Fs@public.gmane.org>
Reviewed-by: Andrew Jeffery <andrew-zrmu5oMJ5Fs@public.gmane.org>
---
 Documentation/hwmon/occ    |   9 ++
 drivers/hwmon/occ/occ_p8.c | 217 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/occ_p8.h |  30 +++++++
 3 files changed, 256 insertions(+)
 create mode 100644 drivers/hwmon/occ/occ_p8.c
 create mode 100644 drivers/hwmon/occ/occ_p8.h

diff --git a/Documentation/hwmon/occ b/Documentation/hwmon/occ
index 1ee8689..a6b3dd6 100644
--- a/Documentation/hwmon/occ
+++ b/Documentation/hwmon/occ
@@ -25,6 +25,15 @@ Currently, all versions of the OCC support four types of sensor data: power,
 temperature, frequency, and "caps," which indicate limits and thresholds used
 internally on the OCC.
 
+The format for the POWER8 OCC sensor data can be found in the P8 OCC
+specification:
+github.com/open-power/docs/blob/master/occ/OCC_OpenPwr_FW_Interfaces.pdf
+This document provides the details of the OCC sensors: power, frequency,
+temperature, and caps. These sensor formats are specific to the POWER8 OCC. A
+number of data structures, such as command format, response headers, and the
+like, are also defined in this specification, and are common to both POWER8 and
+POWER9 OCCs.
+
 sysfs Entries
 -------------
 
diff --git a/drivers/hwmon/occ/occ_p8.c b/drivers/hwmon/occ/occ_p8.c
new file mode 100644
index 0000000..812df16
--- /dev/null
+++ b/drivers/hwmon/occ/occ_p8.c
@@ -0,0 +1,217 @@
+/*
+ * occ_p8.c - OCC hwmon driver
+ *
+ * This file contains the Power8-specific methods and data structures for
+ * the OCC hwmon driver.
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <asm/unaligned.h>
+
+#include "occ.h"
+#include "occ_p8.h"
+
+/* P8 OCC sensor data format */
+struct p8_occ_sensor {
+	u16 sensor_id;
+	u16 value;
+};
+
+struct p8_power_sensor {
+	u16 sensor_id;
+	u32 update_tag;
+	u32 accumulator;
+	u16 value;
+};
+
+struct p8_caps_sensor {
+	u16 curr_powercap;
+	u16 curr_powerreading;
+	u16 norm_powercap;
+	u16 max_powercap;
+	u16 min_powercap;
+	u16 user_powerlimit;
+};
+
+void p8_parse_sensor(u8 *data, void *sensor, int sensor_type, int off,
+		     int snum)
+{
+	switch (sensor_type) {
+	case FREQ:
+	case TEMP:
+	{
+		struct p8_occ_sensor *os =
+			&(((struct p8_occ_sensor *)sensor)[snum]);
+
+		os->sensor_id = be16_to_cpu(get_unaligned((u16 *)&data[off]));
+		os->value = be16_to_cpu(get_unaligned((u16 *)&data[off + 2]));
+	}
+		break;
+	case POWER:
+	{
+		struct p8_power_sensor *ps =
+			&(((struct p8_power_sensor *)sensor)[snum]);
+
+		ps->sensor_id = be16_to_cpu(get_unaligned((u16 *)&data[off]));
+		ps->update_tag =
+			be32_to_cpu(get_unaligned((u32 *)&data[off + 2]));
+		ps->accumulator =
+			be32_to_cpu(get_unaligned((u32 *)&data[off + 6]));
+		ps->value = be16_to_cpu(get_unaligned((u16 *)&data[off + 10]));
+	}
+		break;
+	case CAPS:
+	{
+		struct p8_caps_sensor *cs =
+			&(((struct p8_caps_sensor *)sensor)[snum]);
+
+		cs->curr_powercap =
+			be16_to_cpu(get_unaligned((u16 *)&data[off]));
+		cs->curr_powerreading =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 2]));
+		cs->norm_powercap =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 4]));
+		cs->max_powercap =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 6]));
+		cs->min_powercap =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 8]));
+		cs->user_powerlimit =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 10]));
+	}
+		break;
+	};
+}
+
+void *p8_alloc_sensor(int sensor_type, int num_sensors)
+{
+	switch (sensor_type) {
+	case FREQ:
+	case TEMP:
+		return kcalloc(num_sensors, sizeof(struct p8_occ_sensor),
+			       GFP_KERNEL);
+	case POWER:
+		return kcalloc(num_sensors, sizeof(struct p8_power_sensor),
+			       GFP_KERNEL);
+	case CAPS:
+		return kcalloc(num_sensors, sizeof(struct p8_caps_sensor),
+			       GFP_KERNEL);
+	default:
+		return NULL;
+	}
+}
+
+int p8_get_sensor_value(struct occ *driver, int sensor_type, int snum)
+{
+	void *sensor;
+
+	if (sensor_type == CAPS)
+		return -EINVAL;
+
+	sensor = occ_get_sensor(driver, sensor_type);
+	if (!sensor)
+		return -ENODEV;
+
+	switch (sensor_type) {
+	case FREQ:
+	case TEMP:
+		return ((struct p8_occ_sensor *)sensor)[snum].value;
+	case POWER:
+		return ((struct p8_power_sensor *)sensor)[snum].value;
+	default:
+		return -EINVAL;
+	}
+}
+
+int p8_get_sensor_id(struct occ *driver, int sensor_type, int snum)
+{
+	void *sensor;
+	int i = snum;
+
+	if (sensor_type == CAPS)
+		return -EINVAL;
+
+	sensor = occ_get_sensor(driver, sensor_type);
+	if (!sensor)
+		return -ENODEV;
+
+	switch (sensor_type) {
+	case FREQ:
+	case TEMP:
+		return ((struct p8_occ_sensor *)sensor)[i].sensor_id;
+	case POWER:
+		return ((struct p8_power_sensor *)sensor)[i].sensor_id;
+	default:
+		return -EINVAL;
+	}
+}
+
+int p8_get_caps_value(void *sensor, int snum, int caps_field)
+{
+	struct p8_caps_sensor *caps_sensor = sensor;
+
+	switch (caps_field) {
+	case 0:
+		return caps_sensor[snum].curr_powercap;
+	case 1:
+		return caps_sensor[snum].curr_powerreading;
+	case 2:
+		return caps_sensor[snum].norm_powercap;
+	case 3:
+		return caps_sensor[snum].max_powercap;
+	case 4:
+		return caps_sensor[snum].min_powercap;
+	case 5:
+		return caps_sensor[snum].user_powerlimit;
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct occ_ops p8_ops = {
+	.parse_sensor = p8_parse_sensor,
+	.alloc_sensor = p8_alloc_sensor,
+	.get_sensor_value = p8_get_sensor_value,
+	.get_sensor_id = p8_get_sensor_id,
+	.get_caps_value = p8_get_caps_value,
+};
+
+static const struct occ_config p8_config = {
+	.command_addr = 0xFFFF6000,
+	.response_addr = 0xFFFF7000,
+};
+
+struct occ *p8_occ_start(struct device *dev, void *bus,
+			 struct occ_bus_ops *bus_ops)
+{
+	return occ_start(dev, bus, bus_ops, &p8_ops, &p8_config);
+}
+EXPORT_SYMBOL(p8_occ_start);
+
+int p8_occ_stop(struct occ *occ)
+{
+	return occ_stop(occ);
+}
+EXPORT_SYMBOL(p8_occ_stop);
+
+MODULE_AUTHOR("Eddie James <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>");
+MODULE_DESCRIPTION("P8 OCC sensors");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/occ/occ_p8.h b/drivers/hwmon/occ/occ_p8.h
new file mode 100644
index 0000000..b44cdd4
--- /dev/null
+++ b/drivers/hwmon/occ/occ_p8.h
@@ -0,0 +1,30 @@
+/*
+ * occ_p8.h - OCC hwmon driver
+ *
+ * This file contains Power8-specific function prototypes
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * 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.
+ */
+
+#ifndef __OCC_P8_H__
+#define __OCC_P8_H__
+
+#include "scom.h"
+
+struct device;
+
+struct occ *p8_occ_start(struct device *dev, void *bus,
+			 struct occ_bus_ops *bus_ops);
+int p8_occ_stop(struct occ *occ);
+
+#endif /* __OCC_P8_H__ */
-- 
1.9.1

--
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 related

* [PATCH linux 3/6] hwmon: occ: Add I2C transport implementation for SCOM operations
From: eajames.ibm-Re5JQEeQqe8AvxtiuMwx3w @ 2016-12-30 17:56 UTC (permalink / raw)
  To: linux-0h96xk9xTtrk1uMJSBkQmQ
  Cc: jdelvare-IBi9RG/b67k, corbet-T1hC0tSOHrs,
	mark.rutland-5wv7dgnIgG8, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
	wsa-z923LK4zBo2bacvFa/9K2g, andrew-zrmu5oMJ5Fs,
	joel-U3u1mxZcP9KHXe+LvDLADg, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-doc-u79uwXL29TY76Z2rM5mHXA,
	linux-hwmon-u79uwXL29TY76Z2rM5mHXA,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Edward A. James
In-Reply-To: <1483120568-21082-1-git-send-email-eajames.ibm-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>

From: "Edward A. James" <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>

Add functions to send SCOM operations over I2C bus. The BMC can
communicate with the Power8 host processor over I2C, but needs to use SCOM
operations in order to access the OCC register space.

Signed-off-by: Edward A. James <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>
Signed-off-by: Andrew Jeffery <andrew-zrmu5oMJ5Fs@public.gmane.org>
---
 drivers/hwmon/occ/occ_scom_i2c.c | 73 ++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/occ_scom_i2c.h | 26 ++++++++++++++
 2 files changed, 99 insertions(+)
 create mode 100644 drivers/hwmon/occ/occ_scom_i2c.c
 create mode 100644 drivers/hwmon/occ/occ_scom_i2c.h

diff --git a/drivers/hwmon/occ/occ_scom_i2c.c b/drivers/hwmon/occ/occ_scom_i2c.c
new file mode 100644
index 0000000..a922f83
--- /dev/null
+++ b/drivers/hwmon/occ/occ_scom_i2c.c
@@ -0,0 +1,73 @@
+/*
+ * occ_scom_i2c.c - hwmon OCC driver
+ *
+ * This file contains the functions for performing SCOM operations over I2C bus
+ * to access the OCC.
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * 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/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include "scom.h"
+#include "occ_scom_i2c.h"
+
+int occ_i2c_getscom(void *bus, u32 address, u64 *data)
+{
+	ssize_t rc;
+	u64 buf;
+	struct i2c_client *client = bus;
+
+	rc = i2c_master_send(client, (const char *)&address, sizeof(u32));
+	if (rc < 0)
+		return rc;
+	else if (rc != sizeof(u32))
+		return -EIO;
+
+	rc = i2c_master_recv(client, (char *)&buf, sizeof(u64));
+	if (rc < 0)
+		return rc;
+	else if (rc != sizeof(u64))
+		return -EIO;
+
+	*data = be64_to_cpu(buf);
+
+	return 0;
+}
+EXPORT_SYMBOL(occ_i2c_getscom);
+
+int occ_i2c_putscom(void *bus, u32 address, u32 data0, u32 data1)
+{
+	u32 buf[3];
+	ssize_t rc;
+	struct i2c_client *client = bus;
+
+	buf[0] = address;
+	buf[1] = data1;
+	buf[2] = data0;
+
+	rc = i2c_master_send(client, (const char *)buf, sizeof(u32) * 3);
+	if (rc < 0)
+		return rc;
+	else if (rc != sizeof(u32) * 3)
+		return -EIO;
+
+	return 0;
+}
+EXPORT_SYMBOL(occ_i2c_putscom);
+
+MODULE_AUTHOR("Eddie James <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>");
+MODULE_DESCRIPTION("I2C OCC SCOM transport");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/occ/occ_scom_i2c.h b/drivers/hwmon/occ/occ_scom_i2c.h
new file mode 100644
index 0000000..945739c
--- /dev/null
+++ b/drivers/hwmon/occ/occ_scom_i2c.h
@@ -0,0 +1,26 @@
+/*
+ * occ_scom_i2c.h - hwmon OCC driver
+ *
+ * This file contains function protoypes for peforming SCOM operations over I2C
+ * bus to access the OCC.
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * 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.
+ */
+
+#ifndef __OCC_SCOM_I2C_H__
+#define __OCC_SCOM_I2C_H__
+
+int occ_i2c_getscom(void *bus, u32 address, u64 *data);
+int occ_i2c_putscom(void *bus, u32 address, u32 data0, u32 data1);
+
+#endif /* __OCC_SCOM_I2C_H__ */
-- 
1.9.1

--
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 related

* [PATCH linux 2/6] hwmon: occ: Add sysfs interface
From: eajames.ibm @ 2016-12-30 17:56 UTC (permalink / raw)
  To: linux
  Cc: jdelvare, corbet, mark.rutland, robh+dt, wsa, andrew, joel,
	devicetree, linux-doc, linux-hwmon, linux-i2c, linux-kernel,
	Edward A. James
In-Reply-To: <1483120568-21082-1-git-send-email-eajames.ibm@gmail.com>

From: "Edward A. James" <eajames@us.ibm.com>

Add a generic mechanism to expose the sensors provided by the OCC in
sysfs.

Signed-off-by: Edward A. James <eajames@us.ibm.com>
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Reviewed-by: Andrew Jeffery <andrew@aj.id.au>
---
 Documentation/hwmon/occ       |  48 +++++
 drivers/hwmon/occ/Makefile    |   2 +-
 drivers/hwmon/occ/occ_sysfs.c | 492 ++++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/occ_sysfs.h |  52 +++++
 4 files changed, 593 insertions(+), 1 deletion(-)
 create mode 100644 drivers/hwmon/occ/occ_sysfs.c
 create mode 100644 drivers/hwmon/occ/occ_sysfs.h

diff --git a/Documentation/hwmon/occ b/Documentation/hwmon/occ
index 79d1642..1ee8689 100644
--- a/Documentation/hwmon/occ
+++ b/Documentation/hwmon/occ
@@ -25,6 +25,54 @@ Currently, all versions of the OCC support four types of sensor data: power,
 temperature, frequency, and "caps," which indicate limits and thresholds used
 internally on the OCC.
 
+sysfs Entries
+-------------
+
+The OCC driver uses the hwmon sysfs framework to provide data to userspace.
+
+The driver exports two sysfs files for each frequency, temperature, and power
+sensor. These are "input" and "label". The input file contains the value of the
+sensor. The label file contains the sensor id. The sensor id is the unique
+internal OCC identifier. Sensor ids may be provided by the OCC specification.
+The names of these files will be in the following format:
+	<sensor type><sensor index>_input
+	<sensor type><sensor index>_label
+Sensor types will be one of "temp", "freq", or "power". The sensor index is
+an index to differentiate different sensor files. For example, a single
+temperature sensor will have two sysfs files: temp1_input and temp1_label.
+
+Caps sensors are exported differently. For each caps sensor, the driver will
+export 6 entries:
+	curr_powercap - current power cap in watts
+	curr_powerreading - current power output in watts
+	norm_powercap - power cap without redundant power
+	max_powercap - maximum power cap that can be set in watts
+	min_powercap - minimum power cap that can be set in watts
+	user_powerlimit - power limit specified by the user in watts
+In addition, the OCC driver for P9 will export a 7th entry:
+	user_powerlimit_source - can be one of two values depending on who set
+		the user_powerlimit. 0x1 - out of band from BMC or host. 0x2 -
+		in band from other source.
+The format for these files is caps<sensor index>_<entry type>. For example,
+caps1_curr_powercap.
+
+The driver also provides a number of sysfs entries through hwmon to better
+control the driver and monitor the OCC.
+	powercap - read or write the OCC user power limit in watts.
+	name - read the name of the driver
+	update_interval - read or write the minimum interval for polling the
+		OCC.
+
+The driver also exports a single sysfs file through the communication protocol
+device (see BMC - Host Communications). The filename is "online" and represents
+the status of the OCC with respect to the driver. The OCC can be in one of two
+states: OCC polling enabled or OCC polling disabled. The purpose of this file
+is to control the behavior of the driver and it's hwmon sysfs entries, not to
+infer any information about the state of the physical OCC. Reading the file
+returns either a 0 (polling disabled) or 1 (polling enabled). Writing 1 to the
+file enables OCC polling in the driver if communications can be established
+with the OCC. Writing a 0 to the driver disables OCC polling.
+
 BMC - Host Communications
 -------------------------
 
diff --git a/drivers/hwmon/occ/Makefile b/drivers/hwmon/occ/Makefile
index 93cb52f..a6881f9 100644
--- a/drivers/hwmon/occ/Makefile
+++ b/drivers/hwmon/occ/Makefile
@@ -1 +1 @@
-obj-$(CONFIG_SENSORS_PPC_OCC) += occ.o
+obj-$(CONFIG_SENSORS_PPC_OCC) += occ.o occ_sysfs.o
diff --git a/drivers/hwmon/occ/occ_sysfs.c b/drivers/hwmon/occ/occ_sysfs.c
new file mode 100644
index 0000000..b0e063da
--- /dev/null
+++ b/drivers/hwmon/occ/occ_sysfs.c
@@ -0,0 +1,492 @@
+/*
+ * occ_sysfs.c - OCC sysfs interface
+ *
+ * This file contains the methods and data structures for implementing the OCC
+ * hwmon sysfs entries.
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+
+#include "occ_sysfs.h"
+
+#define MAX_SENSOR_ATTR_LEN	32
+
+#define RESP_RETURN_CMD_INVAL	0x13
+
+struct sensor_attr_data {
+	enum sensor_type type;
+	u32 hwmon_index;
+	u32 attr_id;
+	char name[MAX_SENSOR_ATTR_LEN];
+	struct device_attribute dev_attr;
+};
+
+static ssize_t show_input(struct device *dev,
+			  struct device_attribute *attr, char *buf)
+{
+	int val;
+	struct sensor_attr_data *sdata = container_of(attr,
+						      struct sensor_attr_data,
+						      dev_attr);
+	struct occ_sysfs *driver = dev_get_drvdata(dev);
+
+	val = occ_get_sensor_value(driver->occ, sdata->type,
+				   sdata->hwmon_index - 1);
+	if (sdata->type == TEMP)
+		val *= 1000;	/* in millidegree Celsius */
+
+	return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
+}
+
+/* show_label provides the OCC sensor id. The sensor id will be either a
+ * 2-byte (for P8) or 4-byte (for P9) value. The sensor id is a way to
+ * identify what each sensor represents, according to the OCC specification.
+ */
+static ssize_t show_label(struct device *dev,
+			  struct device_attribute *attr, char *buf)
+{
+	int val;
+	struct sensor_attr_data *sdata = container_of(attr,
+						      struct sensor_attr_data,
+						      dev_attr);
+	struct occ_sysfs *driver = dev_get_drvdata(dev);
+
+	val = occ_get_sensor_id(driver->occ, sdata->type,
+				sdata->hwmon_index - 1);
+
+	return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
+}
+
+static ssize_t show_caps(struct device *dev,
+			 struct device_attribute *attr, char *buf)
+{
+	int val;
+	struct caps_sensor *sensor;
+	struct sensor_attr_data *sdata = container_of(attr,
+						      struct sensor_attr_data,
+						      dev_attr);
+	struct occ_sysfs *driver = dev_get_drvdata(dev);
+
+	sensor = occ_get_sensor(driver->occ, CAPS);
+	if (!sensor) {
+		val = -1;
+		return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
+	}
+
+	val = occ_get_caps_value(driver->occ, sensor, sdata->hwmon_index - 1,
+				 sdata->attr_id);
+
+	return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
+}
+
+static ssize_t show_update_interval(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	struct occ_sysfs *driver = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE - 1, "%lu\n", driver->update_interval);
+}
+
+static ssize_t store_update_interval(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct occ_sysfs *driver = dev_get_drvdata(dev);
+	unsigned long val;
+	int rc;
+
+	rc = kstrtoul(buf, 10, &val);
+	if (rc)
+		return rc;
+
+	driver->update_interval = val;
+	occ_set_update_interval(driver->occ, val);
+
+	return count;
+}
+
+static DEVICE_ATTR(update_interval, S_IWUSR | S_IRUGO, show_update_interval,
+		   store_update_interval);
+
+static ssize_t show_name(struct device *dev,
+			 struct device_attribute *attr, char *buf)
+{
+	return snprintf(buf, PAGE_SIZE - 1, "occ\n");
+}
+
+static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
+
+static ssize_t show_user_powercap(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct occ_sysfs *driver = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE - 1, "%u\n", driver->user_powercap);
+}
+
+static ssize_t store_user_powercap(struct device *dev,
+				   struct device_attribute *attr,
+				   const char *buf, size_t count)
+{
+	struct occ_sysfs *driver = dev_get_drvdata(dev);
+	u16 val;
+	int rc;
+
+	rc = kstrtou16(buf, 10, &val);
+	if (rc)
+		return rc;
+
+	dev_dbg(dev, "set user powercap to: %d\n", val);
+	rc = occ_set_user_powercap(driver->occ, val);
+	if (rc) {
+		dev_err(dev, "set user powercap failed: 0x%x\n", rc);
+		if (rc == RESP_RETURN_CMD_INVAL) {
+			dev_err(dev, "set invalid powercap value: %d\n", val);
+			return -EINVAL;
+		}
+
+		return rc;
+	}
+
+	driver->user_powercap = val;
+
+	return count;
+}
+
+static DEVICE_ATTR(user_powercap, S_IWUSR | S_IRUGO, show_user_powercap,
+		   store_user_powercap);
+
+static void deinit_sensor_groups(struct device *dev,
+				 struct sensor_group *sensor_groups)
+{
+	int i;
+
+	for (i = 0; i < MAX_OCC_SENSOR_TYPE; i++) {
+		if (sensor_groups[i].group.attrs)
+			devm_kfree(dev, sensor_groups[i].group.attrs);
+		if (sensor_groups[i].sattr)
+			devm_kfree(dev, sensor_groups[i].sattr);
+		sensor_groups[i].group.attrs = NULL;
+		sensor_groups[i].sattr = NULL;
+	}
+}
+
+static void sensor_attr_init(struct sensor_attr_data *sdata,
+			     char *sensor_group_name,
+			     char *attr_name,
+			     ssize_t (*show)(struct device *dev,
+					     struct device_attribute *attr,
+					     char *buf))
+{
+	sysfs_attr_init(&sdata->dev_attr.attr);
+
+	snprintf(sdata->name, MAX_SENSOR_ATTR_LEN, "%s%d_%s",
+		 sensor_group_name, sdata->hwmon_index, attr_name);
+	sdata->dev_attr.attr.name = sdata->name;
+	sdata->dev_attr.attr.mode = S_IRUGO;
+	sdata->dev_attr.show = show;
+}
+
+static int create_sensor_group(struct occ_sysfs *driver,
+			       enum sensor_type type, int sensor_num)
+{
+	struct device *dev = driver->dev;
+	struct sensor_group *sensor_groups = driver->sensor_groups;
+	struct sensor_attr_data *sdata;
+	int rc, i;
+
+	/* each sensor has 'label' and 'input' attributes */
+	sensor_groups[type].group.attrs =
+		devm_kzalloc(dev, sizeof(struct attribute *) *
+			     sensor_num * 2 + 1, GFP_KERNEL);
+	if (!sensor_groups[type].group.attrs) {
+		rc = -ENOMEM;
+		goto err;
+	}
+
+	sensor_groups[type].sattr =
+		devm_kzalloc(dev, sizeof(struct sensor_attr_data) *
+			     sensor_num * 2, GFP_KERNEL);
+	if (!sensor_groups[type].sattr) {
+		rc = -ENOMEM;
+		goto err;
+	}
+
+	for (i = 0; i < sensor_num; i++) {
+		sdata = &sensor_groups[type].sattr[i];
+		/* hwmon attributes index starts from 1 */
+		sdata->hwmon_index = i + 1;
+		sdata->type = type;
+		sensor_attr_init(sdata, sensor_groups[type].name, "input",
+				 show_input);
+		sensor_groups[type].group.attrs[i] = &sdata->dev_attr.attr;
+
+		sdata = &sensor_groups[type].sattr[i + sensor_num];
+		sdata->hwmon_index = i + 1;
+		sdata->type = type;
+		sensor_attr_init(sdata, sensor_groups[type].name, "label",
+				 show_label);
+		sensor_groups[type].group.attrs[i + sensor_num] =
+			&sdata->dev_attr.attr;
+	}
+
+	rc = sysfs_create_group(&dev->kobj, &sensor_groups[type].group);
+	if (rc)
+		goto err;
+
+	return 0;
+err:
+	deinit_sensor_groups(dev, sensor_groups);
+	return rc;
+}
+
+static void caps_sensor_attr_init(struct sensor_attr_data *sdata,
+				  char *attr_name, uint32_t hwmon_index,
+				  uint32_t attr_id)
+{
+	sdata->type = CAPS;
+	sdata->hwmon_index = hwmon_index;
+	sdata->attr_id = attr_id;
+
+	snprintf(sdata->name, MAX_SENSOR_ATTR_LEN, "%s%d_%s",
+		 "caps", sdata->hwmon_index, attr_name);
+
+	sysfs_attr_init(&sdata->dev_attr.attr);
+	sdata->dev_attr.attr.name = sdata->name;
+	sdata->dev_attr.attr.mode = S_IRUGO;
+	sdata->dev_attr.show = show_caps;
+}
+
+static int create_caps_sensor_group(struct occ_sysfs *driver, int sensor_num)
+{
+	struct device *dev = driver->dev;
+	struct sensor_group *sensor_groups = driver->sensor_groups;
+	int field_num = driver->num_caps_fields;
+	struct sensor_attr_data *sdata;
+	int i, j, rc;
+
+	sensor_groups[CAPS].group.attrs =
+		devm_kzalloc(dev, sizeof(struct attribute *) * sensor_num *
+			     field_num + 1, GFP_KERNEL);
+	if (!sensor_groups[CAPS].group.attrs) {
+		rc = -ENOMEM;
+		goto err;
+	}
+
+	sensor_groups[CAPS].sattr =
+		devm_kzalloc(dev, sizeof(struct sensor_attr_data) *
+			     sensor_num * field_num, GFP_KERNEL);
+	if (!sensor_groups[CAPS].sattr) {
+		rc = -ENOMEM;
+		goto err;
+	}
+
+	for (j = 0; j < sensor_num; ++j) {
+		for (i = 0; i < field_num; ++i) {
+			sdata = &sensor_groups[CAPS].sattr[j * field_num + i];
+			caps_sensor_attr_init(sdata,
+					      driver->caps_names[i], j + 1, i);
+			sensor_groups[CAPS].group.attrs[j * field_num + i] =
+				&sdata->dev_attr.attr;
+		}
+	}
+
+	rc = sysfs_create_group(&dev->kobj, &sensor_groups[CAPS].group);
+	if (rc)
+		goto err;
+
+	return rc;
+err:
+	deinit_sensor_groups(dev, sensor_groups);
+	return rc;
+}
+
+static void occ_remove_hwmon_attrs(struct occ_sysfs *driver)
+{
+	struct device *dev = driver->dev;
+
+	device_remove_file(dev, &dev_attr_user_powercap);
+	device_remove_file(dev, &dev_attr_update_interval);
+	device_remove_file(dev, &dev_attr_name);
+}
+
+static int occ_create_hwmon_attrs(struct occ_sysfs *driver)
+{
+	int i, rc, id, sensor_num;
+	struct device *dev = driver->dev;
+	struct sensor_group *sensor_groups = driver->sensor_groups;
+	struct occ_blocks *resp = NULL;
+
+	occ_get_response_blocks(driver->occ, &resp);
+
+	for (i = 0; i < MAX_OCC_SENSOR_TYPE; ++i)
+		resp->sensor_block_id[i] = -1;
+
+	/* read sensor data from occ */
+	rc = occ_update_device(driver->occ);
+	if (rc) {
+		dev_err(dev, "cannot get occ sensor data: %d\n", rc);
+		return rc;
+	}
+	if (!resp->blocks)
+		return -ENOMEM;
+
+	rc = device_create_file(dev, &dev_attr_name);
+	if (rc)
+		goto error;
+
+	rc = device_create_file(dev, &dev_attr_update_interval);
+	if (rc)
+		goto error;
+
+	if (resp->sensor_block_id[CAPS] >= 0) {
+		/* user powercap: only for master OCC */
+		rc = device_create_file(dev, &dev_attr_user_powercap);
+		if (rc)
+			goto error;
+	}
+
+	sensor_groups[FREQ].name = "freq";
+	sensor_groups[TEMP].name = "temp";
+	sensor_groups[POWER].name = "power";
+	sensor_groups[CAPS].name = "caps";
+
+	for (i = 0; i < MAX_OCC_SENSOR_TYPE; i++) {
+		id = resp->sensor_block_id[i];
+		if (id < 0)
+			continue;
+
+		sensor_num = resp->blocks[id].header.sensor_num;
+		if (i == CAPS)
+			rc = create_caps_sensor_group(driver, sensor_num);
+		else
+			rc = create_sensor_group(driver, i, sensor_num);
+		if (rc)
+			goto error;
+	}
+
+	return 0;
+
+error:
+	dev_err(dev, "cannot create hwmon attributes: %d\n", rc);
+	occ_remove_hwmon_attrs(driver);
+	return rc;
+}
+
+static ssize_t show_occ_online(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	struct occ_sysfs *driver = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE - 1, "%u\n", driver->occ_online);
+}
+
+static ssize_t store_occ_online(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct occ_sysfs *driver = dev_get_drvdata(dev);
+	unsigned long val;
+	int rc;
+
+	rc = kstrtoul(buf, 10, &val);
+	if (rc)
+		return rc;
+
+	if (val == 1) {
+		if (driver->occ_online)
+			return count;
+
+		driver->dev = hwmon_device_register(dev);
+		if (IS_ERR(driver->dev))
+			return PTR_ERR(driver->dev);
+
+		dev_set_drvdata(driver->dev, driver);
+
+		rc = occ_create_hwmon_attrs(driver);
+		if (rc) {
+			hwmon_device_unregister(driver->dev);
+			driver->dev = NULL;
+			return rc;
+		}
+	} else if (val == 0) {
+		if (!driver->occ_online)
+			return count;
+
+		occ_remove_hwmon_attrs(driver);
+		hwmon_device_unregister(driver->dev);
+		driver->dev = NULL;
+	} else
+		return -EINVAL;
+
+	driver->occ_online = val;
+	return count;
+}
+
+static DEVICE_ATTR(online, S_IWUSR | S_IRUGO, show_occ_online,
+		   store_occ_online);
+
+struct occ_sysfs *occ_sysfs_start(struct device *dev, struct occ *occ,
+				  struct occ_sysfs_config *config)
+{
+	struct occ_sysfs *hwmon = devm_kzalloc(dev, sizeof(struct occ_sysfs),
+					       GFP_KERNEL);
+	int rc;
+
+	if (!hwmon)
+		return ERR_PTR(-ENOMEM);
+
+	hwmon->occ = occ;
+	hwmon->num_caps_fields = config->num_caps_fields;
+	hwmon->caps_names = config->caps_names;
+
+	dev_set_drvdata(dev, hwmon);
+
+	rc = device_create_file(dev, &dev_attr_online);
+	if (rc)
+		return ERR_PTR(rc);
+
+	return hwmon;
+}
+EXPORT_SYMBOL(occ_sysfs_start);
+
+int occ_sysfs_stop(struct device *dev, struct occ_sysfs *driver)
+{
+	if (driver->dev) {
+		occ_remove_hwmon_attrs(driver);
+		hwmon_device_unregister(driver->dev);
+	}
+
+	device_remove_file(driver->dev, &dev_attr_online);
+
+	devm_kfree(dev, driver);
+
+	return 0;
+}
+EXPORT_SYMBOL(occ_sysfs_stop);
+
+MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>");
+MODULE_DESCRIPTION("OCC sysfs driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/occ/occ_sysfs.h b/drivers/hwmon/occ/occ_sysfs.h
new file mode 100644
index 0000000..2a8044f
--- /dev/null
+++ b/drivers/hwmon/occ/occ_sysfs.h
@@ -0,0 +1,52 @@
+/*
+ * occ_sysfs.h - OCC sysfs interface
+ *
+ * This file contains the data structures and function prototypes for the OCC
+ * hwmon sysfs entries.
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * 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.
+ */
+
+#ifndef __OCC_SYSFS_H__
+#define __OCC_SYSFS_H__
+
+#include "occ.h"
+
+struct sensor_group {
+	char *name;
+	struct sensor_attr_data *sattr;
+	struct attribute_group group;
+};
+
+struct occ_sysfs_config {
+	unsigned int num_caps_fields;
+	char **caps_names;
+};
+
+struct occ_sysfs {
+	struct device *dev;
+	struct occ *occ;
+
+	u16 user_powercap;
+	bool occ_online;
+	struct sensor_group sensor_groups[MAX_OCC_SENSOR_TYPE];
+	unsigned long update_interval;
+	unsigned int num_caps_fields;
+	char **caps_names;
+};
+
+struct occ_sysfs *occ_sysfs_start(struct device *dev, struct occ *occ,
+				  struct occ_sysfs_config *config);
+int occ_sysfs_stop(struct device *dev, struct occ_sysfs *driver);
+
+#endif /* __OCC_SYSFS_H__ */
-- 
1.9.1

^ permalink raw reply related

* [PATCH linux 1/6] hwmon: Add core On-Chip Controller support for POWER CPUs
From: eajames.ibm @ 2016-12-30 17:56 UTC (permalink / raw)
  To: linux
  Cc: jdelvare, corbet, mark.rutland, robh+dt, wsa, andrew, joel,
	devicetree, linux-doc, linux-hwmon, linux-i2c, linux-kernel,
	Edward A. James
In-Reply-To: <1483120568-21082-1-git-send-email-eajames.ibm@gmail.com>

From: "Edward A. James" <eajames@us.ibm.com>

Add core support for polling the OCC for it's sensor data and parsing that
data into sensor-specific information.

Signed-off-by: Edward A. James <eajames@us.ibm.com>
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
---
 Documentation/hwmon/occ    |  40 ++++
 drivers/hwmon/Kconfig      |   2 +
 drivers/hwmon/Makefile     |   1 +
 drivers/hwmon/occ/Kconfig  |  15 ++
 drivers/hwmon/occ/Makefile |   1 +
 drivers/hwmon/occ/occ.c    | 544 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/occ.h    |  86 +++++++
 drivers/hwmon/occ/scom.h   |  47 ++++
 8 files changed, 736 insertions(+)
 create mode 100644 Documentation/hwmon/occ
 create mode 100644 drivers/hwmon/occ/Kconfig
 create mode 100644 drivers/hwmon/occ/Makefile
 create mode 100644 drivers/hwmon/occ/occ.c
 create mode 100644 drivers/hwmon/occ/occ.h
 create mode 100644 drivers/hwmon/occ/scom.h

diff --git a/Documentation/hwmon/occ b/Documentation/hwmon/occ
new file mode 100644
index 0000000..79d1642
--- /dev/null
+++ b/Documentation/hwmon/occ
@@ -0,0 +1,40 @@
+Kernel driver occ
+=================
+
+Supported chips:
+ * ASPEED AST2400
+ * ASPEED AST2500
+
+Please note that the chip must be connected to a POWER8 or POWER9 processor
+(see the BMC - Host Communications section).
+
+Author: Eddie James <eajames@us.ibm.com>
+
+Description
+-----------
+
+This driver implements support for the OCC (On-Chip Controller) on the IBM
+POWER8 and POWER9 processors, from a BMC (Baseboard Management Controller). The
+OCC is an embedded processor that provides real time power and thermal
+monitoring.
+
+This driver provides an interface on a BMC to poll OCC sensor data, set user
+power caps, and perform some basic OCC error handling.
+
+Currently, all versions of the OCC support four types of sensor data: power,
+temperature, frequency, and "caps," which indicate limits and thresholds used
+internally on the OCC.
+
+BMC - Host Communications
+-------------------------
+
+For the POWER8 application, the BMC can communicate with the P8 over I2C bus.
+However, to access the OCC register space, any data transfer must use a SCOM
+operation. SCOM is a procedure to initiate a data transfer, typically of 8
+bytes. SCOMs consist of writing a 32-bit command register and then
+reading/writing two 32-bit data registers. This driver implements these
+SCOM operations over I2C bus in order to communicate with the OCC.
+
+For the POWER9 application, the BMC can communicate with the P9 over FSI bus
+and SBE engine. Once again, SCOM operations are required. This driver will
+implement SCOM ops over FSI/SBE. This will require the FSI driver.
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 190d270..e80ca81 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1240,6 +1240,8 @@ config SENSORS_NSA320
 	  This driver can also be built as a module. If so, the module
 	  will be called nsa320-hwmon.
 
+source drivers/hwmon/occ/Kconfig
+
 config SENSORS_PCF8591
 	tristate "Philips PCF8591 ADC/DAC"
 	depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index d2cb7e8..c7ec5d4 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -169,6 +169,7 @@ obj-$(CONFIG_SENSORS_WM831X)	+= wm831x-hwmon.o
 obj-$(CONFIG_SENSORS_WM8350)	+= wm8350-hwmon.o
 obj-$(CONFIG_SENSORS_XGENE)	+= xgene-hwmon.o
 
+obj-$(CONFIG_SENSORS_PPC_OCC)	+= occ/
 obj-$(CONFIG_PMBUS)		+= pmbus/
 
 ccflags-$(CONFIG_HWMON_DEBUG_CHIP) := -DDEBUG
diff --git a/drivers/hwmon/occ/Kconfig b/drivers/hwmon/occ/Kconfig
new file mode 100644
index 0000000..cdb64a7
--- /dev/null
+++ b/drivers/hwmon/occ/Kconfig
@@ -0,0 +1,15 @@
+#
+# On Chip Controller configuration
+#
+
+menuconfig SENSORS_PPC_OCC
+	bool "PPC On-Chip Controller"
+	help
+	  If you say yes here you get support to monitor Power CPU
+	  sensors via the On-Chip Controller (OCC).
+
+	  Generally this is used by management controllers such as a BMC
+	  on an OpenPower system.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called occ.
diff --git a/drivers/hwmon/occ/Makefile b/drivers/hwmon/occ/Makefile
new file mode 100644
index 0000000..93cb52f
--- /dev/null
+++ b/drivers/hwmon/occ/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_SENSORS_PPC_OCC) += occ.o
diff --git a/drivers/hwmon/occ/occ.c b/drivers/hwmon/occ/occ.c
new file mode 100644
index 0000000..9884ddd
--- /dev/null
+++ b/drivers/hwmon/occ/occ.c
@@ -0,0 +1,544 @@
+/*
+ * occ.c - OCC hwmon driver
+ *
+ * This file contains the methods and data structures for the OCC hwmon driver.
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <asm/unaligned.h>
+
+#include "occ.h"
+
+#define OCC_DATA_MAX		4096
+#define OCC_BMC_TIMEOUT_MS	20000
+
+/* To generate attn to OCC */
+#define ATTN_DATA		0x0006B035
+
+/* For BMC to read/write SRAM */
+#define OCB_ADDRESS		0x0006B070
+#define OCB_DATA		0x0006B075
+#define OCB_STATUS_CONTROL_AND	0x0006B072
+#define OCB_STATUS_CONTROL_OR	0x0006B073
+
+/* To init OCB */
+#define OCB_AND_INIT0		0xFBFFFFFF
+#define OCB_AND_INIT1		0xFFFFFFFF
+#define OCB_OR_INIT0		0x08000000
+#define OCB_OR_INIT1		0x00000000
+
+/* To generate attention on OCC */
+#define ATTN0			0x01010000
+#define ATTN1			0x00000000
+
+/* OCC return status */
+#define RESP_RETURN_CMD_IN_PRG	0xFF
+#define RESP_RETURN_SUCCESS	0
+#define RESP_RETURN_CMD_INVAL	0x11
+#define RESP_RETURN_CMD_LEN	0x12
+#define RESP_RETURN_DATA_INVAL	0x13
+#define RESP_RETURN_CHKSUM	0x14
+#define RESP_RETURN_OCC_ERR	0x15
+#define RESP_RETURN_STATE	0x16
+
+/* time interval to retry on "command in progress" return status */
+#define CMD_IN_PRG_INT_MS	100
+#define CMD_IN_PRG_RETRIES	(OCC_BMC_TIMEOUT_MS / CMD_IN_PRG_INT_MS)
+
+/* OCC command definitions */
+#define OCC_POLL		0
+#define OCC_SET_USER_POWR_CAP	0x22
+
+/* OCC poll command data */
+#define OCC_POLL_STAT_SENSOR	0x10
+
+/* OCC response data offsets */
+#define RESP_RETURN_STATUS	2
+#define RESP_DATA_LENGTH	3
+#define RESP_HEADER_OFFSET	5
+#define SENSOR_STR_OFFSET	37
+#define SENSOR_BLOCK_NUM_OFFSET	43
+#define SENSOR_BLOCK_OFFSET	45
+
+/* occ_poll_header
+ * structure to match the raw occ poll response data
+ */
+struct occ_poll_header {
+	u8 status;
+	u8 ext_status;
+	u8 occs_present;
+	u8 config;
+	u8 occ_state;
+	u8 mode;
+	u8 ips_status;
+	u8 error_log_id;
+	u32 error_log_addr_start;
+	u16 error_log_length;
+	u8 reserved2;
+	u8 reserved3;
+	u8 occ_code_level[16];
+	u8 sensor_eye_catcher[6];
+	u8 sensor_block_num;
+	u8 sensor_data_version;
+} __attribute__((packed, aligned(4)));
+
+struct occ_response {
+	struct occ_poll_header header;
+	struct occ_blocks data;
+};
+
+struct occ {
+	struct device *dev;
+	void *bus;
+	struct occ_bus_ops bus_ops;
+	struct occ_ops ops;
+	struct occ_config config;
+	unsigned long update_interval;
+	unsigned long last_updated;
+	struct mutex update_lock;
+	struct occ_response response;
+	bool valid;
+};
+
+static void deinit_occ_resp_buf(struct occ_response *resp)
+{
+	int i;
+
+	if (!resp)
+		return;
+
+	if (!resp->data.blocks)
+		return;
+
+	for (i = 0; i < resp->header.sensor_block_num; ++i)
+		kfree(resp->data.blocks[i].sensors);
+
+	kfree(resp->data.blocks);
+
+	memset(resp, 0, sizeof(struct occ_response));
+
+	for (i = 0; i < MAX_OCC_SENSOR_TYPE; ++i)
+		resp->data.sensor_block_id[i] = -1;
+}
+
+static void *occ_get_sensor_by_type(struct occ_response *resp,
+				    enum sensor_type t)
+{
+	if (!resp->data.blocks)
+		return NULL;
+
+	if (resp->data.sensor_block_id[t] == -1)
+		return NULL;
+
+	return resp->data.blocks[resp->data.sensor_block_id[t]].sensors;
+}
+
+static int occ_check_sensor(struct occ *driver, u8 sensor_length,
+			    u8 sensor_num, enum sensor_type t, int block)
+{
+	void *sensor;
+	int type_block_id;
+	struct occ_response *resp = &driver->response;
+
+	sensor = occ_get_sensor_by_type(resp, t);
+
+	/* empty sensor block, release older sensor data */
+	if (sensor_num == 0 || sensor_length == 0) {
+		kfree(sensor);
+		dev_err(driver->dev, "no sensor blocks available\n");
+		return -ENODATA;
+	}
+
+	type_block_id = resp->data.sensor_block_id[t];
+	if (!sensor || sensor_num !=
+	    resp->data.blocks[type_block_id].header.sensor_num) {
+		kfree(sensor);
+		resp->data.blocks[block].sensors =
+			driver->ops.alloc_sensor(t, sensor_num);
+		if (!resp->data.blocks[block].sensors)
+			return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static int parse_occ_response(struct occ *driver, u8 *data,
+			      struct occ_response *resp)
+{
+	int b;
+	int s;
+	int rc;
+	int offset = SENSOR_BLOCK_OFFSET;
+	int sensor_type;
+	u8 sensor_block_num;
+	char sensor_type_string[5] = { 0 };
+	struct sensor_data_block_header *block;
+	struct device *dev = driver->dev;
+
+	/* check if the data is valid */
+	if (strncmp(&data[SENSOR_STR_OFFSET], "SENSOR", 6) != 0) {
+		dev_err(dev, "no SENSOR string in response\n");
+		rc = -ENODATA;
+		goto err;
+	}
+
+	sensor_block_num = data[SENSOR_BLOCK_NUM_OFFSET];
+	if (sensor_block_num == 0) {
+		dev_err(dev, "no sensor blocks available\n");
+		rc = -ENODATA;
+		goto err;
+	}
+
+	/* if number of sensor block has changed, re-malloc */
+	if (sensor_block_num != resp->header.sensor_block_num) {
+		deinit_occ_resp_buf(resp);
+		resp->data.blocks = kcalloc(sensor_block_num,
+					    sizeof(struct sensor_data_block),
+					    GFP_KERNEL);
+		if (!resp->data.blocks)
+			return -ENOMEM;
+	}
+
+	memcpy(&resp->header, &data[RESP_HEADER_OFFSET],
+	       sizeof(struct occ_poll_header));
+	resp->header.error_log_addr_start =
+		be32_to_cpu(resp->header.error_log_addr_start);
+	resp->header.error_log_length =
+		be16_to_cpu(resp->header.error_log_length);
+
+	dev_dbg(dev, "Reading %d sensor blocks\n",
+		resp->header.sensor_block_num);
+	for (b = 0; b < sensor_block_num; b++) {
+		block = (struct sensor_data_block_header *)&data[offset];
+		/* copy to a null terminated string */
+		strncpy(sensor_type_string, block->sensor_type, 4);
+		offset += 8;
+
+		dev_dbg(dev, "sensor block[%d]: type: %s, sensor_num: %d\n", b,
+			sensor_type_string, block->sensor_num);
+
+		if (strncmp(block->sensor_type, "FREQ", 4) == 0)
+			sensor_type = FREQ;
+		else if (strncmp(block->sensor_type, "TEMP", 4) == 0)
+			sensor_type = TEMP;
+		else if (strncmp(block->sensor_type, "POWR", 4) == 0)
+			sensor_type = POWER;
+		else if (strncmp(block->sensor_type, "CAPS", 4) == 0)
+			sensor_type = CAPS;
+		else {
+			dev_err(dev, "sensor type not supported %s\n",
+				sensor_type_string);
+			continue;
+		}
+
+		rc = occ_check_sensor(driver, block->sensor_length,
+				      block->sensor_num, sensor_type, b);
+		if (rc == -ENOMEM)
+			goto err;
+		else if (rc)
+			continue;
+
+		resp->data.sensor_block_id[sensor_type] = b;
+		for (s = 0; s < block->sensor_num; s++) {
+			driver->ops.parse_sensor(data,
+						 resp->data.blocks[b].sensors,
+						 sensor_type, offset, s);
+			offset += block->sensor_length;
+		}
+
+		/* copy block data over to response pointer */
+		resp->data.blocks[b].header = *block;
+	}
+
+	return 0;
+err:
+	deinit_occ_resp_buf(resp);
+	return rc;
+}
+
+static u8 occ_send_cmd(struct occ *driver, u8 seq, u8 type, u16 length,
+		       u8 *data, u8 *resp)
+{
+	u32 cmd1, cmd2;
+	u16 checksum = 0;
+	u16 length_le = cpu_to_le16(length);
+	bool retry = 0;
+	int i, rc, tries = 0;
+
+	cmd1 = (seq << 24) | (type << 16) | length_le;
+	memcpy(&cmd2, data, length);
+	cmd2 <<= ((4 - length) * 8);
+
+	/* checksum: sum of every bytes of cmd1, cmd2 */
+	for (i = 0; i < 4; i++) {
+		checksum += (cmd1 >> (i * 8)) & 0xFF;
+		checksum += (cmd2 >> (i * 8)) & 0xFF;
+	}
+
+	cmd2 |= checksum << ((2 - length) * 8);
+
+	/* Init OCB */
+	rc = driver->bus_ops.putscom(driver->bus, OCB_STATUS_CONTROL_OR,
+				     OCB_OR_INIT0, OCB_OR_INIT1);
+	if (rc)
+		goto err;
+
+	rc = driver->bus_ops.putscom(driver->bus, OCB_STATUS_CONTROL_AND,
+				     OCB_AND_INIT0, OCB_AND_INIT1);
+	if (rc)
+		goto err;
+
+	/* Send command, 2nd half of the 64-bit addr is unused (write 0) */
+	rc = driver->bus_ops.putscom(driver->bus, OCB_ADDRESS,
+				     driver->config.command_addr, 0);
+	if (rc)
+		goto err;
+
+	rc = driver->bus_ops.putscom(driver->bus, OCB_DATA, cmd1, cmd2);
+	if (rc)
+		goto err;
+
+	/* Trigger attention */
+	rc = driver->bus_ops.putscom(driver->bus, ATTN_DATA, ATTN0, ATTN1);
+	if (rc)
+		goto err;
+
+	/* Get response data */
+	rc = driver->bus_ops.putscom(driver->bus, OCB_ADDRESS,
+				     driver->config.response_addr, 0);
+	if (rc)
+		goto err;
+
+	do {
+		if (retry) {
+			set_current_state(TASK_INTERRUPTIBLE);
+			schedule_timeout(msecs_to_jiffies(CMD_IN_PRG_INT_MS));
+		}
+
+		rc = driver->bus_ops.getscom(driver->bus, OCB_DATA,
+					     (u64 *)resp);
+		if (rc)
+			goto err;
+
+		/* retry if we get "command in progress" return status */
+		retry = (resp[RESP_RETURN_STATUS] == RESP_RETURN_CMD_IN_PRG) &&
+			(tries++ < CMD_IN_PRG_RETRIES);
+	} while (retry);
+
+	switch (resp[RESP_RETURN_STATUS]) {
+	case RESP_RETURN_CMD_IN_PRG:
+		rc = -EALREADY;
+		break;
+	case RESP_RETURN_SUCCESS:
+		rc = 0;
+		break;
+	case RESP_RETURN_CMD_INVAL:
+	case RESP_RETURN_CMD_LEN:
+	case RESP_RETURN_DATA_INVAL:
+	case RESP_RETURN_CHKSUM:
+		rc = -EINVAL;
+		break;
+	case RESP_RETURN_OCC_ERR:
+		rc = -EREMOTE;
+		break;
+	default:
+		rc = -EFAULT;
+	}
+
+	return rc;
+
+err:
+	dev_err(driver->dev, "scom op failed rc:%d\n", rc);
+	return rc;
+}
+
+static int occ_get_all(struct occ *driver)
+{
+	int i = 0, rc;
+	u8 *occ_data;
+	u16 num_bytes;
+	const u8 poll_cmd_data = OCC_POLL_STAT_SENSOR;
+	struct device *dev = driver->dev;
+	struct occ_response *resp = &driver->response;
+
+	occ_data = devm_kzalloc(dev, OCC_DATA_MAX, GFP_KERNEL);
+	if (!occ_data)
+		return -ENOMEM;
+
+	rc = occ_send_cmd(driver, 0, OCC_POLL, 1, &poll_cmd_data, occ_data);
+	if (rc) {
+		dev_err(dev, "OCC poll failed: %d\n", rc);
+		goto out;
+	}
+
+	num_bytes = get_unaligned((u16 *)&occ_data[RESP_DATA_LENGTH]);
+	num_bytes = be16_to_cpu(num_bytes);
+	dev_dbg(dev, "OCC data length: %d\n", num_bytes);
+
+	if (num_bytes > OCC_DATA_MAX) {
+		dev_err(dev, "OCC data length must be < 4KB\n");
+		rc = -EINVAL;
+		goto out;
+	}
+
+	if (num_bytes <= 0) {
+		dev_err(dev, "OCC data length is zero\n");
+		rc = -EINVAL;
+		goto out;
+	}
+
+	/* read remaining data */
+	for (i = 8; i < num_bytes + 8; i += 8) {
+		rc = driver->bus_ops.getscom(driver->bus, OCB_DATA,
+					     (u64 *)&occ_data[i]);
+		if (rc) {
+			dev_err(dev, "scom op failed rc:%d\n", rc);
+			goto out;
+		}
+	}
+
+	/* don't need more sanity checks; buffer is alloc'd for max response
+	 * size so we just check for valid data in parse_occ_response
+	 */
+	rc = parse_occ_response(driver, occ_data, resp);
+
+out:
+	devm_kfree(dev, occ_data);
+	return rc;
+}
+
+int occ_update_device(struct occ *driver)
+{
+	int rc = 0;
+
+	mutex_lock(&driver->update_lock);
+
+	if (time_after(jiffies, driver->last_updated + driver->update_interval)
+	    || !driver->valid) {
+		driver->valid = 1;
+
+		rc = occ_get_all(driver);
+		if (rc)
+			driver->valid = 0;
+
+		driver->last_updated = jiffies;
+	}
+
+	mutex_unlock(&driver->update_lock);
+
+	return rc;
+}
+EXPORT_SYMBOL(occ_update_device);
+
+void *occ_get_sensor(struct occ *driver, int sensor_type)
+{
+	int rc;
+
+	/* occ_update_device locks the update lock */
+	rc = occ_update_device(driver);
+	if (rc != 0) {
+		dev_err(driver->dev, "cannot get occ sensor data: %d\n",
+			rc);
+		return NULL;
+	}
+
+	return occ_get_sensor_by_type(&driver->response, sensor_type);
+}
+EXPORT_SYMBOL(occ_get_sensor);
+
+int occ_get_sensor_value(struct occ *occ, int sensor_type, int snum)
+{
+	return occ->ops.get_sensor_value(occ, sensor_type, snum);
+}
+EXPORT_SYMBOL(occ_get_sensor_value);
+
+int occ_get_sensor_id(struct occ *occ, int sensor_type, int snum)
+{
+	return occ->ops.get_sensor_id(occ, sensor_type, snum);
+}
+EXPORT_SYMBOL(occ_get_sensor_id);
+
+int occ_get_caps_value(struct occ *occ, void *sensor, int snum, int caps_field)
+{
+	return occ->ops.get_caps_value(sensor, snum, caps_field);
+}
+EXPORT_SYMBOL(occ_get_caps_value);
+
+void occ_get_response_blocks(struct occ *occ, struct occ_blocks **blocks)
+{
+	*blocks = &occ->response.data;
+}
+EXPORT_SYMBOL(occ_get_response_blocks);
+
+void occ_set_update_interval(struct occ *occ, unsigned long interval)
+{
+	occ->update_interval = msecs_to_jiffies(interval);
+}
+EXPORT_SYMBOL(occ_set_update_interval);
+
+int occ_set_user_powercap(struct occ *occ, u16 cap)
+{
+	u8 resp[8];
+
+	cap = cpu_to_be16(cap);
+
+	return occ_send_cmd(occ, 0, OCC_SET_USER_POWR_CAP, 2, (u8 *)&cap,
+			    resp);
+}
+EXPORT_SYMBOL(occ_set_user_powercap);
+
+struct occ *occ_start(struct device *dev, void *bus,
+		      struct occ_bus_ops *bus_ops, const struct occ_ops *ops,
+		      const struct occ_config *config)
+{
+	struct occ *driver = devm_kzalloc(dev, sizeof(struct occ), GFP_KERNEL);
+
+	if (!driver)
+		return ERR_PTR(-ENOMEM);
+
+	driver->dev = dev;
+	driver->bus = bus;
+	driver->bus_ops = *bus_ops;
+	driver->ops = *ops;
+	driver->config = *config;
+
+	driver->update_interval = HZ;
+	mutex_init(&driver->update_lock);
+
+	return driver;
+}
+EXPORT_SYMBOL(occ_start);
+
+int occ_stop(struct occ *occ)
+{
+	devm_kfree(occ->dev, occ);
+
+	return 0;
+}
+EXPORT_SYMBOL(occ_stop);
+
+MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>");
+MODULE_DESCRIPTION("OCC hwmon core driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/occ/occ.h b/drivers/hwmon/occ/occ.h
new file mode 100644
index 0000000..8c08607
--- /dev/null
+++ b/drivers/hwmon/occ/occ.h
@@ -0,0 +1,86 @@
+/*
+ * occ.h - hwmon OCC driver
+ *
+ * This file contains data structures and function prototypes for common access
+ * between different bus protocols and host systems.
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * 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.
+ */
+
+#ifndef __OCC_H__
+#define __OCC_H__
+
+#include "scom.h"
+
+struct device;
+struct occ;
+
+/* sensor_data_block_header
+ * structure to match the raw occ sensor block header
+ */
+struct sensor_data_block_header {
+	u8 sensor_type[4];
+	u8 reserved0;
+	u8 sensor_format;
+	u8 sensor_length;
+	u8 sensor_num;
+} __attribute__((packed, aligned(4)));
+
+struct sensor_data_block {
+	struct sensor_data_block_header header;
+	void *sensors;
+};
+
+enum sensor_type {
+	FREQ = 0,
+	TEMP,
+	POWER,
+	CAPS,
+	MAX_OCC_SENSOR_TYPE
+};
+
+struct occ_ops {
+	void (*parse_sensor)(u8 *data, void *sensor, int sensor_type, int off,
+			     int snum);
+	void *(*alloc_sensor)(int sensor_type, int num_sensors);
+	int (*get_sensor_value)(struct occ *driver, int sensor_type, int snum);
+	int (*get_sensor_id)(struct occ *driver, int sensor_type, int snum);
+	int (*get_caps_value)(void *sensor, int snum, int caps_field);
+};
+
+struct occ_config {
+	u32 command_addr;
+	u32 response_addr;
+};
+
+struct occ_blocks {
+	int sensor_block_id[MAX_OCC_SENSOR_TYPE];
+	struct sensor_data_block *blocks;
+};
+
+struct occ *occ_start(struct device *dev, void *bus,
+		      struct occ_bus_ops *bus_ops, const struct occ_ops *ops,
+		      const struct occ_config *config);
+int occ_stop(struct occ *occ);
+
+void *occ_get_sensor(struct occ *occ, int sensor_type);
+int occ_get_sensor_value(struct occ *occ, int sensor_type, int snum);
+int occ_get_sensor_id(struct occ *occ, int sensor_type, int snum);
+int occ_get_caps_value(struct occ *occ, void *sensor, int snum,
+		       int caps_field);
+void occ_get_response_blocks(struct occ *occ, struct occ_blocks **blocks);
+int occ_update_device(struct occ *driver);
+void occ_set_update_interval(struct occ *occ, unsigned long interval);
+int occ_set_user_powercap(struct occ *occ, u16 cap);
+
+#endif /* __OCC_H__ */
diff --git a/drivers/hwmon/occ/scom.h b/drivers/hwmon/occ/scom.h
new file mode 100644
index 0000000..c1da645
--- /dev/null
+++ b/drivers/hwmon/occ/scom.h
@@ -0,0 +1,47 @@
+/*
+ * scom.h - hwmon OCC driver
+ *
+ * This file contains data structures for scom operations to the OCC
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * 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.
+ */
+
+#ifndef __SCOM_H__
+#define __SCOM_H__
+
+/*
+ * occ_bus_ops - represent the low-level transfer methods to communicate with
+ * the OCC.
+ *
+ * getscom - OCC scom read
+ * @bus: handle to slave device
+ * @address: address
+ * @data: where to store data read from slave; buffer size must be at least
+ * eight bytes.
+ *
+ * Returns 0 on success or a negative errno on error
+ *
+ * putscom - OCC scom write
+ * @bus: handle to slave device
+ * @address: address
+ * @data0: first data byte to write
+ * @data1: second data byte to write
+ *
+ * Returns 0 on success or a negative errno on error
+ */
+struct occ_bus_ops {
+	int (*getscom)(void *bus, u32 address, u64 *data);
+	int (*putscom)(void *bus, u32 address, u32 data0, u32 data1);
+};
+
+#endif /* __SCOM_H__ */
-- 
1.9.1

^ permalink raw reply related

* [PATCH linux 0/6] drivers: hwmon: Add On-Chip Controller driver
From: eajames.ibm @ 2016-12-30 17:56 UTC (permalink / raw)
  To: linux
  Cc: jdelvare, corbet, mark.rutland, robh+dt, wsa, andrew, joel,
	devicetree, linux-doc, linux-hwmon, linux-i2c, linux-kernel,
	Edward A. James

From: "Edward A. James" <eajames@us.ibm.com>

This patchset adds a hwmon driver to support the OCC (On-Chip Controller)
on the IBM POWER8 and POWER9 processors, from a BMC (Baseboard Management
Controller). The OCC is an embedded processor that provides real time
power and thermal monitoring.

The driver provides an interface on a BMC to poll OCC sensor data, set
user power caps, and perform some basic OCC error handling. It interfaces
with userspace through hwmon.

The driver is currently functional only for the OCC on POWER8 chips.
Communicating with the POWER9 OCC requries FSI support.

Edward A. James (6):
  hwmon: Add core On-Chip Controller support for POWER CPUs
  hwmon: occ: Add sysfs interface
  hwmon: occ: Add I2C transport implementation for SCOM operations
  hwmon: occ: Add callbacks for parsing P8 OCC datastructures
  hwmon: occ: Add hwmon implementation for the P8 OCC
  hwmon: occ: Add callbacks for parsing P9 OCC datastructures

 .../devicetree/bindings/i2c/i2c-ibm-occ.txt        |  13 +
 Documentation/hwmon/occ                            | 100 ++++
 drivers/hwmon/Kconfig                              |   2 +
 drivers/hwmon/Makefile                             |   1 +
 drivers/hwmon/occ/Kconfig                          |  29 ++
 drivers/hwmon/occ/Makefile                         |   2 +
 drivers/hwmon/occ/occ.c                            | 544 +++++++++++++++++++++
 drivers/hwmon/occ/occ.h                            |  86 ++++
 drivers/hwmon/occ/occ_p8.c                         | 217 ++++++++
 drivers/hwmon/occ/occ_p8.h                         |  30 ++
 drivers/hwmon/occ/occ_scom_i2c.c                   |  73 +++
 drivers/hwmon/occ/occ_scom_i2c.h                   |  26 +
 drivers/hwmon/occ/occ_sysfs.c                      | 492 +++++++++++++++++++
 drivers/hwmon/occ/occ_sysfs.h                      |  52 ++
 drivers/hwmon/occ/p8_occ_i2c.c                     | 141 ++++++
 drivers/hwmon/occ/p9_occ.c                         | 243 +++++++++
 drivers/hwmon/occ/p9_occ.h                         |  30 ++
 drivers/hwmon/occ/scom.h                           |  47 ++
 18 files changed, 2128 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/i2c/i2c-ibm-occ.txt
 create mode 100644 Documentation/hwmon/occ
 create mode 100644 drivers/hwmon/occ/Kconfig
 create mode 100644 drivers/hwmon/occ/Makefile
 create mode 100644 drivers/hwmon/occ/occ.c
 create mode 100644 drivers/hwmon/occ/occ.h
 create mode 100644 drivers/hwmon/occ/occ_p8.c
 create mode 100644 drivers/hwmon/occ/occ_p8.h
 create mode 100644 drivers/hwmon/occ/occ_scom_i2c.c
 create mode 100644 drivers/hwmon/occ/occ_scom_i2c.h
 create mode 100644 drivers/hwmon/occ/occ_sysfs.c
 create mode 100644 drivers/hwmon/occ/occ_sysfs.h
 create mode 100644 drivers/hwmon/occ/p8_occ_i2c.c
 create mode 100644 drivers/hwmon/occ/p9_occ.c
 create mode 100644 drivers/hwmon/occ/p9_occ.h
 create mode 100644 drivers/hwmon/occ/scom.h

-- 
1.9.1


^ permalink raw reply

* Re: [PATCH 1/2 v2] iio: light: add DT bindings for Capella CM3605
From: Jonathan Cameron @ 2016-12-30 17:18 UTC (permalink / raw)
  To: Linus Walleij, linux-iio-u79uwXL29TY76Z2rM5mHXA
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, Capella Microsystems,
	Kevin Tsai
In-Reply-To: <1482098155-16148-1-git-send-email-linus.walleij-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>

On 18/12/16 21:55, Linus Walleij wrote:
> This adds device tree bindings for the Capella Microsystems CM3605
> ambient light sensor and short range proximity sensor.
> 
> Cc: devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> Cc: Capella Microsystems <capellamicro-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> Cc: Kevin Tsai <ktsai-GubuWUlQtMwciDkP5Hr2oA@public.gmane.org>
> Acked-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> Signed-off-by: Linus Walleij <linus.walleij-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
Applied
> ---
> ChangeLog v1->v2:
> - Rename aset-resistance to aset-resistance-ohms
> ---
>  .../devicetree/bindings/iio/light/cm3605.txt       | 41 ++++++++++++++++++++++
>  1 file changed, 41 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/iio/light/cm3605.txt
> 
> diff --git a/Documentation/devicetree/bindings/iio/light/cm3605.txt b/Documentation/devicetree/bindings/iio/light/cm3605.txt
> new file mode 100644
> index 000000000000..56331a79f9ab
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/light/cm3605.txt
> @@ -0,0 +1,41 @@
> +Capella Microsystems CM3605
> +Ambient Light and Short Distance Proximity Sensor
> +
> +The CM3605 is an entirely analog part which however require quite a bit of
> +software logic to interface a host operating system.
> +
> +This ALS and proximity sensor was one of the very first deployed in mobile
> +handsets, notably it is used in the very first Nexus One Android phone from
> +2010.
> +
> +Required properties:
> +- compatible: must be: "capella,cm3605"
> +- aset-gpios: GPIO line controlling the ASET line (drive low
> +  to activate the ALS, should be flagged GPIO_ACTIVE_LOW)
> +- interrupts: the IRQ line (such as a GPIO) that is connected to
> +  the POUT (proximity sensor out) line. The edge detection must
> +  be set to IRQ_TYPE_EDGE_BOTH so as to detect movements toward
> +  and away from the proximity sensor.
> +- io-channels: the ADC channel used for converting the voltage from
> +  AOUT to a digital representation.
> +- io-channel-names: must be "aout"
> +
> +Optional properties:
> +- vdd-supply: regulator supplying VDD power to the component.
> +- capella,aset-resistance-ohms: the sensitivity calibration resistance,
> +  in Ohms. Valid values are: 50000, 100000, 300000 and 600000,
> +  as these are the resistance values that we are supplied with
> +  calibration curves for. If not supplied, 100 kOhm will be assumed
> +  but it is strongly recommended to supply this.
> +
> +Example:
> +
> +cm3605 {
> +	compatible = "capella,cm3605";
> +	vdd-supply = <&foo_reg>;
> +	aset-gpios = <&foo_gpio 1 GPIO_ACTIVE_LOW>;
> +	capella,aset-resistance-ohms = <100000>;
> +	interrupts = <1 IRQ_TYPE_EDGE_BOTH>;
> +	io-channels = <&adc 0x01>;
> +	io-channel-names = "aout";
> +};
> 

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox