From 8b770e3c9824c98eafe67950ad6e41e09ec9c98a Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Thu, 4 Jul 2013 21:13:24 +0200 Subject: backlight: Add GPIO-based backlight driver The GPIO backlight driver controls the backlight in on/off mode through a single GPIO. Signed-off-by: Laurent Pinchart Acked-by: Jingoo Han Signed-off-by: Simon Horman --- include/linux/platform_data/gpio_backlight.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 include/linux/platform_data/gpio_backlight.h (limited to 'include/linux/platform_data') diff --git a/include/linux/platform_data/gpio_backlight.h b/include/linux/platform_data/gpio_backlight.h new file mode 100644 index 000000000000..5ae0d9c80d4d --- /dev/null +++ b/include/linux/platform_data/gpio_backlight.h @@ -0,0 +1,21 @@ +/* + * gpio_backlight.h - Simple GPIO-controlled backlight + * + * 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. + */ +#ifndef __GPIO_BACKLIGHT_H__ +#define __GPIO_BACKLIGHT_H__ + +struct device; + +struct gpio_backlight_platform_data { + struct device *fbdev; + int gpio; + int def_value; + bool active_low; + const char *name; +}; + +#endif -- cgit v1.2.3-71-gd317 From 82e5c40d88f928afffe7bd4027719d3184433908 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Thu, 4 Jul 2013 21:13:25 +0200 Subject: backlight: Add Sanyo LV5207LP backlight driver The LV5207LP is a multi-purpose 7 LEDs driver for the mobile market. Only the main LED is supported by this driver. Signed-off-by: Laurent Pinchart Acked-by: Jingoo Han Signed-off-by: Simon Horman --- drivers/video/backlight/Kconfig | 6 ++ drivers/video/backlight/Makefile | 1 + drivers/video/backlight/lv5207lp.c | 171 +++++++++++++++++++++++++++++++++ include/linux/platform_data/lv5207lp.h | 19 ++++ 4 files changed, 197 insertions(+) create mode 100644 drivers/video/backlight/lv5207lp.c create mode 100644 include/linux/platform_data/lv5207lp.h (limited to 'include/linux/platform_data') diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 5ab64237c972..b304e075cf3a 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -432,6 +432,12 @@ config BACKLIGHT_GPIO If you have a LCD backlight adjustable by GPIO, say Y to enable this driver. +config BACKLIGHT_LV5207LP + tristate "Sanyo LV5207LP Backlight" + depends on I2C + help + If you have a Sanyo LV5207LP say Y to enable the backlight driver. + endif # BACKLIGHT_CLASS_DEVICE endif # BACKLIGHT_LCD_SUPPORT diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index 65698a882c2f..4e5e8116c8c4 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_BACKLIGHT_LM3639) += lm3639_bl.o obj-$(CONFIG_BACKLIGHT_LOCOMO) += locomolcd.o obj-$(CONFIG_BACKLIGHT_LP855X) += lp855x_bl.o obj-$(CONFIG_BACKLIGHT_LP8788) += lp8788_bl.o +obj-$(CONFIG_BACKLIGHT_LV5207LP) += lv5207lp.o obj-$(CONFIG_BACKLIGHT_MAX8925) += max8925_bl.o obj-$(CONFIG_BACKLIGHT_OMAP1) += omap1_bl.o obj-$(CONFIG_BACKLIGHT_OT200) += ot200_bl.o diff --git a/drivers/video/backlight/lv5207lp.c b/drivers/video/backlight/lv5207lp.c new file mode 100644 index 000000000000..498fd73d59b9 --- /dev/null +++ b/drivers/video/backlight/lv5207lp.c @@ -0,0 +1,171 @@ +/* + * Sanyo LV5207LP LED Driver + * + * Copyright (C) 2013 Ideas on board SPRL + * + * Contact: Laurent Pinchart + * + * 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 +#include +#include +#include +#include +#include +#include + +#define LV5207LP_CTRL1 0x00 +#define LV5207LP_CPSW (1 << 7) +#define LV5207LP_SCTEN (1 << 6) +#define LV5207LP_C10 (1 << 5) +#define LV5207LP_CKSW (1 << 4) +#define LV5207LP_RSW (1 << 3) +#define LV5207LP_GSW (1 << 2) +#define LV5207LP_BSW (1 << 1) +#define LV5207LP_CTRL2 0x01 +#define LV5207LP_MSW (1 << 7) +#define LV5207LP_MLED4 (1 << 6) +#define LV5207LP_RED 0x02 +#define LV5207LP_GREEN 0x03 +#define LV5207LP_BLUE 0x04 + +#define LV5207LP_MAX_BRIGHTNESS 32 + +struct lv5207lp { + struct i2c_client *client; + struct backlight_device *backlight; + struct lv5207lp_platform_data *pdata; +}; + +static int lv5207lp_write(struct lv5207lp *lv, u8 reg, u8 data) +{ + return i2c_smbus_write_byte_data(lv->client, reg, data); +} + +static int lv5207lp_backlight_update_status(struct backlight_device *backlight) +{ + struct lv5207lp *lv = bl_get_data(backlight); + int brightness = backlight->props.brightness; + + if (backlight->props.power != FB_BLANK_UNBLANK || + backlight->props.fb_blank != FB_BLANK_UNBLANK || + backlight->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) + brightness = 0; + + if (brightness) { + lv5207lp_write(lv, LV5207LP_CTRL1, + LV5207LP_CPSW | LV5207LP_C10 | LV5207LP_CKSW); + lv5207lp_write(lv, LV5207LP_CTRL2, + LV5207LP_MSW | LV5207LP_MLED4 | + (brightness - 1)); + } else { + lv5207lp_write(lv, LV5207LP_CTRL1, 0); + lv5207lp_write(lv, LV5207LP_CTRL2, 0); + } + + return 0; +} + +static int lv5207lp_backlight_get_brightness(struct backlight_device *backlight) +{ + return backlight->props.brightness; +} + +static int lv5207lp_backlight_check_fb(struct backlight_device *backlight, + struct fb_info *info) +{ + struct lv5207lp *lv = bl_get_data(backlight); + + return lv->pdata->fbdev == NULL || lv->pdata->fbdev == info->dev; +} + +static const struct backlight_ops lv5207lp_backlight_ops = { + .options = BL_CORE_SUSPENDRESUME, + .update_status = lv5207lp_backlight_update_status, + .get_brightness = lv5207lp_backlight_get_brightness, + .check_fb = lv5207lp_backlight_check_fb, +}; + +static int lv5207lp_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lv5207lp_platform_data *pdata = client->dev.platform_data; + struct backlight_device *backlight; + struct backlight_properties props; + struct lv5207lp *lv; + + if (pdata == NULL) { + dev_err(&client->dev, "No platform data supplied\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_warn(&client->dev, + "I2C adapter doesn't support I2C_FUNC_SMBUS_BYTE\n"); + return -EIO; + } + + lv = devm_kzalloc(&client->dev, sizeof(*lv), GFP_KERNEL); + if (!lv) + return -ENOMEM; + + lv->client = client; + lv->pdata = pdata; + + memset(&props, 0, sizeof(props)); + props.type = BACKLIGHT_RAW; + props.max_brightness = min_t(unsigned int, pdata->max_value, + LV5207LP_MAX_BRIGHTNESS); + props.brightness = clamp_t(unsigned int, pdata->def_value, 0, + props.max_brightness); + + backlight = backlight_device_register(dev_name(&client->dev), + &lv->client->dev, lv, + &lv5207lp_backlight_ops, &props); + if (IS_ERR(backlight)) { + dev_err(&client->dev, "failed to register backlight\n"); + return PTR_ERR(backlight); + } + + backlight_update_status(backlight); + i2c_set_clientdata(client, backlight); + + return 0; +} + +static int lv5207lp_remove(struct i2c_client *client) +{ + struct backlight_device *backlight = i2c_get_clientdata(client); + + backlight->props.brightness = 0; + backlight_update_status(backlight); + backlight_device_unregister(backlight); + + return 0; +} + +static const struct i2c_device_id lv5207lp_ids[] = { + { "lv5207lp", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lv5207lp_ids); + +static struct i2c_driver lv5207lp_driver = { + .driver = { + .name = "lv5207lp", + }, + .probe = lv5207lp_probe, + .remove = lv5207lp_remove, + .id_table = lv5207lp_ids, +}; + +module_i2c_driver(lv5207lp_driver); + +MODULE_DESCRIPTION("Sanyo LV5207LP Backlight Driver"); +MODULE_AUTHOR("Laurent Pinchart "); +MODULE_LICENSE("GPL"); diff --git a/include/linux/platform_data/lv5207lp.h b/include/linux/platform_data/lv5207lp.h new file mode 100644 index 000000000000..7dc4d9a219a6 --- /dev/null +++ b/include/linux/platform_data/lv5207lp.h @@ -0,0 +1,19 @@ +/* + * lv5207lp.h - Sanyo LV5207LP LEDs Driver + * + * 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. + */ +#ifndef __LV5207LP_H__ +#define __LV5207LP_H__ + +struct device; + +struct lv5207lp_platform_data { + struct device *fbdev; + unsigned int max_value; + unsigned int def_value; +}; + +#endif -- cgit v1.2.3-71-gd317 From 67b43e590415866649e5ba8a6421bb84ecb74f72 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Thu, 4 Jul 2013 21:13:26 +0200 Subject: backlight: Add ROHM BD6107 backlight driver The BD6107 is a multi-purpose 10 channels LED driver for the mobile market. Only the main channel is supported by this driver. Signed-off-by: Laurent Pinchart Acked-by: Jingoo Han Signed-off-by: Simon Horman --- drivers/video/backlight/Kconfig | 6 + drivers/video/backlight/Makefile | 1 + drivers/video/backlight/bd6107.c | 213 +++++++++++++++++++++++++++++++++++ include/linux/platform_data/bd6107.h | 19 ++++ 4 files changed, 239 insertions(+) create mode 100644 drivers/video/backlight/bd6107.c create mode 100644 include/linux/platform_data/bd6107.h (limited to 'include/linux/platform_data') diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index b304e075cf3a..d4a7a351d67c 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -438,6 +438,12 @@ config BACKLIGHT_LV5207LP help If you have a Sanyo LV5207LP say Y to enable the backlight driver. +config BACKLIGHT_BD6107 + tristate "Rohm BD6107 Backlight" + depends on I2C + help + If you have a Rohm BD6107 say Y to enable the backlight driver. + endif # BACKLIGHT_CLASS_DEVICE endif # BACKLIGHT_LCD_SUPPORT diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index 4e5e8116c8c4..38e1babb1946 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_BACKLIGHT_ADP8870) += adp8870_bl.o obj-$(CONFIG_BACKLIGHT_APPLE) += apple_bl.o obj-$(CONFIG_BACKLIGHT_AS3711) += as3711_bl.o obj-$(CONFIG_BACKLIGHT_ATMEL_PWM) += atmel-pwm-bl.o +obj-$(CONFIG_BACKLIGHT_BD6107) += bd6107.o obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH) += cr_bllcd.o obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o obj-$(CONFIG_BACKLIGHT_DA903X) += da903x_bl.o diff --git a/drivers/video/backlight/bd6107.c b/drivers/video/backlight/bd6107.c new file mode 100644 index 000000000000..15e3294b29fe --- /dev/null +++ b/drivers/video/backlight/bd6107.c @@ -0,0 +1,213 @@ +/* + * ROHM Semiconductor BD6107 LED Driver + * + * Copyright (C) 2013 Ideas on board SPRL + * + * Contact: Laurent Pinchart + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#define BD6107_PSCNT1 0x00 +#define BD6107_PSCNT1_PSCNTREG2 (1 << 2) +#define BD6107_PSCNT1_PSCNTREG1 (1 << 0) +#define BD6107_REGVSET 0x02 +#define BD6107_REGVSET_REG1VSET_2_85V (1 << 2) +#define BD6107_REGVSET_REG1VSET_2_80V (0 << 2) +#define BD6107_LEDCNT1 0x03 +#define BD6107_LEDCNT1_LEDONOFF2 (1 << 1) +#define BD6107_LEDCNT1_LEDONOFF1 (1 << 0) +#define BD6107_PORTSEL 0x04 +#define BD6107_PORTSEL_LEDM(n) (1 << (n)) +#define BD6107_RGB1CNT1 0x05 +#define BD6107_RGB1CNT2 0x06 +#define BD6107_RGB1CNT3 0x07 +#define BD6107_RGB1CNT4 0x08 +#define BD6107_RGB1CNT5 0x09 +#define BD6107_RGB1FLM 0x0a +#define BD6107_RGB2CNT1 0x0b +#define BD6107_RGB2CNT2 0x0c +#define BD6107_RGB2CNT3 0x0d +#define BD6107_RGB2CNT4 0x0e +#define BD6107_RGB2CNT5 0x0f +#define BD6107_RGB2FLM 0x10 +#define BD6107_PSCONT3 0x11 +#define BD6107_SMMONCNT 0x12 +#define BD6107_DCDCCNT 0x13 +#define BD6107_IOSEL 0x14 +#define BD6107_OUT1 0x15 +#define BD6107_OUT2 0x16 +#define BD6107_MASK1 0x17 +#define BD6107_MASK2 0x18 +#define BD6107_FACTOR1 0x19 +#define BD6107_FACTOR2 0x1a +#define BD6107_CLRFACT1 0x1b +#define BD6107_CLRFACT2 0x1c +#define BD6107_STATE1 0x1d +#define BD6107_LSIVER 0x1e +#define BD6107_GRPSEL 0x1f +#define BD6107_LEDCNT2 0x20 +#define BD6107_LEDCNT3 0x21 +#define BD6107_MCURRENT 0x22 +#define BD6107_MAINCNT1 0x23 +#define BD6107_MAINCNT2 0x24 +#define BD6107_SLOPECNT 0x25 +#define BD6107_MSLOPE 0x26 +#define BD6107_RGBSLOPE 0x27 +#define BD6107_TEST 0x29 +#define BD6107_SFTRST 0x2a +#define BD6107_SFTRSTGD 0x2b + +struct bd6107 { + struct i2c_client *client; + struct backlight_device *backlight; + struct bd6107_platform_data *pdata; +}; + +static int bd6107_write(struct bd6107 *bd, u8 reg, u8 data) +{ + return i2c_smbus_write_byte_data(bd->client, reg, data); +} + +static int bd6107_backlight_update_status(struct backlight_device *backlight) +{ + struct bd6107 *bd = bl_get_data(backlight); + int brightness = backlight->props.brightness; + + if (backlight->props.power != FB_BLANK_UNBLANK || + backlight->props.fb_blank != FB_BLANK_UNBLANK || + backlight->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) + brightness = 0; + + if (brightness) { + bd6107_write(bd, BD6107_PORTSEL, BD6107_PORTSEL_LEDM(2) | + BD6107_PORTSEL_LEDM(1) | BD6107_PORTSEL_LEDM(0)); + bd6107_write(bd, BD6107_MAINCNT1, brightness); + bd6107_write(bd, BD6107_LEDCNT1, BD6107_LEDCNT1_LEDONOFF1); + } else { + gpio_set_value(bd->pdata->reset, 0); + msleep(24); + gpio_set_value(bd->pdata->reset, 1); + } + + return 0; +} + +static int bd6107_backlight_get_brightness(struct backlight_device *backlight) +{ + return backlight->props.brightness; +} + +static int bd6107_backlight_check_fb(struct backlight_device *backlight, + struct fb_info *info) +{ + struct bd6107 *bd = bl_get_data(backlight); + + return bd->pdata->fbdev == NULL || bd->pdata->fbdev == info->dev; +} + +static const struct backlight_ops bd6107_backlight_ops = { + .options = BL_CORE_SUSPENDRESUME, + .update_status = bd6107_backlight_update_status, + .get_brightness = bd6107_backlight_get_brightness, + .check_fb = bd6107_backlight_check_fb, +}; + +static int bd6107_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bd6107_platform_data *pdata = client->dev.platform_data; + struct backlight_device *backlight; + struct backlight_properties props; + struct bd6107 *bd; + int ret; + + if (pdata == NULL || !pdata->reset) { + dev_err(&client->dev, "No reset GPIO in platform data\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_warn(&client->dev, + "I2C adapter doesn't support I2C_FUNC_SMBUS_BYTE\n"); + return -EIO; + } + + bd = devm_kzalloc(&client->dev, sizeof(*bd), GFP_KERNEL); + if (!bd) + return -ENOMEM; + + bd->client = client; + bd->pdata = pdata; + + ret = devm_gpio_request_one(&client->dev, pdata->reset, + GPIOF_DIR_OUT | GPIOF_INIT_LOW, "reset"); + if (ret < 0) { + dev_err(&client->dev, "unable to request reset GPIO\n"); + return ret; + } + + memset(&props, 0, sizeof(props)); + props.type = BACKLIGHT_RAW; + props.max_brightness = 128; + props.brightness = clamp_t(unsigned int, pdata->def_value, 0, + props.max_brightness); + + backlight = backlight_device_register(dev_name(&client->dev), + &bd->client->dev, bd, + &bd6107_backlight_ops, &props); + if (IS_ERR(backlight)) { + dev_err(&client->dev, "failed to register backlight\n"); + return PTR_ERR(backlight); + } + + backlight_update_status(backlight); + i2c_set_clientdata(client, backlight); + + return 0; +} + +static int bd6107_remove(struct i2c_client *client) +{ + struct backlight_device *backlight = i2c_get_clientdata(client); + + backlight->props.brightness = 0; + backlight_update_status(backlight); + backlight_device_unregister(backlight); + + return 0; +} + +static const struct i2c_device_id bd6107_ids[] = { + { "bd6107", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bd6107_ids); + +static struct i2c_driver bd6107_driver = { + .driver = { + .name = "bd6107", + }, + .probe = bd6107_probe, + .remove = bd6107_remove, + .id_table = bd6107_ids, +}; + +module_i2c_driver(bd6107_driver); + +MODULE_DESCRIPTION("Rohm BD6107 Backlight Driver"); +MODULE_AUTHOR("Laurent Pinchart "); +MODULE_LICENSE("GPL"); diff --git a/include/linux/platform_data/bd6107.h b/include/linux/platform_data/bd6107.h new file mode 100644 index 000000000000..671d6502d241 --- /dev/null +++ b/include/linux/platform_data/bd6107.h @@ -0,0 +1,19 @@ +/* + * bd6107.h - Rohm BD6107 LEDs Driver + * + * 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. + */ +#ifndef __BD6107_H__ +#define __BD6107_H__ + +struct device; + +struct bd6107_platform_data { + struct device *fbdev; + int reset; /* Reset GPIO */ + unsigned int def_value; +}; + +#endif -- cgit v1.2.3-71-gd317 From 640efa08cb635ae43d5ceae302b20c2c3f2035e5 Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Wed, 3 Jul 2013 13:14:32 +0900 Subject: gpio: em: Add pinctrl support Register the GPIO pin range, and request and free GPIO pins using the pinctrl API. The pctl_name platform data member should be used by platform devices to point out which pinctrl device to use. Follows same style as "dc3465a gpio-rcar: Add pinctrl support", by Laurent Pinchart, thanks to him. Signed-off-by: Magnus Damm Signed-off-by: Linus Walleij --- drivers/gpio/gpio-em.c | 25 +++++++++++++++++++++++++ include/linux/platform_data/gpio-em.h | 1 + 2 files changed, 26 insertions(+) (limited to 'include/linux/platform_data') diff --git a/drivers/gpio/gpio-em.c b/drivers/gpio/gpio-em.c index 5cba855638bf..eca119b58c21 100644 --- a/drivers/gpio/gpio-em.c +++ b/drivers/gpio/gpio-em.c @@ -30,6 +30,7 @@ #include #include #include +#include #include struct em_gio_priv { @@ -216,6 +217,21 @@ static int em_gio_to_irq(struct gpio_chip *chip, unsigned offset) return irq_create_mapping(gpio_to_priv(chip)->irq_domain, offset); } +static int em_gio_request(struct gpio_chip *chip, unsigned offset) +{ + return pinctrl_request_gpio(chip->base + offset); +} + +static void em_gio_free(struct gpio_chip *chip, unsigned offset) +{ + pinctrl_free_gpio(chip->base + offset); + + /* Set the GPIO as an input to ensure that the next GPIO request won't + * drive the GPIO pin as an output. + */ + em_gio_direction_input(chip, offset); +} + static int em_gio_irq_domain_map(struct irq_domain *h, unsigned int virq, irq_hw_number_t hw) { @@ -308,6 +324,8 @@ static int em_gio_probe(struct platform_device *pdev) gpio_chip->direction_output = em_gio_direction_output; gpio_chip->set = em_gio_set; gpio_chip->to_irq = em_gio_to_irq; + gpio_chip->request = em_gio_request; + gpio_chip->free = em_gio_free; gpio_chip->label = name; gpio_chip->owner = THIS_MODULE; gpio_chip->base = pdata->gpio_base; @@ -351,6 +369,13 @@ static int em_gio_probe(struct platform_device *pdev) dev_err(&pdev->dev, "failed to add GPIO controller\n"); goto err1; } + + if (pdata->pctl_name) { + ret = gpiochip_add_pin_range(gpio_chip, pdata->pctl_name, 0, + gpio_chip->base, gpio_chip->ngpio); + if (ret < 0) + dev_warn(&pdev->dev, "failed to add pin range\n"); + } return 0; err1: diff --git a/include/linux/platform_data/gpio-em.h b/include/linux/platform_data/gpio-em.h index 573edfb046c4..7c5a519d2dcd 100644 --- a/include/linux/platform_data/gpio-em.h +++ b/include/linux/platform_data/gpio-em.h @@ -5,6 +5,7 @@ struct gpio_em_config { unsigned int gpio_base; unsigned int irq_base; unsigned int number_of_pins; + const char *pctl_name; }; #endif /* __GPIO_EM_H__ */ -- cgit v1.2.3-71-gd317 From ae3e4c277669e16093f0e71ca927cf33e7dde053 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Tue, 16 Jul 2013 12:32:08 +0200 Subject: leds: Remove leds-renesas-tpu driver The driver is superseded by the generic pwm-renesas-tpu driver used with leds-pwm. Signed-off-by: Laurent Pinchart Acked-by: Bryan Wu Signed-off-by: Simon Horman --- drivers/leds/Kconfig | 12 - drivers/leds/Makefile | 1 - drivers/leds/leds-renesas-tpu.c | 337 ------------------------- include/linux/platform_data/leds-renesas-tpu.h | 14 - 4 files changed, 364 deletions(-) delete mode 100644 drivers/leds/leds-renesas-tpu.c delete mode 100644 include/linux/platform_data/leds-renesas-tpu.h (limited to 'include/linux/platform_data') diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index e43402dd1dea..074bcb3892b5 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -429,18 +429,6 @@ config LEDS_ASIC3 cannot be used. This driver supports hardware blinking with an on+off period from 62ms to 125s. Say Y to enable LEDs on the HP iPAQ hx4700. -config LEDS_RENESAS_TPU - bool "LED support for Renesas TPU" - depends on LEDS_CLASS=y && HAVE_CLK && GPIOLIB - help - This option enables build of the LED TPU platform driver, - suitable to drive any TPU channel on newer Renesas SoCs. - The driver controls the GPIO pin connected to the LED via - the GPIO framework and expects the LED to be connected to - a pin that can be driven in both GPIO mode and using TPU - pin function. The latter to support brightness control. - Brightness control is supported but hardware blinking is not. - config LEDS_TCA6507 tristate "LED Support for TCA6507 I2C chip" depends on LEDS_CLASS && I2C diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index ac2897732b02..ae4b6135f665 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -49,7 +49,6 @@ obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o obj-$(CONFIG_LEDS_NS2) += leds-ns2.o obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o -obj-$(CONFIG_LEDS_RENESAS_TPU) += leds-renesas-tpu.o obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o diff --git a/drivers/leds/leds-renesas-tpu.c b/drivers/leds/leds-renesas-tpu.c deleted file mode 100644 index adebf4931e1e..000000000000 --- a/drivers/leds/leds-renesas-tpu.c +++ /dev/null @@ -1,337 +0,0 @@ -/* - * LED control using Renesas TPU - * - * Copyright (C) 2011 Magnus Damm - * - * 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 - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -enum r_tpu_pin { R_TPU_PIN_UNUSED, R_TPU_PIN_GPIO, R_TPU_PIN_GPIO_FN }; -enum r_tpu_timer { R_TPU_TIMER_UNUSED, R_TPU_TIMER_ON }; - -struct r_tpu_priv { - struct led_classdev ldev; - void __iomem *mapbase; - struct clk *clk; - struct platform_device *pdev; - enum r_tpu_pin pin_state; - enum r_tpu_timer timer_state; - unsigned long min_rate; - unsigned int refresh_rate; - struct work_struct work; - enum led_brightness new_brightness; -}; - -static DEFINE_SPINLOCK(r_tpu_lock); - -#define TSTR -1 /* Timer start register (shared register) */ -#define TCR 0 /* Timer control register (+0x00) */ -#define TMDR 1 /* Timer mode register (+0x04) */ -#define TIOR 2 /* Timer I/O control register (+0x08) */ -#define TIER 3 /* Timer interrupt enable register (+0x0c) */ -#define TSR 4 /* Timer status register (+0x10) */ -#define TCNT 5 /* Timer counter (+0x14) */ -#define TGRA 6 /* Timer general register A (+0x18) */ -#define TGRB 7 /* Timer general register B (+0x1c) */ -#define TGRC 8 /* Timer general register C (+0x20) */ -#define TGRD 9 /* Timer general register D (+0x24) */ - -static inline u16 r_tpu_read(struct r_tpu_priv *p, int reg_nr) -{ - struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; - void __iomem *base = p->mapbase; - unsigned long offs = reg_nr << 2; - - if (reg_nr == TSTR) - return ioread16(base - cfg->channel_offset); - - return ioread16(base + offs); -} - -static inline void r_tpu_write(struct r_tpu_priv *p, int reg_nr, u16 value) -{ - struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; - void __iomem *base = p->mapbase; - unsigned long offs = reg_nr << 2; - - if (reg_nr == TSTR) { - iowrite16(value, base - cfg->channel_offset); - return; - } - - iowrite16(value, base + offs); -} - -static void r_tpu_start_stop_ch(struct r_tpu_priv *p, int start) -{ - struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; - unsigned long flags; - u16 value; - - /* start stop register shared by multiple timer channels */ - spin_lock_irqsave(&r_tpu_lock, flags); - value = r_tpu_read(p, TSTR); - - if (start) - value |= 1 << cfg->timer_bit; - else - value &= ~(1 << cfg->timer_bit); - - r_tpu_write(p, TSTR, value); - spin_unlock_irqrestore(&r_tpu_lock, flags); -} - -static int r_tpu_enable(struct r_tpu_priv *p, enum led_brightness brightness) -{ - struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; - int prescaler[] = { 1, 4, 16, 64 }; - int k, ret; - unsigned long rate, tmp; - - if (p->timer_state == R_TPU_TIMER_ON) - return 0; - - /* wake up device and enable clock */ - pm_runtime_get_sync(&p->pdev->dev); - ret = clk_enable(p->clk); - if (ret) { - dev_err(&p->pdev->dev, "cannot enable clock\n"); - return ret; - } - - /* make sure channel is disabled */ - r_tpu_start_stop_ch(p, 0); - - /* get clock rate after enabling it */ - rate = clk_get_rate(p->clk); - - /* pick the lowest acceptable rate */ - for (k = ARRAY_SIZE(prescaler) - 1; k >= 0; k--) - if ((rate / prescaler[k]) >= p->min_rate) - break; - - if (k < 0) { - dev_err(&p->pdev->dev, "clock rate mismatch\n"); - goto err0; - } - dev_dbg(&p->pdev->dev, "rate = %lu, prescaler %u\n", - rate, prescaler[k]); - - /* clear TCNT on TGRB match, count on rising edge, set prescaler */ - r_tpu_write(p, TCR, 0x0040 | k); - - /* output 0 until TGRA, output 1 until TGRB */ - r_tpu_write(p, TIOR, 0x0002); - - rate /= prescaler[k] * p->refresh_rate; - r_tpu_write(p, TGRB, rate); - dev_dbg(&p->pdev->dev, "TRGB = 0x%04lx\n", rate); - - tmp = (cfg->max_brightness - brightness) * rate; - r_tpu_write(p, TGRA, tmp / cfg->max_brightness); - dev_dbg(&p->pdev->dev, "TRGA = 0x%04lx\n", tmp / cfg->max_brightness); - - /* PWM mode */ - r_tpu_write(p, TMDR, 0x0002); - - /* enable channel */ - r_tpu_start_stop_ch(p, 1); - - p->timer_state = R_TPU_TIMER_ON; - return 0; - err0: - clk_disable(p->clk); - pm_runtime_put_sync(&p->pdev->dev); - return -ENOTSUPP; -} - -static void r_tpu_disable(struct r_tpu_priv *p) -{ - if (p->timer_state == R_TPU_TIMER_UNUSED) - return; - - /* disable channel */ - r_tpu_start_stop_ch(p, 0); - - /* stop clock and mark device as idle */ - clk_disable(p->clk); - pm_runtime_put_sync(&p->pdev->dev); - - p->timer_state = R_TPU_TIMER_UNUSED; -} - -static void r_tpu_set_pin(struct r_tpu_priv *p, enum r_tpu_pin new_state, - enum led_brightness brightness) -{ - struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; - - if (p->pin_state == new_state) { - if (p->pin_state == R_TPU_PIN_GPIO) - gpio_set_value(cfg->pin_gpio, brightness); - return; - } - - if (p->pin_state == R_TPU_PIN_GPIO) - gpio_free(cfg->pin_gpio); - - if (p->pin_state == R_TPU_PIN_GPIO_FN) - gpio_free(cfg->pin_gpio_fn); - - if (new_state == R_TPU_PIN_GPIO) - gpio_request_one(cfg->pin_gpio, !!brightness ? - GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW, - cfg->name); - - if (new_state == R_TPU_PIN_GPIO_FN) - gpio_request(cfg->pin_gpio_fn, cfg->name); - - p->pin_state = new_state; -} - -static void r_tpu_work(struct work_struct *work) -{ - struct r_tpu_priv *p = container_of(work, struct r_tpu_priv, work); - enum led_brightness brightness = p->new_brightness; - - r_tpu_disable(p); - - /* off and maximum are handled as GPIO pins, in between PWM */ - if ((brightness == 0) || (brightness == p->ldev.max_brightness)) - r_tpu_set_pin(p, R_TPU_PIN_GPIO, brightness); - else { - r_tpu_set_pin(p, R_TPU_PIN_GPIO_FN, 0); - r_tpu_enable(p, brightness); - } -} - -static void r_tpu_set_brightness(struct led_classdev *ldev, - enum led_brightness brightness) -{ - struct r_tpu_priv *p = container_of(ldev, struct r_tpu_priv, ldev); - p->new_brightness = brightness; - schedule_work(&p->work); -} - -static int r_tpu_probe(struct platform_device *pdev) -{ - struct led_renesas_tpu_config *cfg = pdev->dev.platform_data; - struct r_tpu_priv *p; - struct resource *res; - int ret; - - if (!cfg) { - dev_err(&pdev->dev, "missing platform data\n"); - return -ENODEV; - } - - p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); - if (p == NULL) { - dev_err(&pdev->dev, "failed to allocate driver data\n"); - return -ENOMEM; - } - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(&pdev->dev, "failed to get I/O memory\n"); - return -ENXIO; - } - - /* map memory, let mapbase point to our channel */ - p->mapbase = devm_ioremap_nocache(&pdev->dev, res->start, - resource_size(res)); - if (p->mapbase == NULL) { - dev_err(&pdev->dev, "failed to remap I/O memory\n"); - return -ENXIO; - } - - /* get hold of clock */ - p->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(p->clk)) { - dev_err(&pdev->dev, "cannot get clock\n"); - return PTR_ERR(p->clk); - } - - p->pdev = pdev; - p->pin_state = R_TPU_PIN_UNUSED; - p->timer_state = R_TPU_TIMER_UNUSED; - p->refresh_rate = cfg->refresh_rate ? cfg->refresh_rate : 100; - r_tpu_set_pin(p, R_TPU_PIN_GPIO, LED_OFF); - platform_set_drvdata(pdev, p); - - INIT_WORK(&p->work, r_tpu_work); - - p->ldev.name = cfg->name; - p->ldev.brightness = LED_OFF; - p->ldev.max_brightness = cfg->max_brightness; - p->ldev.brightness_set = r_tpu_set_brightness; - p->ldev.flags |= LED_CORE_SUSPENDRESUME; - ret = led_classdev_register(&pdev->dev, &p->ldev); - if (ret < 0) - goto err0; - - /* max_brightness may be updated by the LED core code */ - p->min_rate = p->ldev.max_brightness * p->refresh_rate; - - pm_runtime_enable(&pdev->dev); - return 0; - - err0: - r_tpu_set_pin(p, R_TPU_PIN_UNUSED, LED_OFF); - return ret; -} - -static int r_tpu_remove(struct platform_device *pdev) -{ - struct r_tpu_priv *p = platform_get_drvdata(pdev); - - r_tpu_set_brightness(&p->ldev, LED_OFF); - led_classdev_unregister(&p->ldev); - cancel_work_sync(&p->work); - r_tpu_disable(p); - r_tpu_set_pin(p, R_TPU_PIN_UNUSED, LED_OFF); - - pm_runtime_disable(&pdev->dev); - - return 0; -} - -static struct platform_driver r_tpu_device_driver = { - .probe = r_tpu_probe, - .remove = r_tpu_remove, - .driver = { - .name = "leds-renesas-tpu", - } -}; - -module_platform_driver(r_tpu_device_driver); - -MODULE_AUTHOR("Magnus Damm"); -MODULE_DESCRIPTION("Renesas TPU LED Driver"); -MODULE_LICENSE("GPL v2"); diff --git a/include/linux/platform_data/leds-renesas-tpu.h b/include/linux/platform_data/leds-renesas-tpu.h deleted file mode 100644 index 055387086fc1..000000000000 --- a/include/linux/platform_data/leds-renesas-tpu.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef __LEDS_RENESAS_TPU_H__ -#define __LEDS_RENESAS_TPU_H__ - -struct led_renesas_tpu_config { - char *name; - unsigned pin_gpio_fn; - unsigned pin_gpio; - unsigned int channel_offset; - unsigned int timer_bit; - unsigned int max_brightness; - unsigned int refresh_rate; -}; - -#endif /* __LEDS_RENESAS_TPU_H__ */ -- cgit v1.2.3-71-gd317 From 10d8b34a421716d55a4ff7c2d427c35e88b8fd60 Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Sat, 29 Jun 2013 10:44:17 +0400 Subject: serial: max310x: Driver rework This patch rework max310x driver. Major changes have been made: - Prepare driver to support ICs with more than one UART. - Prepare driver to support work with I2C-bus. The patch changes almost every function and can not be divided into parts. Signed-off-by: Alexander Shiyan Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/Kconfig | 4 +- drivers/tty/serial/max310x.c | 918 +++++++++++++++++----------------- include/linux/platform_data/max310x.h | 5 +- 3 files changed, 454 insertions(+), 473 deletions(-) (limited to 'include/linux/platform_data') diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 5e3d68917ffe..25772c15276d 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -291,9 +291,9 @@ config SERIAL_MAX3100 config SERIAL_MAX310X bool "MAX310X support" - depends on SPI + depends on SPI_MASTER select SERIAL_CORE - select REGMAP_SPI if SPI + select REGMAP_SPI if SPI_MASTER default n help This selects support for an advanced UART from Maxim (Dallas). diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index 8941e6418942..4620289e9e49 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -1,7 +1,7 @@ /* * Maxim (Dallas) MAX3107/8 serial driver * - * Copyright (C) 2012 Alexander Shiyan + * Copyright (C) 2012-2013 Alexander Shiyan * * Based on max3100.c, by Christian Pellegrin * Based on max3110.c, by Feng Tang @@ -17,7 +17,9 @@ /* TODO: MAX14830 support (Quad) */ #include +#include #include +#include #include #include #include @@ -25,8 +27,10 @@ #include #include #include + #include +#define MAX310X_NAME "max310x" #define MAX310X_MAJOR 204 #define MAX310X_MINOR 209 @@ -37,7 +41,8 @@ #define MAX310X_IRQSTS_REG (0x02) /* IRQ status */ #define MAX310X_LSR_IRQEN_REG (0x03) /* LSR IRQ enable */ #define MAX310X_LSR_IRQSTS_REG (0x04) /* LSR IRQ status */ -#define MAX310X_SPCHR_IRQEN_REG (0x05) /* Special char IRQ enable */ +#define MAX310X_REG_05 (0x05) +#define MAX310X_SPCHR_IRQEN_REG MAX310X_REG_05 /* Special char IRQ en */ #define MAX310X_SPCHR_IRQSTS_REG (0x06) /* Special char IRQ status */ #define MAX310X_STS_IRQEN_REG (0x07) /* Status IRQ enable */ #define MAX310X_STS_IRQSTS_REG (0x08) /* Status IRQ status */ @@ -63,8 +68,15 @@ #define MAX310X_BRGDIVLSB_REG (0x1c) /* Baud rate divisor LSB */ #define MAX310X_BRGDIVMSB_REG (0x1d) /* Baud rate divisor MSB */ #define MAX310X_CLKSRC_REG (0x1e) /* Clock source */ -/* Only present in MAX3107 */ -#define MAX3107_REVID_REG (0x1f) /* Revision identification */ +#define MAX310X_REG_1F (0x1f) + +#define MAX310X_REVID_REG MAX310X_REG_1F /* Revision ID */ + +#define MAX310X_GLOBALIRQ_REG MAX310X_REG_1F /* Global IRQ (RO) */ +#define MAX310X_GLOBALCMD_REG MAX310X_REG_1F /* Global Command (WO) */ + +/* Extended registers */ +#define MAX310X_REVID_EXTREG MAX310X_REG_05 /* Revision ID */ /* IRQ register bits */ #define MAX310X_IRQ_LSR_BIT (1 << 0) /* LSR interrupt */ @@ -246,58 +258,139 @@ #define MAX310X_CLKSRC_EXTCLK_BIT (1 << 4) /* External clock enable */ #define MAX310X_CLKSRC_CLK2RTS_BIT (1 << 7) /* Baud clk to RTS pin */ +/* Global commands */ +#define MAX310X_EXTREG_ENBL (0xce) +#define MAX310X_EXTREG_DSBL (0xcd) + /* Misc definitions */ #define MAX310X_FIFO_SIZE (128) +#define MAX310x_REV_MASK (0xfc) /* MAX3107 specific */ #define MAX3107_REV_ID (0xa0) -#define MAX3107_REV_MASK (0xfe) - -/* IRQ status bits definitions */ -#define MAX310X_IRQ_TX (MAX310X_IRQ_TXFIFO_BIT | \ - MAX310X_IRQ_TXEMPTY_BIT) -#define MAX310X_IRQ_RX (MAX310X_IRQ_RXFIFO_BIT | \ - MAX310X_IRQ_RXEMPTY_BIT) - -/* Supported chip types */ -enum { - MAX310X_TYPE_MAX3107 = 3107, - MAX310X_TYPE_MAX3108 = 3108, + +struct max310x_devtype { + char name[9]; + int nr; + int (*detect)(struct device *); + void (*power)(struct uart_port *, int); }; -struct max310x_port { - struct uart_driver uart; +struct max310x_one { struct uart_port port; + struct work_struct tx_work; +}; - const char *name; - int uartclk; - - unsigned int nr_gpio; +struct max310x_port { + struct uart_driver uart; + struct max310x_devtype *devtype; + struct regmap *regmap; + struct regmap_config regcfg; + struct mutex mutex; + struct max310x_pdata *pdata; + int gpio_used; #ifdef CONFIG_GPIOLIB struct gpio_chip gpio; #endif + struct max310x_one p[0]; +}; - struct regmap *regmap; - struct regmap_config regcfg; +static u8 max310x_port_read(struct uart_port *port, u8 reg) +{ + struct max310x_port *s = dev_get_drvdata(port->dev); + unsigned int val = 0; - struct workqueue_struct *wq; - struct work_struct tx_work; + regmap_read(s->regmap, port->iobase + reg, &val); - struct mutex max310x_mutex; + return val; +} - struct max310x_pdata *pdata; +static void max310x_port_write(struct uart_port *port, u8 reg, u8 val) +{ + struct max310x_port *s = dev_get_drvdata(port->dev); + + regmap_write(s->regmap, port->iobase + reg, val); +} + +static void max310x_port_update(struct uart_port *port, u8 reg, u8 mask, u8 val) +{ + struct max310x_port *s = dev_get_drvdata(port->dev); + + regmap_update_bits(s->regmap, port->iobase + reg, mask, val); +} + +static int max3107_detect(struct device *dev) +{ + struct max310x_port *s = dev_get_drvdata(dev); + unsigned int val = 0; + int ret; + + ret = regmap_read(s->regmap, MAX310X_REVID_REG, &val); + if (ret) + return ret; + + if (((val & MAX310x_REV_MASK) != MAX3107_REV_ID)) { + dev_err(dev, + "%s ID 0x%02x does not match\n", s->devtype->name, val); + return -ENODEV; + } + + return 0; +} + +static int max3108_detect(struct device *dev) +{ + struct max310x_port *s = dev_get_drvdata(dev); + unsigned int val = 0; + int ret; + + /* MAX3108 have not REV ID register, we just check default value + * from clocksource register to make sure everything works. + */ + ret = regmap_read(s->regmap, MAX310X_CLKSRC_REG, &val); + if (ret) + return ret; + + if (val != (MAX310X_CLKSRC_EXTCLK_BIT | MAX310X_CLKSRC_PLLBYP_BIT)) { + dev_err(dev, "%s not present\n", s->devtype->name); + return -ENODEV; + } + + return 0; +} + +static void max310x_power(struct uart_port *port, int on) +{ + max310x_port_update(port, MAX310X_MODE1_REG, + MAX310X_MODE1_FORCESLEEP_BIT, + on ? 0 : MAX310X_MODE1_FORCESLEEP_BIT); + if (on) + msleep(50); +} + +static const struct max310x_devtype max3107_devtype = { + .name = "MAX3107", + .nr = 1, + .detect = max3107_detect, + .power = max310x_power, }; -static bool max3107_8_reg_writeable(struct device *dev, unsigned int reg) +static const struct max310x_devtype max3108_devtype = { + .name = "MAX3108", + .nr = 1, + .detect = max3108_detect, + .power = max310x_power, +}; + +static bool max310x_reg_writeable(struct device *dev, unsigned int reg) { - switch (reg) { + switch (reg & 0x1f) { case MAX310X_IRQSTS_REG: case MAX310X_LSR_IRQSTS_REG: case MAX310X_SPCHR_IRQSTS_REG: case MAX310X_STS_IRQSTS_REG: case MAX310X_TXFIFOLVL_REG: case MAX310X_RXFIFOLVL_REG: - case MAX3107_REVID_REG: /* Only available on MAX3107 */ return false; default: break; @@ -308,7 +401,7 @@ static bool max3107_8_reg_writeable(struct device *dev, unsigned int reg) static bool max310x_reg_volatile(struct device *dev, unsigned int reg) { - switch (reg) { + switch (reg & 0x1f) { case MAX310X_RHR_REG: case MAX310X_IRQSTS_REG: case MAX310X_LSR_IRQSTS_REG: @@ -317,6 +410,9 @@ static bool max310x_reg_volatile(struct device *dev, unsigned int reg) case MAX310X_TXFIFOLVL_REG: case MAX310X_RXFIFOLVL_REG: case MAX310X_GPIODATA_REG: + case MAX310X_BRGDIVLSB_REG: + case MAX310X_REG_05: + case MAX310X_REG_1F: return true; default: break; @@ -327,7 +423,7 @@ static bool max310x_reg_volatile(struct device *dev, unsigned int reg) static bool max310x_reg_precious(struct device *dev, unsigned int reg) { - switch (reg) { + switch (reg & 0x1f) { case MAX310X_RHR_REG: case MAX310X_IRQSTS_REG: case MAX310X_SPCHR_IRQSTS_REG: @@ -340,42 +436,25 @@ static bool max310x_reg_precious(struct device *dev, unsigned int reg) return false; } -static void max310x_set_baud(struct max310x_port *s, int baud) +static void max310x_set_baud(struct uart_port *port, int baud) { - unsigned int mode = 0, div = s->uartclk / baud; + unsigned int mode = 0, div = port->uartclk / baud; if (!(div / 16)) { /* Mode x2 */ mode = MAX310X_BRGCFG_2XMODE_BIT; - div = (s->uartclk * 2) / baud; + div = (port->uartclk * 2) / baud; } if (!(div / 16)) { /* Mode x4 */ mode = MAX310X_BRGCFG_4XMODE_BIT; - div = (s->uartclk * 4) / baud; + div = (port->uartclk * 4) / baud; } - regmap_write(s->regmap, MAX310X_BRGDIVMSB_REG, - ((div / 16) >> 8) & 0xff); - regmap_write(s->regmap, MAX310X_BRGDIVLSB_REG, (div / 16) & 0xff); - regmap_write(s->regmap, MAX310X_BRGCFG_REG, (div % 16) | mode); -} - -static void max310x_wait_pll(struct max310x_port *s) -{ - int tryes = 1000; - - /* Wait for PLL only if crystal is used */ - if (!(s->pdata->driver_flags & MAX310X_EXT_CLK)) { - unsigned int sts = 0; - - while (tryes--) { - regmap_read(s->regmap, MAX310X_STS_IRQSTS_REG, &sts); - if (sts & MAX310X_STS_CLKREADY_BIT) - break; - } - } + max310x_port_write(port, MAX310X_BRGDIVMSB_REG, (div / 16) >> 8); + max310x_port_write(port, MAX310X_BRGDIVLSB_REG, div / 16); + max310x_port_write(port, MAX310X_BRGCFG_REG, (div % 16) | mode); } static int max310x_update_best_err(unsigned long f, long *besterr) @@ -449,49 +528,49 @@ static int max310x_set_ref_clk(struct max310x_port *s) regmap_write(s->regmap, MAX310X_CLKSRC_REG, clksrc); - if (pllcfg) - max310x_wait_pll(s); - - dev_dbg(s->port.dev, "Reference clock set to %lu Hz\n", bestfreq); + /* Wait for crystal */ + if (pllcfg && !(s->pdata->driver_flags & MAX310X_EXT_CLK)) + msleep(10); return (int)bestfreq; } -static void max310x_handle_rx(struct max310x_port *s, unsigned int rxlen) +static void max310x_handle_rx(struct uart_port *port, unsigned int rxlen) { - unsigned int sts = 0, ch = 0, flag; + unsigned int sts, ch, flag; - if (unlikely(rxlen >= MAX310X_FIFO_SIZE)) { - dev_warn(s->port.dev, "Possible RX FIFO overrun %d\n", rxlen); + if (unlikely(rxlen >= port->fifosize)) { + dev_warn_ratelimited(port->dev, + "Port %i: Possible RX FIFO overrun\n", + port->line); + port->icount.buf_overrun++; /* Ensure sanity of RX level */ - rxlen = MAX310X_FIFO_SIZE; + rxlen = port->fifosize; } - dev_dbg(s->port.dev, "RX Len = %u\n", rxlen); - while (rxlen--) { - regmap_read(s->regmap, MAX310X_RHR_REG, &ch); - regmap_read(s->regmap, MAX310X_LSR_IRQSTS_REG, &sts); + ch = max310x_port_read(port, MAX310X_RHR_REG); + sts = max310x_port_read(port, MAX310X_LSR_IRQSTS_REG); sts &= MAX310X_LSR_RXPAR_BIT | MAX310X_LSR_FRERR_BIT | MAX310X_LSR_RXOVR_BIT | MAX310X_LSR_RXBRK_BIT; - s->port.icount.rx++; + port->icount.rx++; flag = TTY_NORMAL; if (unlikely(sts)) { if (sts & MAX310X_LSR_RXBRK_BIT) { - s->port.icount.brk++; - if (uart_handle_break(&s->port)) + port->icount.brk++; + if (uart_handle_break(port)) continue; } else if (sts & MAX310X_LSR_RXPAR_BIT) - s->port.icount.parity++; + port->icount.parity++; else if (sts & MAX310X_LSR_FRERR_BIT) - s->port.icount.frame++; + port->icount.frame++; else if (sts & MAX310X_LSR_RXOVR_BIT) - s->port.icount.overrun++; + port->icount.overrun++; - sts &= s->port.read_status_mask; + sts &= port->read_status_mask; if (sts & MAX310X_LSR_RXBRK_BIT) flag = TTY_BREAK; else if (sts & MAX310X_LSR_RXPAR_BIT) @@ -502,129 +581,129 @@ static void max310x_handle_rx(struct max310x_port *s, unsigned int rxlen) flag = TTY_OVERRUN; } - if (uart_handle_sysrq_char(s->port, ch)) + if (uart_handle_sysrq_char(port, ch)) continue; - if (sts & s->port.ignore_status_mask) + if (sts & port->ignore_status_mask) continue; - uart_insert_char(&s->port, sts, MAX310X_LSR_RXOVR_BIT, - ch, flag); + uart_insert_char(port, sts, MAX310X_LSR_RXOVR_BIT, ch, flag); } - tty_flip_buffer_push(&s->port.state->port); + tty_flip_buffer_push(&port->state->port); } -static void max310x_handle_tx(struct max310x_port *s) +static void max310x_handle_tx(struct uart_port *port) { - struct circ_buf *xmit = &s->port.state->xmit; - unsigned int txlen = 0, to_send; + struct circ_buf *xmit = &port->state->xmit; + unsigned int txlen, to_send; - if (unlikely(s->port.x_char)) { - regmap_write(s->regmap, MAX310X_THR_REG, s->port.x_char); - s->port.icount.tx++; - s->port.x_char = 0; + if (unlikely(port->x_char)) { + max310x_port_write(port, MAX310X_THR_REG, port->x_char); + port->icount.tx++; + port->x_char = 0; return; } - if (uart_circ_empty(xmit) || uart_tx_stopped(&s->port)) + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) return; /* Get length of data pending in circular buffer */ to_send = uart_circ_chars_pending(xmit); if (likely(to_send)) { /* Limit to size of TX FIFO */ - regmap_read(s->regmap, MAX310X_TXFIFOLVL_REG, &txlen); - txlen = MAX310X_FIFO_SIZE - txlen; + txlen = max310x_port_read(port, MAX310X_TXFIFOLVL_REG); + txlen = port->fifosize - txlen; to_send = (to_send > txlen) ? txlen : to_send; - dev_dbg(s->port.dev, "TX Len = %u\n", to_send); - /* Add data to send */ - s->port.icount.tx += to_send; + port->icount.tx += to_send; while (to_send--) { - regmap_write(s->regmap, MAX310X_THR_REG, - xmit->buf[xmit->tail]); + max310x_port_write(port, MAX310X_THR_REG, + xmit->buf[xmit->tail]); xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); }; } if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) - uart_write_wakeup(&s->port); + uart_write_wakeup(port); } -static irqreturn_t max310x_ist(int irq, void *dev_id) +static void max310x_port_irq(struct max310x_port *s, int portno) { - struct max310x_port *s = (struct max310x_port *)dev_id; - unsigned int ists = 0, lsr = 0, rxlen = 0; + struct uart_port *port = &s->p[portno].port; - mutex_lock(&s->max310x_mutex); + do { + unsigned int ists, lsr, rxlen; - for (;;) { /* Read IRQ status & RX FIFO level */ - regmap_read(s->regmap, MAX310X_IRQSTS_REG, &ists); - regmap_read(s->regmap, MAX310X_LSR_IRQSTS_REG, &lsr); - regmap_read(s->regmap, MAX310X_RXFIFOLVL_REG, &rxlen); - if (!ists && !(lsr & MAX310X_LSR_RXTO_BIT) && !rxlen) + ists = max310x_port_read(port, MAX310X_IRQSTS_REG); + rxlen = max310x_port_read(port, MAX310X_RXFIFOLVL_REG); + if (!ists && !rxlen) break; - dev_dbg(s->port.dev, "IRQ status: 0x%02x\n", ists); - - if (rxlen) - max310x_handle_rx(s, rxlen); - if (ists & MAX310X_IRQ_TX) - max310x_handle_tx(s); - if (ists & MAX310X_IRQ_CTS_BIT) - uart_handle_cts_change(&s->port, + if (ists & MAX310X_IRQ_CTS_BIT) { + lsr = max310x_port_read(port, MAX310X_LSR_IRQSTS_REG); + uart_handle_cts_change(port, !!(lsr & MAX310X_LSR_CTS_BIT)); - } + } + if (rxlen) + max310x_handle_rx(port, rxlen); + if (ists & MAX310X_IRQ_TXEMPTY_BIT) { + mutex_lock(&s->mutex); + max310x_handle_tx(port); + mutex_unlock(&s->mutex); + } + } while (1); +} - mutex_unlock(&s->max310x_mutex); +static irqreturn_t max310x_ist(int irq, void *dev_id) +{ + struct max310x_port *s = (struct max310x_port *)dev_id; + + if (s->uart.nr > 1) { + do { + unsigned int val = ~0; + + WARN_ON_ONCE(regmap_read(s->regmap, + MAX310X_GLOBALIRQ_REG, &val)); + val = ((1 << s->uart.nr) - 1) & ~val; + if (!val) + break; + max310x_port_irq(s, fls(val) - 1); + } while (1); + } else + max310x_port_irq(s, 0); return IRQ_HANDLED; } static void max310x_wq_proc(struct work_struct *ws) { - struct max310x_port *s = container_of(ws, struct max310x_port, tx_work); + struct max310x_one *one = container_of(ws, struct max310x_one, tx_work); + struct max310x_port *s = dev_get_drvdata(one->port.dev); - mutex_lock(&s->max310x_mutex); - max310x_handle_tx(s); - mutex_unlock(&s->max310x_mutex); + mutex_lock(&s->mutex); + max310x_handle_tx(&one->port); + mutex_unlock(&s->mutex); } static void max310x_start_tx(struct uart_port *port) { - struct max310x_port *s = container_of(port, struct max310x_port, port); + struct max310x_one *one = container_of(port, struct max310x_one, port); - queue_work(s->wq, &s->tx_work); -} - -static void max310x_stop_tx(struct uart_port *port) -{ - /* Do nothing */ -} - -static void max310x_stop_rx(struct uart_port *port) -{ - /* Do nothing */ + if (!work_pending(&one->tx_work)) + schedule_work(&one->tx_work); } static unsigned int max310x_tx_empty(struct uart_port *port) { - unsigned int val = 0; - struct max310x_port *s = container_of(port, struct max310x_port, port); - - mutex_lock(&s->max310x_mutex); - regmap_read(s->regmap, MAX310X_TXFIFOLVL_REG, &val); - mutex_unlock(&s->max310x_mutex); + unsigned int lvl, sts; - return val ? 0 : TIOCSER_TEMT; -} + lvl = max310x_port_read(port, MAX310X_TXFIFOLVL_REG); + sts = max310x_port_read(port, MAX310X_IRQSTS_REG); -static void max310x_enable_ms(struct uart_port *port) -{ - /* Modem status not supported */ + return ((sts & MAX310X_IRQ_TXEMPTY_BIT) && !lvl) ? TIOCSER_TEMT : 0; } static unsigned int max310x_get_mctrl(struct uart_port *port) @@ -644,28 +723,20 @@ static void max310x_set_mctrl(struct uart_port *port, unsigned int mctrl) static void max310x_break_ctl(struct uart_port *port, int break_state) { - struct max310x_port *s = container_of(port, struct max310x_port, port); - - mutex_lock(&s->max310x_mutex); - regmap_update_bits(s->regmap, MAX310X_LCR_REG, - MAX310X_LCR_TXBREAK_BIT, - break_state ? MAX310X_LCR_TXBREAK_BIT : 0); - mutex_unlock(&s->max310x_mutex); + max310x_port_update(port, MAX310X_LCR_REG, + MAX310X_LCR_TXBREAK_BIT, + break_state ? MAX310X_LCR_TXBREAK_BIT : 0); } static void max310x_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old) { - struct max310x_port *s = container_of(port, struct max310x_port, port); unsigned int lcr, flow = 0; int baud; - mutex_lock(&s->max310x_mutex); - /* Mask termios capabilities we don't support */ termios->c_cflag &= ~CMSPAR; - termios->c_iflag &= ~IXANY; /* Word size */ switch (termios->c_cflag & CSIZE) { @@ -696,7 +767,7 @@ static void max310x_set_termios(struct uart_port *port, lcr |= MAX310X_LCR_STOPLEN_BIT; /* 2 stops */ /* Update LCR register */ - regmap_write(s->regmap, MAX310X_LCR_REG, lcr); + max310x_port_write(port, MAX310X_LCR_REG, lcr); /* Set read status mask */ port->read_status_mask = MAX310X_LSR_RXOVR_BIT; @@ -717,8 +788,8 @@ static void max310x_set_termios(struct uart_port *port, MAX310X_LSR_RXBRK_BIT; /* Configure flow control */ - regmap_write(s->regmap, MAX310X_XON1_REG, termios->c_cc[VSTART]); - regmap_write(s->regmap, MAX310X_XOFF1_REG, termios->c_cc[VSTOP]); + max310x_port_write(port, MAX310X_XON1_REG, termios->c_cc[VSTART]); + max310x_port_write(port, MAX310X_XOFF1_REG, termios->c_cc[VSTOP]); if (termios->c_cflag & CRTSCTS) flow |= MAX310X_FLOWCTRL_AUTOCTS_BIT | MAX310X_FLOWCTRL_AUTORTS_BIT; @@ -728,7 +799,7 @@ static void max310x_set_termios(struct uart_port *port, if (termios->c_iflag & IXOFF) flow |= MAX310X_FLOWCTRL_SWFLOW1_BIT | MAX310X_FLOWCTRL_SWFLOWEN_BIT; - regmap_write(s->regmap, MAX310X_FLOWCTRL_REG, flow); + max310x_port_write(port, MAX310X_FLOWCTRL_REG, flow); /* Get baud rate generator configuration */ baud = uart_get_baud_rate(port, termios, old, @@ -736,36 +807,30 @@ static void max310x_set_termios(struct uart_port *port, port->uartclk / 4); /* Setup baudrate generator */ - max310x_set_baud(s, baud); + max310x_set_baud(port, baud); /* Update timeout according to new baud rate */ uart_update_timeout(port, termios->c_cflag, baud); - - mutex_unlock(&s->max310x_mutex); } static int max310x_startup(struct uart_port *port) { unsigned int val, line = port->line; - struct max310x_port *s = container_of(port, struct max310x_port, port); + struct max310x_port *s = dev_get_drvdata(port->dev); - if (s->pdata->suspend) - s->pdata->suspend(0); - - mutex_lock(&s->max310x_mutex); + s->devtype->power(port, 1); /* Configure baud rate, 9600 as default */ - max310x_set_baud(s, 9600); + max310x_set_baud(port, 9600); /* Configure LCR register, 8N1 mode by default */ - val = MAX310X_LCR_WORD_LEN_8; - regmap_write(s->regmap, MAX310X_LCR_REG, val); + max310x_port_write(port, MAX310X_LCR_REG, MAX310X_LCR_WORD_LEN_8); /* Configure MODE1 register */ - regmap_update_bits(s->regmap, MAX310X_MODE1_REG, - MAX310X_MODE1_TRNSCVCTRL_BIT, - (s->pdata->uart_flags[line] & MAX310X_AUTO_DIR_CTRL) - ? MAX310X_MODE1_TRNSCVCTRL_BIT : 0); + max310x_port_update(port, MAX310X_MODE1_REG, + MAX310X_MODE1_TRNSCVCTRL_BIT, + (s->pdata->uart_flags[line] & MAX310X_AUTO_DIR_CTRL) + ? MAX310X_MODE1_TRNSCVCTRL_BIT : 0); /* Configure MODE2 register */ val = MAX310X_MODE2_RXEMPTINV_BIT; @@ -776,63 +841,40 @@ static int max310x_startup(struct uart_port *port) /* Reset FIFOs */ val |= MAX310X_MODE2_FIFORST_BIT; - regmap_write(s->regmap, MAX310X_MODE2_REG, val); - - /* Configure FIFO trigger level register */ - /* RX FIFO trigger for 16 words, TX FIFO trigger for 64 words */ - val = MAX310X_FIFOTRIGLVL_RX(16) | MAX310X_FIFOTRIGLVL_TX(64); - regmap_write(s->regmap, MAX310X_FIFOTRIGLVL_REG, val); + max310x_port_write(port, MAX310X_MODE2_REG, val); + max310x_port_update(port, MAX310X_MODE2_REG, + MAX310X_MODE2_FIFORST_BIT, 0); /* Configure flow control levels */ /* Flow control halt level 96, resume level 48 */ - val = MAX310X_FLOWLVL_RES(48) | MAX310X_FLOWLVL_HALT(96); - regmap_write(s->regmap, MAX310X_FLOWLVL_REG, val); - - /* Clear timeout register */ - regmap_write(s->regmap, MAX310X_RXTO_REG, 0); - - /* Configure LSR interrupt enable register */ - /* Enable RX timeout interrupt */ - val = MAX310X_LSR_RXTO_BIT; - regmap_write(s->regmap, MAX310X_LSR_IRQEN_REG, val); + max310x_port_write(port, MAX310X_FLOWLVL_REG, + MAX310X_FLOWLVL_RES(48) | MAX310X_FLOWLVL_HALT(96)); - /* Clear FIFO reset */ - regmap_update_bits(s->regmap, MAX310X_MODE2_REG, - MAX310X_MODE2_FIFORST_BIT, 0); + /* Clear IRQ status register */ + max310x_port_read(port, MAX310X_IRQSTS_REG); - /* Clear IRQ status register by reading it */ - regmap_read(s->regmap, MAX310X_IRQSTS_REG, &val); - - /* Configure interrupt enable register */ - /* Enable CTS change interrupt */ - val = MAX310X_IRQ_CTS_BIT; - /* Enable RX, TX interrupts */ - val |= MAX310X_IRQ_RX | MAX310X_IRQ_TX; - regmap_write(s->regmap, MAX310X_IRQEN_REG, val); - - mutex_unlock(&s->max310x_mutex); + /* Enable RX, TX, CTS change interrupts */ + val = MAX310X_IRQ_RXEMPTY_BIT | MAX310X_IRQ_TXEMPTY_BIT; + max310x_port_write(port, MAX310X_IRQEN_REG, val | MAX310X_IRQ_CTS_BIT); return 0; } static void max310x_shutdown(struct uart_port *port) { - struct max310x_port *s = container_of(port, struct max310x_port, port); + struct max310x_port *s = dev_get_drvdata(port->dev); /* Disable all interrupts */ - mutex_lock(&s->max310x_mutex); - regmap_write(s->regmap, MAX310X_IRQEN_REG, 0); - mutex_unlock(&s->max310x_mutex); + max310x_port_write(port, MAX310X_IRQEN_REG, 0); - if (s->pdata->suspend) - s->pdata->suspend(1); + s->devtype->power(port, 0); } static const char *max310x_type(struct uart_port *port) { - struct max310x_port *s = container_of(port, struct max310x_port, port); + struct max310x_port *s = dev_get_drvdata(port->dev); - return (port->type == PORT_MAX310X) ? s->name : NULL; + return (port->type == PORT_MAX310X) ? s->devtype->name : NULL; } static int max310x_request_port(struct uart_port *port) @@ -841,134 +883,100 @@ static int max310x_request_port(struct uart_port *port) return 0; } -static void max310x_release_port(struct uart_port *port) -{ - /* Do nothing */ -} - static void max310x_config_port(struct uart_port *port, int flags) { if (flags & UART_CONFIG_TYPE) port->type = PORT_MAX310X; } -static int max310x_verify_port(struct uart_port *port, struct serial_struct *ser) +static int max310x_verify_port(struct uart_port *port, struct serial_struct *s) { - if ((ser->type == PORT_UNKNOWN) || (ser->type == PORT_MAX310X)) - return 0; - if (ser->irq == port->irq) - return 0; + if ((s->type != PORT_UNKNOWN) && (s->type != PORT_MAX310X)) + return -EINVAL; + if (s->irq != port->irq) + return -EINVAL; - return -EINVAL; + return 0; } -static struct uart_ops max310x_ops = { +static void max310x_null_void(struct uart_port *port) +{ + /* Do nothing */ +} + +static const struct uart_ops max310x_ops = { .tx_empty = max310x_tx_empty, .set_mctrl = max310x_set_mctrl, .get_mctrl = max310x_get_mctrl, - .stop_tx = max310x_stop_tx, + .stop_tx = max310x_null_void, .start_tx = max310x_start_tx, - .stop_rx = max310x_stop_rx, - .enable_ms = max310x_enable_ms, + .stop_rx = max310x_null_void, + .enable_ms = max310x_null_void, .break_ctl = max310x_break_ctl, .startup = max310x_startup, .shutdown = max310x_shutdown, .set_termios = max310x_set_termios, .type = max310x_type, .request_port = max310x_request_port, - .release_port = max310x_release_port, + .release_port = max310x_null_void, .config_port = max310x_config_port, .verify_port = max310x_verify_port, }; -#ifdef CONFIG_PM_SLEEP - -static int max310x_suspend(struct device *dev) +static int __maybe_unused max310x_suspend(struct spi_device *spi, + pm_message_t state) { - int ret; - struct max310x_port *s = dev_get_drvdata(dev); - - dev_dbg(dev, "Suspend\n"); + struct max310x_port *s = dev_get_drvdata(&spi->dev); + int i; - ret = uart_suspend_port(&s->uart, &s->port); - - mutex_lock(&s->max310x_mutex); - - /* Enable sleep mode */ - regmap_update_bits(s->regmap, MAX310X_MODE1_REG, - MAX310X_MODE1_FORCESLEEP_BIT, - MAX310X_MODE1_FORCESLEEP_BIT); - - mutex_unlock(&s->max310x_mutex); - - if (s->pdata->suspend) - s->pdata->suspend(1); + for (i = 0; i < s->uart.nr; i++) { + uart_suspend_port(&s->uart, &s->p[i].port); + s->devtype->power(&s->p[i].port, 0); + } - return ret; + return 0; } -static int max310x_resume(struct device *dev) +static int __maybe_unused max310x_resume(struct spi_device *spi) { - struct max310x_port *s = dev_get_drvdata(dev); - - dev_dbg(dev, "Resume\n"); - - if (s->pdata->suspend) - s->pdata->suspend(0); - - mutex_lock(&s->max310x_mutex); + struct max310x_port *s = dev_get_drvdata(&spi->dev); + int i; - /* Disable sleep mode */ - regmap_update_bits(s->regmap, MAX310X_MODE1_REG, - MAX310X_MODE1_FORCESLEEP_BIT, - 0); - - max310x_wait_pll(s); - - mutex_unlock(&s->max310x_mutex); + for (i = 0; i < s->uart.nr; i++) { + s->devtype->power(&s->p[i].port, 1); + uart_resume_port(&s->uart, &s->p[i].port); + } - return uart_resume_port(&s->uart, &s->port); + return 0; } -static SIMPLE_DEV_PM_OPS(max310x_pm_ops, max310x_suspend, max310x_resume); -#define MAX310X_PM_OPS (&max310x_pm_ops) - -#else -#define MAX310X_PM_OPS NULL -#endif - #ifdef CONFIG_GPIOLIB static int max310x_gpio_get(struct gpio_chip *chip, unsigned offset) { - unsigned int val = 0; + unsigned int val; struct max310x_port *s = container_of(chip, struct max310x_port, gpio); + struct uart_port *port = &s->p[offset / 4].port; - mutex_lock(&s->max310x_mutex); - regmap_read(s->regmap, MAX310X_GPIODATA_REG, &val); - mutex_unlock(&s->max310x_mutex); + val = max310x_port_read(port, MAX310X_GPIODATA_REG); - return !!((val >> 4) & (1 << offset)); + return !!((val >> 4) & (1 << (offset % 4))); } static void max310x_gpio_set(struct gpio_chip *chip, unsigned offset, int value) { struct max310x_port *s = container_of(chip, struct max310x_port, gpio); + struct uart_port *port = &s->p[offset / 4].port; - mutex_lock(&s->max310x_mutex); - regmap_update_bits(s->regmap, MAX310X_GPIODATA_REG, 1 << offset, value ? - 1 << offset : 0); - mutex_unlock(&s->max310x_mutex); + max310x_port_update(port, MAX310X_GPIODATA_REG, 1 << (offset % 4), + value ? 1 << (offset % 4) : 0); } static int max310x_gpio_direction_input(struct gpio_chip *chip, unsigned offset) { struct max310x_port *s = container_of(chip, struct max310x_port, gpio); + struct uart_port *port = &s->p[offset / 4].port; - mutex_lock(&s->max310x_mutex); - - regmap_update_bits(s->regmap, MAX310X_GPIOCFG_REG, 1 << offset, 0); - - mutex_unlock(&s->max310x_mutex); + max310x_port_update(port, MAX310X_GPIOCFG_REG, 1 << (offset % 4), 0); return 0; } @@ -977,74 +985,42 @@ static int max310x_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) { struct max310x_port *s = container_of(chip, struct max310x_port, gpio); + struct uart_port *port = &s->p[offset / 4].port; - mutex_lock(&s->max310x_mutex); - - regmap_update_bits(s->regmap, MAX310X_GPIOCFG_REG, 1 << offset, - 1 << offset); - regmap_update_bits(s->regmap, MAX310X_GPIODATA_REG, 1 << offset, value ? - 1 << offset : 0); - - mutex_unlock(&s->max310x_mutex); + max310x_port_update(port, MAX310X_GPIODATA_REG, 1 << (offset % 4), + value ? 1 << (offset % 4) : 0); + max310x_port_update(port, MAX310X_GPIOCFG_REG, 1 << (offset % 4), + 1 << (offset % 4)); return 0; } #endif -/* Generic platform data */ -static struct max310x_pdata generic_plat_data = { - .driver_flags = MAX310X_EXT_CLK, - .uart_flags[0] = MAX310X_ECHO_SUPRESS, - .frequency = 26000000, -}; - -static int max310x_probe(struct spi_device *spi) +static int max310x_probe(struct device *dev, int is_spi, + struct max310x_devtype *devtype, int irq) { struct max310x_port *s; - struct device *dev = &spi->dev; - int chiptype = spi_get_device_id(spi)->driver_data; - struct max310x_pdata *pdata = dev->platform_data; - unsigned int val = 0; - int ret; + struct max310x_pdata *pdata = dev_get_platdata(dev); + int i, ret, uartclk; /* Check for IRQ */ - if (spi->irq <= 0) { + if (irq <= 0) { dev_err(dev, "No IRQ specified\n"); return -ENOTSUPP; } + if (!pdata) { + dev_err(dev, "No platform data supplied\n"); + return -EINVAL; + } + /* Alloc port structure */ - s = devm_kzalloc(dev, sizeof(struct max310x_port), GFP_KERNEL); + s = devm_kzalloc(dev, sizeof(*s) + + sizeof(struct max310x_one) * devtype->nr, GFP_KERNEL); if (!s) { dev_err(dev, "Error allocating port structure\n"); return -ENOMEM; } - dev_set_drvdata(dev, s); - - if (!pdata) { - dev_warn(dev, "No platform data supplied, using defaults\n"); - pdata = &generic_plat_data; - } - s->pdata = pdata; - - /* Individual chip settings */ - switch (chiptype) { - case MAX310X_TYPE_MAX3107: - s->name = "MAX3107"; - s->nr_gpio = 4; - s->uart.nr = 1; - s->regcfg.max_register = 0x1f; - break; - case MAX310X_TYPE_MAX3108: - s->name = "MAX3108"; - s->nr_gpio = 4; - s->uart.nr = 1; - s->regcfg.max_register = 0x1e; - break; - default: - dev_err(dev, "Unsupported chip type %i\n", chiptype); - return -ENOTSUPP; - } /* Check input frequency */ if ((pdata->driver_flags & MAX310X_EXT_CLK) && @@ -1055,13 +1031,11 @@ static int max310x_probe(struct spi_device *spi) ((pdata->frequency < 1000000) || (pdata->frequency > 4000000))) goto err_freq; - mutex_init(&s->max310x_mutex); + s->pdata = pdata; + s->devtype = devtype; + dev_set_drvdata(dev, s); - /* Setup SPI bus */ - spi->mode = SPI_MODE_0; - spi->bits_per_word = 8; - spi->max_speed_hz = 26000000; - spi_setup(spi); + mutex_init(&s->mutex); /* Setup regmap */ s->regcfg.reg_bits = 8; @@ -1069,109 +1043,100 @@ static int max310x_probe(struct spi_device *spi) s->regcfg.read_flag_mask = 0x00; s->regcfg.write_flag_mask = 0x80; s->regcfg.cache_type = REGCACHE_RBTREE; - s->regcfg.writeable_reg = max3107_8_reg_writeable; + s->regcfg.writeable_reg = max310x_reg_writeable; s->regcfg.volatile_reg = max310x_reg_volatile; s->regcfg.precious_reg = max310x_reg_precious; - s->regmap = devm_regmap_init_spi(spi, &s->regcfg); + s->regcfg.max_register = devtype->nr * 0x20 - 1; + + if (IS_ENABLED(CONFIG_SPI_MASTER) && is_spi) { + struct spi_device *spi = to_spi_device(dev); + + s->regmap = devm_regmap_init_spi(spi, &s->regcfg); + } else + return -ENOTSUPP; + if (IS_ERR(s->regmap)) { - ret = PTR_ERR(s->regmap); dev_err(dev, "Failed to initialize register map\n"); - goto err_out; - } - - /* Reset chip & check SPI function */ - ret = regmap_write(s->regmap, MAX310X_MODE2_REG, MAX310X_MODE2_RST_BIT); - if (ret) { - dev_err(dev, "SPI transfer failed\n"); - goto err_out; - } - /* Clear chip reset */ - regmap_write(s->regmap, MAX310X_MODE2_REG, 0); - - switch (chiptype) { - case MAX310X_TYPE_MAX3107: - /* Check REV ID to ensure we are talking to what we expect */ - regmap_read(s->regmap, MAX3107_REVID_REG, &val); - if (((val & MAX3107_REV_MASK) != MAX3107_REV_ID)) { - dev_err(dev, "%s ID 0x%02x does not match\n", - s->name, val); - ret = -ENODEV; - goto err_out; - } - break; - case MAX310X_TYPE_MAX3108: - /* MAX3108 have not REV ID register, we just check default value - * from clocksource register to make sure everything works. - */ - regmap_read(s->regmap, MAX310X_CLKSRC_REG, &val); - if (val != (MAX310X_CLKSRC_EXTCLK_BIT | - MAX310X_CLKSRC_PLLBYP_BIT)) { - dev_err(dev, "%s not present\n", s->name); - ret = -ENODEV; - goto err_out; - } - break; + return PTR_ERR(s->regmap); } /* Board specific configure */ - if (pdata->init) - pdata->init(); - if (pdata->suspend) - pdata->suspend(0); - - /* Calculate referecne clock */ - s->uartclk = max310x_set_ref_clk(s); - - /* Disable all interrupts */ - regmap_write(s->regmap, MAX310X_IRQEN_REG, 0); - - /* Setup MODE1 register */ - val = MAX310X_MODE1_IRQSEL_BIT; /* Enable IRQ pin */ - if (pdata->driver_flags & MAX310X_AUTOSLEEP) - val = MAX310X_MODE1_AUTOSLEEP_BIT; - regmap_write(s->regmap, MAX310X_MODE1_REG, val); - - /* Setup interrupt */ - ret = devm_request_threaded_irq(dev, spi->irq, NULL, max310x_ist, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, - dev_name(dev), s); - if (ret) { - dev_err(dev, "Unable to reguest IRQ %i\n", spi->irq); - goto err_out; + if (s->pdata->init) + s->pdata->init(); + + /* Check device to ensure we are talking to what we expect */ + ret = devtype->detect(dev); + if (ret) + return ret; + + for (i = 0; i < devtype->nr; i++) { + unsigned int offs = i << 5; + + /* Reset port */ + regmap_write(s->regmap, MAX310X_MODE2_REG + offs, + MAX310X_MODE2_RST_BIT); + /* Clear port reset */ + regmap_write(s->regmap, MAX310X_MODE2_REG + offs, 0); + + /* Wait for port startup */ + do { + regmap_read(s->regmap, + MAX310X_BRGDIVLSB_REG + offs, &ret); + } while (ret != 0x01); + + regmap_update_bits(s->regmap, MAX310X_MODE1_REG + offs, + MAX310X_MODE1_AUTOSLEEP_BIT, + MAX310X_MODE1_AUTOSLEEP_BIT); } + uartclk = max310x_set_ref_clk(s); + dev_dbg(dev, "Reference clock set to %i Hz\n", uartclk); + /* Register UART driver */ s->uart.owner = THIS_MODULE; - s->uart.driver_name = dev_name(dev); s->uart.dev_name = "ttyMAX"; s->uart.major = MAX310X_MAJOR; s->uart.minor = MAX310X_MINOR; + s->uart.nr = devtype->nr; ret = uart_register_driver(&s->uart); if (ret) { dev_err(dev, "Registering UART driver failed\n"); - goto err_out; + return ret; } - /* Initialize workqueue for start TX */ - s->wq = create_freezable_workqueue(dev_name(dev)); - INIT_WORK(&s->tx_work, max310x_wq_proc); - - /* Initialize UART port data */ - s->port.line = 0; - s->port.dev = dev; - s->port.irq = spi->irq; - s->port.type = PORT_MAX310X; - s->port.fifosize = MAX310X_FIFO_SIZE; - s->port.flags = UPF_SKIP_TEST | UPF_FIXED_TYPE; - s->port.iotype = UPIO_PORT; - s->port.membase = (void __iomem *)0xffffffff; /* Bogus value */ - s->port.uartclk = s->uartclk; - s->port.ops = &max310x_ops; - uart_add_one_port(&s->uart, &s->port); + for (i = 0; i < devtype->nr; i++) { + /* Initialize port data */ + s->p[i].port.line = i; + s->p[i].port.dev = dev; + s->p[i].port.irq = irq; + s->p[i].port.type = PORT_MAX310X; + s->p[i].port.fifosize = MAX310X_FIFO_SIZE; + s->p[i].port.flags = UPF_SKIP_TEST | UPF_FIXED_TYPE | + UPF_LOW_LATENCY; + s->p[i].port.iotype = UPIO_PORT; + s->p[i].port.iobase = i * 0x20; + s->p[i].port.membase = (void __iomem *)~0; + s->p[i].port.uartclk = uartclk; + s->p[i].port.ops = &max310x_ops; + /* Disable all interrupts */ + max310x_port_write(&s->p[i].port, MAX310X_IRQEN_REG, 0); + /* Clear IRQ status register */ + max310x_port_read(&s->p[i].port, MAX310X_IRQSTS_REG); + /* Enable IRQ pin */ + max310x_port_update(&s->p[i].port, MAX310X_MODE1_REG, + MAX310X_MODE1_IRQSEL_BIT, + MAX310X_MODE1_IRQSEL_BIT); + /* Initialize queue for start TX */ + INIT_WORK(&s->p[i].tx_work, max310x_wq_proc); + /* Register port */ + uart_add_one_port(&s->uart, &s->p[i].port); + /* Go to suspend mode */ + devtype->power(&s->p[i].port, 0); + } #ifdef CONFIG_GPIOLIB /* Setup GPIO cotroller */ - if (pdata->gpio_base) { + if (s->pdata->gpio_base) { s->gpio.owner = THIS_MODULE; s->gpio.dev = dev; s->gpio.label = dev_name(dev); @@ -1179,86 +1144,105 @@ static int max310x_probe(struct spi_device *spi) s->gpio.get = max310x_gpio_get; s->gpio.direction_output= max310x_gpio_direction_output; s->gpio.set = max310x_gpio_set; - s->gpio.base = pdata->gpio_base; - s->gpio.ngpio = s->nr_gpio; + s->gpio.base = s->pdata->gpio_base; + s->gpio.ngpio = devtype->nr * 4; s->gpio.can_sleep = 1; - if (gpiochip_add(&s->gpio)) { - /* Indicate that we should not call gpiochip_remove */ - s->gpio.base = 0; - } + if (!gpiochip_add(&s->gpio)) + s->gpio_used = 1; } else dev_info(dev, "GPIO support not enabled\n"); #endif - /* Go to suspend mode */ - if (pdata->suspend) - pdata->suspend(1); + /* Setup interrupt */ + ret = devm_request_threaded_irq(dev, irq, NULL, max310x_ist, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + dev_name(dev), s); + if (ret) { + dev_err(dev, "Unable to reguest IRQ %i\n", irq); +#ifdef CONFIG_GPIOLIB + if (s->gpio_used) + WARN_ON(gpiochip_remove(&s->gpio)); +#endif + } - return 0; + return ret; err_freq: dev_err(dev, "Frequency parameter incorrect\n"); - ret = -EINVAL; - -err_out: - dev_set_drvdata(dev, NULL); - - return ret; + return -EINVAL; } -static int max310x_remove(struct spi_device *spi) +static int max310x_remove(struct device *dev) { - struct device *dev = &spi->dev; struct max310x_port *s = dev_get_drvdata(dev); - int ret = 0; - - dev_dbg(dev, "Removing port\n"); - - devm_free_irq(dev, s->port.irq, s); + int i, ret = 0; - destroy_workqueue(s->wq); - - uart_remove_one_port(&s->uart, &s->port); + for (i = 0; i < s->uart.nr; i++) { + cancel_work_sync(&s->p[i].tx_work); + uart_remove_one_port(&s->uart, &s->p[i].port); + s->devtype->power(&s->p[i].port, 0); + } uart_unregister_driver(&s->uart); #ifdef CONFIG_GPIOLIB - if (s->pdata->gpio_base) { + if (s->gpio_used) ret = gpiochip_remove(&s->gpio); - if (ret) - dev_err(dev, "Failed to remove gpio chip: %d\n", ret); - } #endif - dev_set_drvdata(dev, NULL); - - if (s->pdata->suspend) - s->pdata->suspend(1); if (s->pdata->exit) s->pdata->exit(); return ret; } +#ifdef CONFIG_SPI_MASTER +static int max310x_spi_probe(struct spi_device *spi) +{ + struct max310x_devtype *devtype = + (struct max310x_devtype *)spi_get_device_id(spi)->driver_data; + int ret; + + /* Setup SPI bus */ + spi->bits_per_word = 8; + spi->mode = spi->mode ? : SPI_MODE_0; + spi->max_speed_hz = spi->max_speed_hz ? : 26000000; + ret = spi_setup(spi); + if (ret) { + dev_err(&spi->dev, "SPI setup failed\n"); + return ret; + } + + return max310x_probe(&spi->dev, 1, devtype, spi->irq); +} + +static int max310x_spi_remove(struct spi_device *spi) +{ + return max310x_remove(&spi->dev); +} + +static SIMPLE_DEV_PM_OPS(max310x_pm_ops, max310x_suspend, max310x_resume); + static const struct spi_device_id max310x_id_table[] = { - { "max3107", MAX310X_TYPE_MAX3107 }, - { "max3108", MAX310X_TYPE_MAX3108 }, + { "max3107", (kernel_ulong_t)&max3107_devtype, }, + { "max3108", (kernel_ulong_t)&max3108_devtype, }, { } }; MODULE_DEVICE_TABLE(spi, max310x_id_table); -static struct spi_driver max310x_driver = { +static struct spi_driver max310x_uart_driver = { .driver = { - .name = "max310x", + .name = MAX310X_NAME, .owner = THIS_MODULE, - .pm = MAX310X_PM_OPS, + .pm = &max310x_pm_ops, }, - .probe = max310x_probe, - .remove = max310x_remove, + .probe = max310x_spi_probe, + .remove = max310x_spi_remove, .id_table = max310x_id_table, }; -module_spi_driver(max310x_driver); +module_spi_driver(max310x_uart_driver); +#endif -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); MODULE_AUTHOR("Alexander Shiyan "); MODULE_DESCRIPTION("MAX310X serial driver"); diff --git a/include/linux/platform_data/max310x.h b/include/linux/platform_data/max310x.h index 91648bf5fc5c..1aec0b620ac3 100644 --- a/include/linux/platform_data/max310x.h +++ b/include/linux/platform_data/max310x.h @@ -42,9 +42,8 @@ /* MAX310X platform data structure */ struct max310x_pdata { /* Flags global to driver */ - const u8 driver_flags:2; + const u8 driver_flags; #define MAX310X_EXT_CLK (0x00000001) /* External clock enable */ -#define MAX310X_AUTOSLEEP (0x00000002) /* Enable AutoSleep mode */ /* Flags global to UART port */ const u8 uart_flags[MAX310X_MAX_UARTS]; #define MAX310X_LOOPBACK (0x00000001) /* Loopback mode enable */ @@ -60,8 +59,6 @@ struct max310x_pdata { void (*init)(void); /* Called before finish */ void (*exit)(void); - /* Suspend callback */ - void (*suspend)(int do_suspend); }; #endif -- cgit v1.2.3-71-gd317 From 21fc509f1194c2fa06eff4c72238210089c29453 Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Sat, 29 Jun 2013 10:44:18 +0400 Subject: serial: max310x: Add MAX3109 support This patch adds support for MAX3109 (advanced dual universal asynchronous receiver-transmitter) into max310x driver. Signed-off-by: Alexander Shiyan Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/Kconfig | 2 +- drivers/tty/serial/max310x.c | 35 +++++++++++++++++++++++++++++++---- include/linux/platform_data/max310x.h | 4 ++-- 3 files changed, 34 insertions(+), 7 deletions(-) (limited to 'include/linux/platform_data') diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 25772c15276d..8e1a9c53ad7f 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -297,7 +297,7 @@ config SERIAL_MAX310X default n help This selects support for an advanced UART from Maxim (Dallas). - Supported ICs are MAX3107, MAX3108. + Supported ICs are MAX3107, MAX3108, MAX3109. Each IC contains 128 words each of receive and transmit FIFO that can be controlled through I2C or high-speed SPI. diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index 4620289e9e49..a6c46427363b 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -1,5 +1,5 @@ /* - * Maxim (Dallas) MAX3107/8 serial driver + * Maxim (Dallas) MAX3107/8/9 serial driver * * Copyright (C) 2012-2013 Alexander Shiyan * @@ -13,9 +13,6 @@ * (at your option) any later version. */ -/* TODO: MAX3109 support (Dual) */ -/* TODO: MAX14830 support (Quad) */ - #include #include #include @@ -269,6 +266,9 @@ /* MAX3107 specific */ #define MAX3107_REV_ID (0xa0) +/* MAX3109 specific */ +#define MAX3109_REV_ID (0xc0) + struct max310x_devtype { char name[9]; int nr; @@ -359,6 +359,25 @@ static int max3108_detect(struct device *dev) return 0; } +static int max3109_detect(struct device *dev) +{ + struct max310x_port *s = dev_get_drvdata(dev); + unsigned int val = 0; + int ret; + + ret = regmap_read(s->regmap, MAX310X_REVID_REG, &val); + if (ret) + return ret; + + if (((val & MAX310x_REV_MASK) != MAX3109_REV_ID)) { + dev_err(dev, + "%s ID 0x%02x does not match\n", s->devtype->name, val); + return -ENODEV; + } + + return 0; +} + static void max310x_power(struct uart_port *port, int on) { max310x_port_update(port, MAX310X_MODE1_REG, @@ -382,6 +401,13 @@ static const struct max310x_devtype max3108_devtype = { .power = max310x_power, }; +static const struct max310x_devtype max3109_devtype = { + .name = "MAX3109", + .nr = 2, + .detect = max3109_detect, + .power = max310x_power, +}; + static bool max310x_reg_writeable(struct device *dev, unsigned int reg) { switch (reg & 0x1f) { @@ -1226,6 +1252,7 @@ static SIMPLE_DEV_PM_OPS(max310x_pm_ops, max310x_suspend, max310x_resume); static const struct spi_device_id max310x_id_table[] = { { "max3107", (kernel_ulong_t)&max3107_devtype, }, { "max3108", (kernel_ulong_t)&max3108_devtype, }, + { "max3109", (kernel_ulong_t)&max3109_devtype, }, { } }; MODULE_DEVICE_TABLE(spi, max310x_id_table); diff --git a/include/linux/platform_data/max310x.h b/include/linux/platform_data/max310x.h index 1aec0b620ac3..4c128eda26ba 100644 --- a/include/linux/platform_data/max310x.h +++ b/include/linux/platform_data/max310x.h @@ -1,5 +1,5 @@ /* - * Maxim (Dallas) MAX3107/8 serial driver + * Maxim (Dallas) MAX3107/8/9 serial driver * * Copyright (C) 2012 Alexander Shiyan * @@ -37,7 +37,7 @@ * }; */ -#define MAX310X_MAX_UARTS 1 +#define MAX310X_MAX_UARTS 2 /* MAX310X platform data structure */ struct max310x_pdata { -- cgit v1.2.3-71-gd317 From 003236d9ac4d02721867e47f7ad4371ab7f74689 Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Sat, 29 Jun 2013 10:44:19 +0400 Subject: serial: max310x: Add MAX14830 support This patch adds support for MAX14830 (advanced quad universal asynchronous receiver-transmitter) into max310x driver. Signed-off-by: Alexander Shiyan Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/Kconfig | 2 +- drivers/tty/serial/max310x.c | 45 ++++++++++++++++++++++++++++++++++- include/linux/platform_data/max310x.h | 4 ++-- 3 files changed, 47 insertions(+), 4 deletions(-) (limited to 'include/linux/platform_data') diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 8e1a9c53ad7f..bc486deeeae5 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -297,7 +297,7 @@ config SERIAL_MAX310X default n help This selects support for an advanced UART from Maxim (Dallas). - Supported ICs are MAX3107, MAX3108, MAX3109. + Supported ICs are MAX3107, MAX3108, MAX3109, MAX14830. Each IC contains 128 words each of receive and transmit FIFO that can be controlled through I2C or high-speed SPI. diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index a6c46427363b..4ab5b272a593 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -1,5 +1,5 @@ /* - * Maxim (Dallas) MAX3107/8/9 serial driver + * Maxim (Dallas) MAX3107/8/9, MAX14830 serial driver * * Copyright (C) 2012-2013 Alexander Shiyan * @@ -269,6 +269,10 @@ /* MAX3109 specific */ #define MAX3109_REV_ID (0xc0) +/* MAX14830 specific */ +#define MAX14830_BRGCFG_CLKDIS_BIT (1 << 6) /* Clock Disable */ +#define MAX14830_REV_ID (0xb0) + struct max310x_devtype { char name[9]; int nr; @@ -387,6 +391,37 @@ static void max310x_power(struct uart_port *port, int on) msleep(50); } +static int max14830_detect(struct device *dev) +{ + struct max310x_port *s = dev_get_drvdata(dev); + unsigned int val = 0; + int ret; + + ret = regmap_write(s->regmap, MAX310X_GLOBALCMD_REG, + MAX310X_EXTREG_ENBL); + if (ret) + return ret; + + regmap_read(s->regmap, MAX310X_REVID_EXTREG, &val); + regmap_write(s->regmap, MAX310X_GLOBALCMD_REG, MAX310X_EXTREG_DSBL); + if (((val & MAX310x_REV_MASK) != MAX14830_REV_ID)) { + dev_err(dev, + "%s ID 0x%02x does not match\n", s->devtype->name, val); + return -ENODEV; + } + + return 0; +} + +static void max14830_power(struct uart_port *port, int on) +{ + max310x_port_update(port, MAX310X_BRGCFG_REG, + MAX14830_BRGCFG_CLKDIS_BIT, + on ? 0 : MAX14830_BRGCFG_CLKDIS_BIT); + if (on) + msleep(50); +} + static const struct max310x_devtype max3107_devtype = { .name = "MAX3107", .nr = 1, @@ -408,6 +443,13 @@ static const struct max310x_devtype max3109_devtype = { .power = max310x_power, }; +static const struct max310x_devtype max14830_devtype = { + .name = "MAX14830", + .nr = 4, + .detect = max14830_detect, + .power = max14830_power, +}; + static bool max310x_reg_writeable(struct device *dev, unsigned int reg) { switch (reg & 0x1f) { @@ -1253,6 +1295,7 @@ static const struct spi_device_id max310x_id_table[] = { { "max3107", (kernel_ulong_t)&max3107_devtype, }, { "max3108", (kernel_ulong_t)&max3108_devtype, }, { "max3109", (kernel_ulong_t)&max3109_devtype, }, + { "max14830", (kernel_ulong_t)&max14830_devtype, }, { } }; MODULE_DEVICE_TABLE(spi, max310x_id_table); diff --git a/include/linux/platform_data/max310x.h b/include/linux/platform_data/max310x.h index 4c128eda26ba..dd11dcd1a184 100644 --- a/include/linux/platform_data/max310x.h +++ b/include/linux/platform_data/max310x.h @@ -1,5 +1,5 @@ /* - * Maxim (Dallas) MAX3107/8/9 serial driver + * Maxim (Dallas) MAX3107/8/9, MAX14830 serial driver * * Copyright (C) 2012 Alexander Shiyan * @@ -37,7 +37,7 @@ * }; */ -#define MAX310X_MAX_UARTS 2 +#define MAX310X_MAX_UARTS 4 /* MAX310X platform data structure */ struct max310x_pdata { -- cgit v1.2.3-71-gd317 From 5fed6828318119656e243c4f0a11955cefc8eebd Mon Sep 17 00:00:00 2001 From: Tuomas Tynkkynen Date: Thu, 25 Jul 2013 21:38:04 +0300 Subject: ARM: tegra: Remove USB platform data USB-related platform data is not used anymore in the Tegra USB drivers, so remove all of it. Signed-off-by: Tuomas Tynkkynen Reviewed-by: Stephen Warren Tested-by: Stephen Warren Acked-by: Stephen Warren Signed-off-by: Felipe Balbi --- arch/arm/mach-tegra/tegra.c | 38 +-------------------------------- include/linux/platform_data/tegra_usb.h | 32 --------------------------- include/linux/usb/tegra_usb_phy.h | 5 ----- 3 files changed, 1 insertion(+), 74 deletions(-) delete mode 100644 include/linux/platform_data/tegra_usb.h (limited to 'include/linux/platform_data') diff --git a/arch/arm/mach-tegra/tegra.c b/arch/arm/mach-tegra/tegra.c index 0d1e4128d460..fc97cfd52769 100644 --- a/arch/arm/mach-tegra/tegra.c +++ b/arch/arm/mach-tegra/tegra.c @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -46,40 +45,6 @@ #include "fuse.h" #include "iomap.h" -static struct tegra_ehci_platform_data tegra_ehci1_pdata = { - .operating_mode = TEGRA_USB_OTG, - .power_down_on_bus_suspend = 1, - .vbus_gpio = -1, -}; - -static struct tegra_ulpi_config tegra_ehci2_ulpi_phy_config = { - .reset_gpio = -1, - .clk = "cdev2", -}; - -static struct tegra_ehci_platform_data tegra_ehci2_pdata = { - .phy_config = &tegra_ehci2_ulpi_phy_config, - .operating_mode = TEGRA_USB_HOST, - .power_down_on_bus_suspend = 1, - .vbus_gpio = -1, -}; - -static struct tegra_ehci_platform_data tegra_ehci3_pdata = { - .operating_mode = TEGRA_USB_HOST, - .power_down_on_bus_suspend = 1, - .vbus_gpio = -1, -}; - -static struct of_dev_auxdata tegra20_auxdata_lookup[] __initdata = { - OF_DEV_AUXDATA("nvidia,tegra20-ehci", 0xC5000000, "tegra-ehci.0", - &tegra_ehci1_pdata), - OF_DEV_AUXDATA("nvidia,tegra20-ehci", 0xC5004000, "tegra-ehci.1", - &tegra_ehci2_pdata), - OF_DEV_AUXDATA("nvidia,tegra20-ehci", 0xC5008000, "tegra-ehci.2", - &tegra_ehci3_pdata), - {} -}; - static void __init tegra_dt_init(void) { struct soc_device_attribute *soc_dev_attr; @@ -112,8 +77,7 @@ static void __init tegra_dt_init(void) * devices */ out: - of_platform_populate(NULL, of_default_bus_match_table, - tegra20_auxdata_lookup, parent); + of_platform_populate(NULL, of_default_bus_match_table, NULL, parent); } static void __init trimslice_init(void) diff --git a/include/linux/platform_data/tegra_usb.h b/include/linux/platform_data/tegra_usb.h deleted file mode 100644 index 66c673fef408..000000000000 --- a/include/linux/platform_data/tegra_usb.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2010 Google, Inc. - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * 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 _TEGRA_USB_H_ -#define _TEGRA_USB_H_ - -enum tegra_usb_operating_modes { - TEGRA_USB_DEVICE, - TEGRA_USB_HOST, - TEGRA_USB_OTG, -}; - -struct tegra_ehci_platform_data { - enum tegra_usb_operating_modes operating_mode; - /* power down the phy on bus suspend */ - int power_down_on_bus_suspend; - void *phy_config; - int vbus_gpio; -}; - -#endif /* _TEGRA_USB_H_ */ diff --git a/include/linux/usb/tegra_usb_phy.h b/include/linux/usb/tegra_usb_phy.h index c2bc7106aaa8..847415313ad2 100644 --- a/include/linux/usb/tegra_usb_phy.h +++ b/include/linux/usb/tegra_usb_phy.h @@ -28,11 +28,6 @@ struct tegra_utmip_config { u8 xcvr_lsrslew; }; -struct tegra_ulpi_config { - int reset_gpio; - const char *clk; -}; - enum tegra_usb_phy_port_speed { TEGRA_USB_PHY_PORT_SPEED_FULL = 0, TEGRA_USB_PHY_PORT_SPEED_LOW, -- cgit v1.2.3-71-gd317 From 8fe120b5a665fc869c23f86e4964b801f6e53486 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 31 Jul 2013 19:00:32 +0200 Subject: ASoC: omap-abe-twl6040: Remove support for pdata (legacy boot) Just recently OMAP4 legacy boot support has been removed. No reason to keep the code used by the legacy boot (pdata based) since neither OMAP4 or OMAP5 can boot in this mode. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- include/linux/platform_data/omap-abe-twl6040.h | 49 --------- sound/soc/omap/omap-abe-twl6040.c | 133 ++++++++----------------- 2 files changed, 41 insertions(+), 141 deletions(-) delete mode 100644 include/linux/platform_data/omap-abe-twl6040.h (limited to 'include/linux/platform_data') diff --git a/include/linux/platform_data/omap-abe-twl6040.h b/include/linux/platform_data/omap-abe-twl6040.h deleted file mode 100644 index 5d298ac10fc2..000000000000 --- a/include/linux/platform_data/omap-abe-twl6040.h +++ /dev/null @@ -1,49 +0,0 @@ -/** - * omap-abe-twl6040.h - ASoC machine driver OMAP4+ devices, header. - * - * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com - * All rights reserved. - * - * Author: Peter Ujfalusi - * - * 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. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA - */ - -#ifndef _OMAP_ABE_TWL6040_H_ -#define _OMAP_ABE_TWL6040_H_ - -/* To select if only one channel is connected in a stereo port */ -#define ABE_TWL6040_LEFT (1 << 0) -#define ABE_TWL6040_RIGHT (1 << 1) - -struct omap_abe_twl6040_data { - char *card_name; - /* Feature flags for connected audio pins */ - u8 has_hs; - u8 has_hf; - bool has_ep; - u8 has_aux; - u8 has_vibra; - bool has_dmic; - bool has_hsmic; - bool has_mainmic; - bool has_submic; - u8 has_afm; - /* Other features */ - bool jack_detection; /* board can detect jack events */ - int mclk_freq; /* MCLK frequency speed for twl6040 */ -}; - -#endif /* _OMAP_ABE_TWL6040_H_ */ diff --git a/sound/soc/omap/omap-abe-twl6040.c b/sound/soc/omap/omap-abe-twl6040.c index 70cd5c7b2e14..ebb13906b3a0 100644 --- a/sound/soc/omap/omap-abe-twl6040.c +++ b/sound/soc/omap/omap-abe-twl6040.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include @@ -166,19 +165,10 @@ static const struct snd_soc_dapm_route audio_map[] = { {"AFMR", NULL, "Line In"}, }; -static inline void twl6040_disconnect_pin(struct snd_soc_dapm_context *dapm, - int connected, char *pin) -{ - if (!connected) - snd_soc_dapm_disable_pin(dapm, pin); -} - static int omap_abe_twl6040_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_codec *codec = rtd->codec; struct snd_soc_card *card = codec->card; - struct snd_soc_dapm_context *dapm = &codec->dapm; - struct omap_abe_twl6040_data *pdata = dev_get_platdata(card->dev); struct abe_twl6040 *priv = snd_soc_card_get_drvdata(card); int hs_trim; int ret = 0; @@ -203,24 +193,6 @@ static int omap_abe_twl6040_init(struct snd_soc_pcm_runtime *rtd) twl6040_hs_jack_detect(codec, &hs_jack, SND_JACK_HEADSET); } - /* - * NULL pdata means we booted with DT. In this case the routing is - * provided and the card is fully routed, no need to mark pins. - */ - if (!pdata) - return ret; - - /* Disable not connected paths if not used */ - twl6040_disconnect_pin(dapm, pdata->has_hs, "Headset Stereophone"); - twl6040_disconnect_pin(dapm, pdata->has_hf, "Ext Spk"); - twl6040_disconnect_pin(dapm, pdata->has_ep, "Earphone Spk"); - twl6040_disconnect_pin(dapm, pdata->has_aux, "Line Out"); - twl6040_disconnect_pin(dapm, pdata->has_vibra, "Vibrator"); - twl6040_disconnect_pin(dapm, pdata->has_hsmic, "Headset Mic"); - twl6040_disconnect_pin(dapm, pdata->has_mainmic, "Main Handset Mic"); - twl6040_disconnect_pin(dapm, pdata->has_submic, "Sub Handset Mic"); - twl6040_disconnect_pin(dapm, pdata->has_afm, "Line In"); - return ret; } @@ -274,13 +246,18 @@ static struct snd_soc_card omap_abe_card = { static int omap_abe_probe(struct platform_device *pdev) { - struct omap_abe_twl6040_data *pdata = dev_get_platdata(&pdev->dev); struct device_node *node = pdev->dev.of_node; struct snd_soc_card *card = &omap_abe_card; + struct device_node *dai_node; struct abe_twl6040 *priv; int num_links = 0; int ret = 0; + if (!node) { + dev_err(&pdev->dev, "of node is missing.\n"); + return -ENODEV; + } + card->dev = &pdev->dev; priv = devm_kzalloc(&pdev->dev, sizeof(struct abe_twl6040), GFP_KERNEL); @@ -289,78 +266,50 @@ static int omap_abe_probe(struct platform_device *pdev) priv->dmic_codec_dev = ERR_PTR(-EINVAL); - if (node) { - struct device_node *dai_node; - - if (snd_soc_of_parse_card_name(card, "ti,model")) { - dev_err(&pdev->dev, "Card name is not provided\n"); - return -ENODEV; - } + if (snd_soc_of_parse_card_name(card, "ti,model")) { + dev_err(&pdev->dev, "Card name is not provided\n"); + return -ENODEV; + } - ret = snd_soc_of_parse_audio_routing(card, - "ti,audio-routing"); - if (ret) { - dev_err(&pdev->dev, - "Error while parsing DAPM routing\n"); - return ret; - } + ret = snd_soc_of_parse_audio_routing(card, "ti,audio-routing"); + if (ret) { + dev_err(&pdev->dev, "Error while parsing DAPM routing\n"); + return ret; + } - dai_node = of_parse_phandle(node, "ti,mcpdm", 0); - if (!dai_node) { - dev_err(&pdev->dev, "McPDM node is not provided\n"); - return -EINVAL; - } - abe_twl6040_dai_links[0].cpu_dai_name = NULL; - abe_twl6040_dai_links[0].cpu_of_node = dai_node; + dai_node = of_parse_phandle(node, "ti,mcpdm", 0); + if (!dai_node) { + dev_err(&pdev->dev, "McPDM node is not provided\n"); + return -EINVAL; + } + abe_twl6040_dai_links[0].cpu_dai_name = NULL; + abe_twl6040_dai_links[0].cpu_of_node = dai_node; - dai_node = of_parse_phandle(node, "ti,dmic", 0); - if (dai_node) { - num_links = 2; - abe_twl6040_dai_links[1].cpu_dai_name = NULL; - abe_twl6040_dai_links[1].cpu_of_node = dai_node; + dai_node = of_parse_phandle(node, "ti,dmic", 0); + if (dai_node) { + num_links = 2; + abe_twl6040_dai_links[1].cpu_dai_name = NULL; + abe_twl6040_dai_links[1].cpu_of_node = dai_node; - priv->dmic_codec_dev = platform_device_register_simple( + priv->dmic_codec_dev = platform_device_register_simple( "dmic-codec", -1, NULL, 0); - if (IS_ERR(priv->dmic_codec_dev)) { - dev_err(&pdev->dev, - "Can't instantiate dmic-codec\n"); - return PTR_ERR(priv->dmic_codec_dev); - } - } else { - num_links = 1; - } - - priv->jack_detection = of_property_read_bool(node, - "ti,jack-detection"); - of_property_read_u32(node, "ti,mclk-freq", - &priv->mclk_freq); - if (!priv->mclk_freq) { - dev_err(&pdev->dev, "MCLK frequency not provided\n"); - ret = -EINVAL; - goto err_unregister; + if (IS_ERR(priv->dmic_codec_dev)) { + dev_err(&pdev->dev, "Can't instantiate dmic-codec\n"); + return PTR_ERR(priv->dmic_codec_dev); } - - omap_abe_card.fully_routed = 1; - } else if (pdata) { - if (pdata->card_name) { - card->name = pdata->card_name; - } else { - dev_err(&pdev->dev, "Card name is not provided\n"); - return -ENODEV; - } - - if (pdata->has_dmic) - num_links = 2; - else - num_links = 1; - - priv->jack_detection = pdata->jack_detection; - priv->mclk_freq = pdata->mclk_freq; } else { - dev_err(&pdev->dev, "Missing pdata\n"); - return -ENODEV; + num_links = 1; + } + + priv->jack_detection = of_property_read_bool(node, "ti,jack-detection"); + of_property_read_u32(node, "ti,mclk-freq", &priv->mclk_freq); + if (!priv->mclk_freq) { + dev_err(&pdev->dev, "MCLK frequency not provided\n"); + ret = -EINVAL; + goto err_unregister; } + card->fully_routed = 1; if (!priv->mclk_freq) { dev_err(&pdev->dev, "MCLK frequency missing\n"); -- cgit v1.2.3-71-gd317 From 90efa75f7ab0be5677f0cca155dbf0b39eacdd03 Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Wed, 31 Jul 2013 14:56:30 +0400 Subject: serial: sccnxp: Using CLK API for getting UART clock This patch removes "frequency" parameter from SCCNXP platform_data and uses CLK API for getting clock. If CLK ommited, default IC frequency will be used instead. Signed-off-by: Alexander Shiyan Signed-off-by: Greg Kroah-Hartman --- arch/mips/sni/a20r.c | 1 - drivers/tty/serial/sccnxp.c | 36 ++++++++++++++++++----------- include/linux/platform_data/serial-sccnxp.h | 3 --- 3 files changed, 22 insertions(+), 18 deletions(-) (limited to 'include/linux/platform_data') diff --git a/arch/mips/sni/a20r.c b/arch/mips/sni/a20r.c index dd0ab982d77e..f9407e170476 100644 --- a/arch/mips/sni/a20r.c +++ b/arch/mips/sni/a20r.c @@ -122,7 +122,6 @@ static struct resource sc26xx_rsrc[] = { static struct sccnxp_pdata sccnxp_data = { .reg_shift = 2, - .frequency = 3686400, .mctrl_cfg[0] = MCTRL_SIG(DTR_OP, LINE_OP7) | MCTRL_SIG(RTS_OP, LINE_OP3) | MCTRL_SIG(DSR_IP, LINE_IP5) | diff --git a/drivers/tty/serial/sccnxp.c b/drivers/tty/serial/sccnxp.c index 12a5c265f43a..81e70477e6fd 100644 --- a/drivers/tty/serial/sccnxp.c +++ b/drivers/tty/serial/sccnxp.c @@ -15,6 +15,7 @@ #define SUPPORT_SYSRQ #endif +#include #include #include #include @@ -783,9 +784,10 @@ static int sccnxp_probe(struct platform_device *pdev) struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); int chiptype = pdev->id_entry->driver_data; struct sccnxp_pdata *pdata = dev_get_platdata(&pdev->dev); - int i, ret, fifosize, freq_min, freq_max; + int i, ret, fifosize, freq_min, freq_max, uartclk; struct sccnxp_port *s; void __iomem *membase; + struct clk *clk; membase = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(membase)) @@ -898,11 +900,25 @@ static int sccnxp_probe(struct platform_device *pdev) } else if (PTR_ERR(s->regulator) == -EPROBE_DEFER) return -EPROBE_DEFER; - if (!pdata) { - dev_warn(&pdev->dev, - "No platform data supplied, using defaults\n"); - s->pdata.frequency = s->freq_std; + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + if (PTR_ERR(clk) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + goto err_out; + } + dev_notice(&pdev->dev, "Using default clock frequency\n"); + uartclk = s->freq_std; } else + uartclk = clk_get_rate(clk); + + /* Check input frequency */ + if ((uartclk < freq_min) || (uartclk > freq_max)) { + dev_err(&pdev->dev, "Frequency out of bounds\n"); + ret = -EINVAL; + goto err_out; + } + + if (pdata) memcpy(&s->pdata, pdata, sizeof(struct sccnxp_pdata)); if (s->pdata.poll_time_us) { @@ -920,14 +936,6 @@ static int sccnxp_probe(struct platform_device *pdev) } } - /* Check input frequency */ - if ((s->pdata.frequency < freq_min) || - (s->pdata.frequency > freq_max)) { - dev_err(&pdev->dev, "Frequency out of bounds\n"); - ret = -EINVAL; - goto err_out; - } - s->uart.owner = THIS_MODULE; s->uart.dev_name = "ttySC"; s->uart.major = SCCNXP_MAJOR; @@ -959,7 +967,7 @@ static int sccnxp_probe(struct platform_device *pdev) s->port[i].mapbase = res->start; s->port[i].membase = membase; s->port[i].regshift = s->pdata.reg_shift; - s->port[i].uartclk = s->pdata.frequency; + s->port[i].uartclk = uartclk; s->port[i].ops = &sccnxp_ops; uart_add_one_port(&s->uart, &s->port[i]); /* Set direction to input */ diff --git a/include/linux/platform_data/serial-sccnxp.h b/include/linux/platform_data/serial-sccnxp.h index bdc510d03245..af0c8c3b89ae 100644 --- a/include/linux/platform_data/serial-sccnxp.h +++ b/include/linux/platform_data/serial-sccnxp.h @@ -60,7 +60,6 @@ * }; * * static struct sccnxp_pdata sc2892_info = { - * .frequency = 3686400, * .mctrl_cfg[0] = MCTRL_SIG(DIR_OP, LINE_OP0), * .mctrl_cfg[1] = MCTRL_SIG(DIR_OP, LINE_OP1), * }; @@ -78,8 +77,6 @@ /* SCCNXP platform data structure */ struct sccnxp_pdata { - /* Frequency (extrenal clock or crystal) */ - int frequency; /* Shift for A0 line */ const u8 reg_shift; /* Modem control lines configuration */ -- cgit v1.2.3-71-gd317 From 85c996907473e4ef824774b97b26499adf66521f Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Wed, 31 Jul 2013 14:55:45 +0400 Subject: serial: sccnxp: Add DT support Add DT support to the SCCNCP serial driver. Signed-off-by: Alexander Shiyan Signed-off-by: Greg Kroah-Hartman --- .../bindings/tty/serial/sccnxp-serial.txt | 53 ++++++++++++++++++++++ drivers/tty/serial/sccnxp.c | 46 +++++++++++++++---- include/linux/platform_data/serial-sccnxp.h | 6 +-- 3 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 Documentation/devicetree/bindings/tty/serial/sccnxp-serial.txt (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/tty/serial/sccnxp-serial.txt b/Documentation/devicetree/bindings/tty/serial/sccnxp-serial.txt new file mode 100644 index 000000000000..d18b1698133e --- /dev/null +++ b/Documentation/devicetree/bindings/tty/serial/sccnxp-serial.txt @@ -0,0 +1,53 @@ +* NXP (Philips) SCC+++(SCN+++) serial driver + +Required properties: +- compatible: Should be "nxp,". The supported ICs include sc2681, + sc2691, sc2692, sc2891, sc2892, sc28202, sc68681 and sc68692. +- reg: Address and length of the register set for the device. +- interrupts: Should contain the interrupt number. If omitted, + polling mode will be used instead, so "poll-interval" property should + be populated in this case. + +Optional properties: +- clocks: Phandle to input clock. If omitted, default IC frequency will be + used instead. +- poll-interval: Poll interval time in nanoseconds. +- vcc-supply: The regulator supplying the VCC to drive the chip. +- nxp,sccnxp-io-cfg: Array contains values for the emulated modem signals. + The number of values depends on the UART-number in the selected chip. + Each value should be composed according to the following rules: + (LINE1 << SIGNAL1) | ... | (LINEX << SIGNALX), where: + LINE - VALUE: + OP0 - 1 + OP1 - 2 + OP2 - 3 + OP3 - 4 + OP4 - 5 + OP5 - 6 + OP6 - 7 + OP7 - 8 + IP0 - 9 + IP1 - 10 + IP2 - 11 + IP3 - 12 + IP4 - 13 + IP5 - 14 + IP6 - 15 + SIGNAL - VALUE: + DTR - 0 + RTS - 4 + DSR - 8 + CTS - 12 + DCD - 16 + RNG - 20 + DIR - 24 + +Example (Dual UART with direction control on OP0 & OP1): +sc2892@10100000 { + compatible = "nxp,sc2892"; + reg = <0x10100000 0x10>; + poll-interval = <10000>; + clocks = <&sc2892_clk>; + vcc-supply = <&sc2892_reg>; + nxp,sccnxp-io-cfg = <0x01000000 0x02000000>; +}; diff --git a/drivers/tty/serial/sccnxp.c b/drivers/tty/serial/sccnxp.c index 49e9bbfe6cab..67f73d1a8e7b 100644 --- a/drivers/tty/serial/sccnxp.c +++ b/drivers/tty/serial/sccnxp.c @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include #include #include @@ -853,10 +855,25 @@ static const struct platform_device_id sccnxp_id_table[] = { }; MODULE_DEVICE_TABLE(platform, sccnxp_id_table); +static const struct of_device_id sccnxp_dt_id_table[] = { + { .compatible = "nxp,sc2681", .data = &sc2681, }, + { .compatible = "nxp,sc2691", .data = &sc2691, }, + { .compatible = "nxp,sc2692", .data = &sc2692, }, + { .compatible = "nxp,sc2891", .data = &sc2891, }, + { .compatible = "nxp,sc2892", .data = &sc2892, }, + { .compatible = "nxp,sc28202", .data = &sc28202, }, + { .compatible = "nxp,sc68681", .data = &sc68681, }, + { .compatible = "nxp,sc68692", .data = &sc68692, }, + { } +}; +MODULE_DEVICE_TABLE(of, sccnxp_dt_id_table); + static int sccnxp_probe(struct platform_device *pdev) { struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); struct sccnxp_pdata *pdata = dev_get_platdata(&pdev->dev); + const struct of_device_id *of_id = + of_match_device(sccnxp_dt_id_table, &pdev->dev); int i, ret, uartclk; struct sccnxp_port *s; void __iomem *membase; @@ -875,7 +892,22 @@ static int sccnxp_probe(struct platform_device *pdev) spin_lock_init(&s->lock); - s->chip = (struct sccnxp_chip *)pdev->id_entry->driver_data; + if (of_id) { + s->chip = (struct sccnxp_chip *)of_id->data; + + of_property_read_u32(pdev->dev.of_node, "poll-interval", + &s->pdata.poll_time_us); + of_property_read_u32(pdev->dev.of_node, "reg-shift", + &s->pdata.reg_shift); + of_property_read_u32_array(pdev->dev.of_node, + "nxp,sccnxp-io-cfg", + s->pdata.mctrl_cfg, s->chip->nr); + } else { + s->chip = (struct sccnxp_chip *)pdev->id_entry->driver_data; + + if (pdata) + memcpy(&s->pdata, pdata, sizeof(struct sccnxp_pdata)); + } s->regulator = devm_regulator_get(&pdev->dev, "vcc"); if (!IS_ERR(s->regulator)) { @@ -906,16 +938,11 @@ static int sccnxp_probe(struct platform_device *pdev) goto err_out; } - if (pdata) - memcpy(&s->pdata, pdata, sizeof(struct sccnxp_pdata)); - if (s->pdata.poll_time_us) { dev_info(&pdev->dev, "Using poll mode, resolution %u usecs\n", s->pdata.poll_time_us); s->poll = 1; - } - - if (!s->poll) { + } else { s->irq = platform_get_irq(pdev, 0); if (s->irq < 0) { dev_err(&pdev->dev, "Missing irq resource data\n"); @@ -1016,8 +1043,9 @@ static int sccnxp_remove(struct platform_device *pdev) static struct platform_driver sccnxp_uart_driver = { .driver = { - .name = SCCNXP_NAME, - .owner = THIS_MODULE, + .name = SCCNXP_NAME, + .owner = THIS_MODULE, + .of_match_table = sccnxp_dt_id_table, }, .probe = sccnxp_probe, .remove = sccnxp_remove, diff --git a/include/linux/platform_data/serial-sccnxp.h b/include/linux/platform_data/serial-sccnxp.h index af0c8c3b89ae..98373d6add27 100644 --- a/include/linux/platform_data/serial-sccnxp.h +++ b/include/linux/platform_data/serial-sccnxp.h @@ -78,11 +78,11 @@ /* SCCNXP platform data structure */ struct sccnxp_pdata { /* Shift for A0 line */ - const u8 reg_shift; + u32 reg_shift; /* Modem control lines configuration */ - const u32 mctrl_cfg[SCCNXP_MAX_UARTS]; + u32 mctrl_cfg[SCCNXP_MAX_UARTS]; /* Timer value for polling mode (usecs) */ - const unsigned int poll_time_us; + u32 poll_time_us; }; #endif -- cgit v1.2.3-71-gd317 From 461a8ecb2d76fb6f3ac1a90eb886697db869d387 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 2 Aug 2013 15:25:19 +0800 Subject: Revert "serial: sccnxp: Add DT support" This reverts commit 85c996907473e4ef824774b97b26499adf66521f. Alexander wishes to remove this patch as it is incorrect. Reported-by: Alexander Shiyan Signed-off-by: Greg Kroah-Hartman --- .../bindings/tty/serial/sccnxp-serial.txt | 53 ---------------------- drivers/tty/serial/sccnxp.c | 46 ++++--------------- include/linux/platform_data/serial-sccnxp.h | 6 +-- 3 files changed, 12 insertions(+), 93 deletions(-) delete mode 100644 Documentation/devicetree/bindings/tty/serial/sccnxp-serial.txt (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/tty/serial/sccnxp-serial.txt b/Documentation/devicetree/bindings/tty/serial/sccnxp-serial.txt deleted file mode 100644 index d18b1698133e..000000000000 --- a/Documentation/devicetree/bindings/tty/serial/sccnxp-serial.txt +++ /dev/null @@ -1,53 +0,0 @@ -* NXP (Philips) SCC+++(SCN+++) serial driver - -Required properties: -- compatible: Should be "nxp,". The supported ICs include sc2681, - sc2691, sc2692, sc2891, sc2892, sc28202, sc68681 and sc68692. -- reg: Address and length of the register set for the device. -- interrupts: Should contain the interrupt number. If omitted, - polling mode will be used instead, so "poll-interval" property should - be populated in this case. - -Optional properties: -- clocks: Phandle to input clock. If omitted, default IC frequency will be - used instead. -- poll-interval: Poll interval time in nanoseconds. -- vcc-supply: The regulator supplying the VCC to drive the chip. -- nxp,sccnxp-io-cfg: Array contains values for the emulated modem signals. - The number of values depends on the UART-number in the selected chip. - Each value should be composed according to the following rules: - (LINE1 << SIGNAL1) | ... | (LINEX << SIGNALX), where: - LINE - VALUE: - OP0 - 1 - OP1 - 2 - OP2 - 3 - OP3 - 4 - OP4 - 5 - OP5 - 6 - OP6 - 7 - OP7 - 8 - IP0 - 9 - IP1 - 10 - IP2 - 11 - IP3 - 12 - IP4 - 13 - IP5 - 14 - IP6 - 15 - SIGNAL - VALUE: - DTR - 0 - RTS - 4 - DSR - 8 - CTS - 12 - DCD - 16 - RNG - 20 - DIR - 24 - -Example (Dual UART with direction control on OP0 & OP1): -sc2892@10100000 { - compatible = "nxp,sc2892"; - reg = <0x10100000 0x10>; - poll-interval = <10000>; - clocks = <&sc2892_clk>; - vcc-supply = <&sc2892_reg>; - nxp,sccnxp-io-cfg = <0x01000000 0x02000000>; -}; diff --git a/drivers/tty/serial/sccnxp.c b/drivers/tty/serial/sccnxp.c index 67f73d1a8e7b..49e9bbfe6cab 100644 --- a/drivers/tty/serial/sccnxp.c +++ b/drivers/tty/serial/sccnxp.c @@ -20,8 +20,6 @@ #include #include #include -#include -#include #include #include #include @@ -855,25 +853,10 @@ static const struct platform_device_id sccnxp_id_table[] = { }; MODULE_DEVICE_TABLE(platform, sccnxp_id_table); -static const struct of_device_id sccnxp_dt_id_table[] = { - { .compatible = "nxp,sc2681", .data = &sc2681, }, - { .compatible = "nxp,sc2691", .data = &sc2691, }, - { .compatible = "nxp,sc2692", .data = &sc2692, }, - { .compatible = "nxp,sc2891", .data = &sc2891, }, - { .compatible = "nxp,sc2892", .data = &sc2892, }, - { .compatible = "nxp,sc28202", .data = &sc28202, }, - { .compatible = "nxp,sc68681", .data = &sc68681, }, - { .compatible = "nxp,sc68692", .data = &sc68692, }, - { } -}; -MODULE_DEVICE_TABLE(of, sccnxp_dt_id_table); - static int sccnxp_probe(struct platform_device *pdev) { struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); struct sccnxp_pdata *pdata = dev_get_platdata(&pdev->dev); - const struct of_device_id *of_id = - of_match_device(sccnxp_dt_id_table, &pdev->dev); int i, ret, uartclk; struct sccnxp_port *s; void __iomem *membase; @@ -892,22 +875,7 @@ static int sccnxp_probe(struct platform_device *pdev) spin_lock_init(&s->lock); - if (of_id) { - s->chip = (struct sccnxp_chip *)of_id->data; - - of_property_read_u32(pdev->dev.of_node, "poll-interval", - &s->pdata.poll_time_us); - of_property_read_u32(pdev->dev.of_node, "reg-shift", - &s->pdata.reg_shift); - of_property_read_u32_array(pdev->dev.of_node, - "nxp,sccnxp-io-cfg", - s->pdata.mctrl_cfg, s->chip->nr); - } else { - s->chip = (struct sccnxp_chip *)pdev->id_entry->driver_data; - - if (pdata) - memcpy(&s->pdata, pdata, sizeof(struct sccnxp_pdata)); - } + s->chip = (struct sccnxp_chip *)pdev->id_entry->driver_data; s->regulator = devm_regulator_get(&pdev->dev, "vcc"); if (!IS_ERR(s->regulator)) { @@ -938,11 +906,16 @@ static int sccnxp_probe(struct platform_device *pdev) goto err_out; } + if (pdata) + memcpy(&s->pdata, pdata, sizeof(struct sccnxp_pdata)); + if (s->pdata.poll_time_us) { dev_info(&pdev->dev, "Using poll mode, resolution %u usecs\n", s->pdata.poll_time_us); s->poll = 1; - } else { + } + + if (!s->poll) { s->irq = platform_get_irq(pdev, 0); if (s->irq < 0) { dev_err(&pdev->dev, "Missing irq resource data\n"); @@ -1043,9 +1016,8 @@ static int sccnxp_remove(struct platform_device *pdev) static struct platform_driver sccnxp_uart_driver = { .driver = { - .name = SCCNXP_NAME, - .owner = THIS_MODULE, - .of_match_table = sccnxp_dt_id_table, + .name = SCCNXP_NAME, + .owner = THIS_MODULE, }, .probe = sccnxp_probe, .remove = sccnxp_remove, diff --git a/include/linux/platform_data/serial-sccnxp.h b/include/linux/platform_data/serial-sccnxp.h index 98373d6add27..af0c8c3b89ae 100644 --- a/include/linux/platform_data/serial-sccnxp.h +++ b/include/linux/platform_data/serial-sccnxp.h @@ -78,11 +78,11 @@ /* SCCNXP platform data structure */ struct sccnxp_pdata { /* Shift for A0 line */ - u32 reg_shift; + const u8 reg_shift; /* Modem control lines configuration */ - u32 mctrl_cfg[SCCNXP_MAX_UARTS]; + const u32 mctrl_cfg[SCCNXP_MAX_UARTS]; /* Timer value for polling mode (usecs) */ - u32 poll_time_us; + const unsigned int poll_time_us; }; #endif -- cgit v1.2.3-71-gd317 From 5ef76da644bf346d29200007d8d3779e7009dabb Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Fri, 2 Aug 2013 14:05:20 +0200 Subject: fbdev: simplefb: add init through platform_data If we create proper platform-devices in x86 boot-code, we can use simplefb for VBE or EFI framebuffers, too. However, there is normally no OF support so we introduce a platform_data object so x86 boot-code can pass the parameters via plain old platform-data. This also removes the OF dependency as it is not needed. The headers provide proper dummies for the case OF is disabled. Furthermore, we move the FORMAT-definitions to the common platform header so initialization code can use it to transform "struct screen_info" to the right format-name. Signed-off-by: David Herrmann Link: http://lkml.kernel.org/r/1375445127-15480-2-git-send-email-dh.herrmann@gmail.com Reviewed-by: Stephen Warren Signed-off-by: H. Peter Anvin --- drivers/video/Kconfig | 5 ++- drivers/video/simplefb.c | 48 +++++++++++++++++++++-------- include/linux/platform_data/simplefb.h | 56 ++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 16 deletions(-) create mode 100644 include/linux/platform_data/simplefb.h (limited to 'include/linux/platform_data') diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 4cf1e1dd5621..34c3d960634d 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -2457,7 +2457,7 @@ config FB_HYPERV config FB_SIMPLE bool "Simple framebuffer support" - depends on (FB = y) && OF + depends on (FB = y) select FB_CFB_FILLRECT select FB_CFB_COPYAREA select FB_CFB_IMAGEBLIT @@ -2469,8 +2469,7 @@ config FB_SIMPLE pre-allocated frame buffer surface. Configuration re: surface address, size, and format must be provided - through device tree, or potentially plain old platform data in the - future. + through device tree, or plain old platform data. source "drivers/video/omap/Kconfig" source "drivers/video/omap2/Kconfig" diff --git a/drivers/video/simplefb.c b/drivers/video/simplefb.c index e2e9e3e61b72..588698986ce1 100644 --- a/drivers/video/simplefb.c +++ b/drivers/video/simplefb.c @@ -24,6 +24,7 @@ #include #include #include +#include #include static struct fb_fix_screeninfo simplefb_fix = { @@ -73,18 +74,7 @@ static struct fb_ops simplefb_ops = { .fb_imageblit = cfb_imageblit, }; -struct simplefb_format { - const char *name; - u32 bits_per_pixel; - struct fb_bitfield red; - struct fb_bitfield green; - struct fb_bitfield blue; - struct fb_bitfield transp; -}; - -static struct simplefb_format simplefb_formats[] = { - { "r5g6b5", 16, {11, 5}, {5, 6}, {0, 5}, {0, 0} }, -}; +static struct simplefb_format simplefb_formats[] = SIMPLEFB_FORMATS; struct simplefb_params { u32 width; @@ -139,6 +129,33 @@ static int simplefb_parse_dt(struct platform_device *pdev, return 0; } +static int simplefb_parse_pd(struct platform_device *pdev, + struct simplefb_params *params) +{ + struct simplefb_platform_data *pd = pdev->dev.platform_data; + int i; + + params->width = pd->width; + params->height = pd->height; + params->stride = pd->stride; + + params->format = NULL; + for (i = 0; i < ARRAY_SIZE(simplefb_formats); i++) { + if (strcmp(pd->format, simplefb_formats[i].name)) + continue; + + params->format = &simplefb_formats[i]; + break; + } + + if (!params->format) { + dev_err(&pdev->dev, "Invalid format value\n"); + return -EINVAL; + } + + return 0; +} + static int simplefb_probe(struct platform_device *pdev) { int ret; @@ -149,7 +166,12 @@ static int simplefb_probe(struct platform_device *pdev) if (fb_get_options("simplefb", NULL)) return -ENODEV; - ret = simplefb_parse_dt(pdev, ¶ms); + ret = -ENODEV; + if (pdev->dev.platform_data) + ret = simplefb_parse_pd(pdev, ¶ms); + else if (pdev->dev.of_node) + ret = simplefb_parse_dt(pdev, ¶ms); + if (ret) return ret; diff --git a/include/linux/platform_data/simplefb.h b/include/linux/platform_data/simplefb.h new file mode 100644 index 000000000000..5fa2c5e02ab8 --- /dev/null +++ b/include/linux/platform_data/simplefb.h @@ -0,0 +1,56 @@ +/* + * simplefb.h - Simple Framebuffer Device + * + * Copyright (C) 2013 David Herrmann + * + * 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. + */ + +#ifndef __PLATFORM_DATA_SIMPLEFB_H__ +#define __PLATFORM_DATA_SIMPLEFB_H__ + +#include +#include +#include + +/* format array, use it to initialize a "struct simplefb_format" array */ +#define SIMPLEFB_FORMATS \ +{ \ + { "r5g6b5", 16, {11, 5}, {5, 6}, {0, 5}, {0, 0}, DRM_FORMAT_RGB565 }, \ +} + +/* + * Data-Format for Simple-Framebuffers + * @name: unique 0-terminated name that can be used to identify the mode + * @red,green,blue: Offsets and sizes of the single RGB parts + * @transp: Offset and size of the alpha bits. length=0 means no alpha + * @fourcc: 32bit DRM four-CC code (see drm_fourcc.h) + */ +struct simplefb_format { + const char *name; + u32 bits_per_pixel; + struct fb_bitfield red; + struct fb_bitfield green; + struct fb_bitfield blue; + struct fb_bitfield transp; + u32 fourcc; +}; + +/* + * Simple-Framebuffer description + * If the arch-boot code creates simple-framebuffers without DT support, it + * can pass the width, height, stride and format via this platform-data object. + * The framebuffer location must be given as IORESOURCE_MEM resource. + * @format must be a format as described in "struct simplefb_format" above. + */ +struct simplefb_platform_data { + u32 width; + u32 height; + u32 stride; + const char *format; +}; + +#endif /* __PLATFORM_DATA_SIMPLEFB_H__ */ -- cgit v1.2.3-71-gd317 From 9a6a36d19c9d24479300511e23933876a4a9cf82 Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Fri, 2 Aug 2013 14:05:24 +0200 Subject: fbdev: simplefb: add common x86 RGB formats 32bit XRGB and ARGB are used by modern x86 systems for EFI and VESA framebuffers. The other formats were reported by hpa to be most common. Add these so simplefb works on most common x86 systems. Signed-off-by: David Herrmann Link: http://lkml.kernel.org/r/1375445127-15480-6-git-send-email-dh.herrmann@gmail.com Signed-off-by: H. Peter Anvin --- include/linux/platform_data/simplefb.h | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'include/linux/platform_data') diff --git a/include/linux/platform_data/simplefb.h b/include/linux/platform_data/simplefb.h index 5fa2c5e02ab8..53774b0cd8e9 100644 --- a/include/linux/platform_data/simplefb.h +++ b/include/linux/platform_data/simplefb.h @@ -20,6 +20,13 @@ #define SIMPLEFB_FORMATS \ { \ { "r5g6b5", 16, {11, 5}, {5, 6}, {0, 5}, {0, 0}, DRM_FORMAT_RGB565 }, \ + { "x1r5g5b5", 16, {10, 5}, {5, 5}, {0, 5}, {0, 0}, DRM_FORMAT_XRGB1555 }, \ + { "a1r5g5b5", 16, {10, 5}, {5, 5}, {0, 5}, {15, 1}, DRM_FORMAT_ARGB1555 }, \ + { "r8g8b8", 24, {16, 8}, {8, 8}, {0, 8}, {0, 0}, DRM_FORMAT_RGB888 }, \ + { "x8r8g8b8", 32, {16, 8}, {8, 8}, {0, 8}, {0, 0}, DRM_FORMAT_XRGB8888 }, \ + { "a8r8g8b8", 32, {16, 8}, {8, 8}, {0, 8}, {24, 8}, DRM_FORMAT_ARGB8888 }, \ + { "x2r10g10b10", 32, {20, 10}, {10, 10}, {0, 10}, {0, 0}, DRM_FORMAT_XRGB2101010 }, \ + { "a2r10g10b10", 32, {20, 10}, {10, 10}, {0, 10}, {30, 2}, DRM_FORMAT_ARGB2101010 }, \ } /* -- cgit v1.2.3-71-gd317 From 23cde4d65cc7d11e2048d2b240cdf13927ac50d0 Mon Sep 17 00:00:00 2001 From: Denis CIOCCA Date: Wed, 19 Jun 2013 09:28:00 +0100 Subject: iio: Added ST-sensors platform data to select the DRDY interrupt pin This patch add support to redirect the DRDY interrupt on INT1 or INT2 on accelerometer and pressure sensors. Signed-off-by: Denis Ciocca Signed-off-by: Jonathan Cameron --- drivers/iio/accel/st_accel.h | 11 ++++++- drivers/iio/accel/st_accel_core.c | 27 +++++++++++----- drivers/iio/accel/st_accel_i2c.c | 2 +- drivers/iio/accel/st_accel_spi.c | 2 +- drivers/iio/common/st_sensors/st_sensors_core.c | 41 ++++++++++++++++++++++--- drivers/iio/gyro/st_gyro.h | 11 ++++++- drivers/iio/gyro/st_gyro_core.c | 13 ++++---- drivers/iio/gyro/st_gyro_i2c.c | 3 +- drivers/iio/gyro/st_gyro_spi.c | 3 +- drivers/iio/magnetometer/st_magn.h | 3 +- drivers/iio/magnetometer/st_magn_core.c | 5 +-- drivers/iio/magnetometer/st_magn_i2c.c | 2 +- drivers/iio/magnetometer/st_magn_spi.c | 2 +- drivers/iio/pressure/st_pressure.h | 11 ++++++- drivers/iio/pressure/st_pressure_core.c | 15 ++++++--- drivers/iio/pressure/st_pressure_i2c.c | 2 +- drivers/iio/pressure/st_pressure_spi.c | 2 +- include/linux/iio/common/st_sensors.h | 14 +++++++-- include/linux/platform_data/st_sensors_pdata.h | 24 +++++++++++++++ 19 files changed, 153 insertions(+), 40 deletions(-) create mode 100644 include/linux/platform_data/st_sensors_pdata.h (limited to 'include/linux/platform_data') diff --git a/drivers/iio/accel/st_accel.h b/drivers/iio/accel/st_accel.h index 37949b94377d..c3877630b2e4 100644 --- a/drivers/iio/accel/st_accel.h +++ b/drivers/iio/accel/st_accel.h @@ -25,7 +25,16 @@ #define LSM303DLM_ACCEL_DEV_NAME "lsm303dlm_accel" #define LSM330_ACCEL_DEV_NAME "lsm330_accel" -int st_accel_common_probe(struct iio_dev *indio_dev); +/** +* struct st_sensors_platform_data - default accel platform data +* @drdy_int_pin: default accel DRDY is available on INT1 pin. +*/ +static const struct st_sensors_platform_data default_accel_pdata = { + .drdy_int_pin = 1, +}; + +int st_accel_common_probe(struct iio_dev *indio_dev, + struct st_sensors_platform_data *pdata); void st_accel_common_remove(struct iio_dev *indio_dev); #ifdef CONFIG_IIO_BUFFER diff --git a/drivers/iio/accel/st_accel_core.c b/drivers/iio/accel/st_accel_core.c index 4aec121261d7..aef3c9be7366 100644 --- a/drivers/iio/accel/st_accel_core.c +++ b/drivers/iio/accel/st_accel_core.c @@ -65,7 +65,8 @@ #define ST_ACCEL_1_BDU_ADDR 0x23 #define ST_ACCEL_1_BDU_MASK 0x80 #define ST_ACCEL_1_DRDY_IRQ_ADDR 0x22 -#define ST_ACCEL_1_DRDY_IRQ_MASK 0x10 +#define ST_ACCEL_1_DRDY_IRQ_INT1_MASK 0x10 +#define ST_ACCEL_1_DRDY_IRQ_INT2_MASK 0x08 #define ST_ACCEL_1_MULTIREAD_BIT true /* CUSTOM VALUES FOR SENSOR 2 */ @@ -89,7 +90,8 @@ #define ST_ACCEL_2_BDU_ADDR 0x23 #define ST_ACCEL_2_BDU_MASK 0x80 #define ST_ACCEL_2_DRDY_IRQ_ADDR 0x22 -#define ST_ACCEL_2_DRDY_IRQ_MASK 0x02 +#define ST_ACCEL_2_DRDY_IRQ_INT1_MASK 0x02 +#define ST_ACCEL_2_DRDY_IRQ_INT2_MASK 0x10 #define ST_ACCEL_2_MULTIREAD_BIT true /* CUSTOM VALUES FOR SENSOR 3 */ @@ -121,7 +123,8 @@ #define ST_ACCEL_3_BDU_ADDR 0x20 #define ST_ACCEL_3_BDU_MASK 0x08 #define ST_ACCEL_3_DRDY_IRQ_ADDR 0x23 -#define ST_ACCEL_3_DRDY_IRQ_MASK 0x80 +#define ST_ACCEL_3_DRDY_IRQ_INT1_MASK 0x80 +#define ST_ACCEL_3_DRDY_IRQ_INT2_MASK 0x00 #define ST_ACCEL_3_IG1_EN_ADDR 0x23 #define ST_ACCEL_3_IG1_EN_MASK 0x08 #define ST_ACCEL_3_MULTIREAD_BIT false @@ -224,7 +227,8 @@ static const struct st_sensors st_accel_sensors[] = { }, .drdy_irq = { .addr = ST_ACCEL_1_DRDY_IRQ_ADDR, - .mask = ST_ACCEL_1_DRDY_IRQ_MASK, + .mask_int1 = ST_ACCEL_1_DRDY_IRQ_INT1_MASK, + .mask_int2 = ST_ACCEL_1_DRDY_IRQ_INT2_MASK, }, .multi_read_bit = ST_ACCEL_1_MULTIREAD_BIT, .bootime = 2, @@ -285,7 +289,8 @@ static const struct st_sensors st_accel_sensors[] = { }, .drdy_irq = { .addr = ST_ACCEL_2_DRDY_IRQ_ADDR, - .mask = ST_ACCEL_2_DRDY_IRQ_MASK, + .mask_int1 = ST_ACCEL_2_DRDY_IRQ_INT1_MASK, + .mask_int2 = ST_ACCEL_2_DRDY_IRQ_INT2_MASK, }, .multi_read_bit = ST_ACCEL_2_MULTIREAD_BIT, .bootime = 2, @@ -358,7 +363,8 @@ static const struct st_sensors st_accel_sensors[] = { }, .drdy_irq = { .addr = ST_ACCEL_3_DRDY_IRQ_ADDR, - .mask = ST_ACCEL_3_DRDY_IRQ_MASK, + .mask_int1 = ST_ACCEL_3_DRDY_IRQ_INT1_MASK, + .mask_int2 = ST_ACCEL_3_DRDY_IRQ_INT2_MASK, .ig1 = { .en_addr = ST_ACCEL_3_IG1_EN_ADDR, .en_mask = ST_ACCEL_3_IG1_EN_MASK, @@ -443,7 +449,8 @@ static const struct iio_trigger_ops st_accel_trigger_ops = { #define ST_ACCEL_TRIGGER_OPS NULL #endif -int st_accel_common_probe(struct iio_dev *indio_dev) +int st_accel_common_probe(struct iio_dev *indio_dev, + struct st_sensors_platform_data *plat_data) { int err; struct st_sensor_data *adata = iio_priv(indio_dev); @@ -465,7 +472,11 @@ int st_accel_common_probe(struct iio_dev *indio_dev) &adata->sensor->fs.fs_avl[0]; adata->odr = adata->sensor->odr.odr_avl[0].hz; - err = st_sensors_init_sensor(indio_dev); + if (!plat_data) + plat_data = + (struct st_sensors_platform_data *)&default_accel_pdata; + + err = st_sensors_init_sensor(indio_dev, plat_data); if (err < 0) goto st_accel_common_probe_error; diff --git a/drivers/iio/accel/st_accel_i2c.c b/drivers/iio/accel/st_accel_i2c.c index ffc9d097e484..58d164d90dca 100644 --- a/drivers/iio/accel/st_accel_i2c.c +++ b/drivers/iio/accel/st_accel_i2c.c @@ -36,7 +36,7 @@ static int st_accel_i2c_probe(struct i2c_client *client, st_sensors_i2c_configure(indio_dev, client, adata); - err = st_accel_common_probe(indio_dev); + err = st_accel_common_probe(indio_dev, client->dev.platform_data); if (err < 0) goto st_accel_common_probe_error; diff --git a/drivers/iio/accel/st_accel_spi.c b/drivers/iio/accel/st_accel_spi.c index 22b35bfea7d2..21ed9296311f 100644 --- a/drivers/iio/accel/st_accel_spi.c +++ b/drivers/iio/accel/st_accel_spi.c @@ -35,7 +35,7 @@ static int st_accel_spi_probe(struct spi_device *spi) st_sensors_spi_configure(indio_dev, spi, adata); - err = st_accel_common_probe(indio_dev); + err = st_accel_common_probe(indio_dev, spi->dev.platform_data); if (err < 0) goto st_accel_common_probe_error; diff --git a/drivers/iio/common/st_sensors/st_sensors_core.c b/drivers/iio/common/st_sensors/st_sensors_core.c index 865b1781df66..965ee22d3ac8 100644 --- a/drivers/iio/common/st_sensors/st_sensors_core.c +++ b/drivers/iio/common/st_sensors/st_sensors_core.c @@ -22,7 +22,7 @@ static inline u32 st_sensors_get_unaligned_le24(const u8 *p) { - return ((s32)((p[0] | p[1] << 8 | p[2] << 16) << 8) >> 8); + return (s32)((p[0] | p[1] << 8 | p[2] << 16) << 8) >> 8; } static int st_sensors_write_data_with_mask(struct iio_dev *indio_dev, @@ -118,7 +118,7 @@ st_sensors_match_odr_error: } static int st_sensors_set_fullscale(struct iio_dev *indio_dev, - unsigned int fs) + unsigned int fs) { int err, i = 0; struct st_sensor_data *sdata = iio_priv(indio_dev); @@ -198,13 +198,39 @@ int st_sensors_set_axis_enable(struct iio_dev *indio_dev, u8 axis_enable) } EXPORT_SYMBOL(st_sensors_set_axis_enable); -int st_sensors_init_sensor(struct iio_dev *indio_dev) +int st_sensors_init_sensor(struct iio_dev *indio_dev, + struct st_sensors_platform_data *pdata) { int err; struct st_sensor_data *sdata = iio_priv(indio_dev); mutex_init(&sdata->tb.buf_lock); + switch (pdata->drdy_int_pin) { + case 1: + if (sdata->sensor->drdy_irq.mask_int1 == 0) { + dev_err(&indio_dev->dev, + "DRDY on INT1 not available.\n"); + err = -EINVAL; + goto init_error; + } + sdata->drdy_int_pin = 1; + break; + case 2: + if (sdata->sensor->drdy_irq.mask_int2 == 0) { + dev_err(&indio_dev->dev, + "DRDY on INT2 not available.\n"); + err = -EINVAL; + goto init_error; + } + sdata->drdy_int_pin = 2; + break; + default: + dev_err(&indio_dev->dev, "DRDY on pdata not valid.\n"); + err = -EINVAL; + goto init_error; + } + err = st_sensors_set_enable(indio_dev, false); if (err < 0) goto init_error; @@ -234,6 +260,7 @@ EXPORT_SYMBOL(st_sensors_init_sensor); int st_sensors_set_dataready_irq(struct iio_dev *indio_dev, bool enable) { int err; + u8 drdy_mask; struct st_sensor_data *sdata = iio_priv(indio_dev); /* Enable/Disable the interrupt generator 1. */ @@ -245,10 +272,14 @@ int st_sensors_set_dataready_irq(struct iio_dev *indio_dev, bool enable) goto st_accel_set_dataready_irq_error; } + if (sdata->drdy_int_pin == 1) + drdy_mask = sdata->sensor->drdy_irq.mask_int1; + else + drdy_mask = sdata->sensor->drdy_irq.mask_int2; + /* Enable/Disable the interrupt generator for data ready. */ err = st_sensors_write_data_with_mask(indio_dev, - sdata->sensor->drdy_irq.addr, - sdata->sensor->drdy_irq.mask, (int)enable); + sdata->sensor->drdy_irq.addr, drdy_mask, (int)enable); st_accel_set_dataready_irq_error: return err; diff --git a/drivers/iio/gyro/st_gyro.h b/drivers/iio/gyro/st_gyro.h index 3ad9907bb154..f8f2bf84a5a2 100644 --- a/drivers/iio/gyro/st_gyro.h +++ b/drivers/iio/gyro/st_gyro.h @@ -23,7 +23,16 @@ #define L3G4IS_GYRO_DEV_NAME "l3g4is_ui" #define LSM330_GYRO_DEV_NAME "lsm330_gyro" -int st_gyro_common_probe(struct iio_dev *indio_dev); +/** + * struct st_sensors_platform_data - gyro platform data + * @drdy_int_pin: DRDY on gyros is available only on INT2 pin. + */ +static const struct st_sensors_platform_data gyro_pdata = { + .drdy_int_pin = 2, +}; + +int st_gyro_common_probe(struct iio_dev *indio_dev, + struct st_sensors_platform_data *pdata); void st_gyro_common_remove(struct iio_dev *indio_dev); #ifdef CONFIG_IIO_BUFFER diff --git a/drivers/iio/gyro/st_gyro_core.c b/drivers/iio/gyro/st_gyro_core.c index f9ed3488c314..85fa8d343bb2 100644 --- a/drivers/iio/gyro/st_gyro_core.c +++ b/drivers/iio/gyro/st_gyro_core.c @@ -60,7 +60,7 @@ #define ST_GYRO_1_BDU_ADDR 0x23 #define ST_GYRO_1_BDU_MASK 0x80 #define ST_GYRO_1_DRDY_IRQ_ADDR 0x22 -#define ST_GYRO_1_DRDY_IRQ_MASK 0x08 +#define ST_GYRO_1_DRDY_IRQ_INT2_MASK 0x08 #define ST_GYRO_1_MULTIREAD_BIT true /* CUSTOM VALUES FOR SENSOR 2 */ @@ -84,7 +84,7 @@ #define ST_GYRO_2_BDU_ADDR 0x23 #define ST_GYRO_2_BDU_MASK 0x80 #define ST_GYRO_2_DRDY_IRQ_ADDR 0x22 -#define ST_GYRO_2_DRDY_IRQ_MASK 0x08 +#define ST_GYRO_2_DRDY_IRQ_INT2_MASK 0x08 #define ST_GYRO_2_MULTIREAD_BIT true static const struct iio_chan_spec st_gyro_16bit_channels[] = { @@ -158,7 +158,7 @@ static const struct st_sensors st_gyro_sensors[] = { }, .drdy_irq = { .addr = ST_GYRO_1_DRDY_IRQ_ADDR, - .mask = ST_GYRO_1_DRDY_IRQ_MASK, + .mask_int2 = ST_GYRO_1_DRDY_IRQ_INT2_MASK, }, .multi_read_bit = ST_GYRO_1_MULTIREAD_BIT, .bootime = 2, @@ -221,7 +221,7 @@ static const struct st_sensors st_gyro_sensors[] = { }, .drdy_irq = { .addr = ST_GYRO_2_DRDY_IRQ_ADDR, - .mask = ST_GYRO_2_DRDY_IRQ_MASK, + .mask_int2 = ST_GYRO_2_DRDY_IRQ_INT2_MASK, }, .multi_read_bit = ST_GYRO_2_MULTIREAD_BIT, .bootime = 2, @@ -302,7 +302,8 @@ static const struct iio_trigger_ops st_gyro_trigger_ops = { #define ST_GYRO_TRIGGER_OPS NULL #endif -int st_gyro_common_probe(struct iio_dev *indio_dev) +int st_gyro_common_probe(struct iio_dev *indio_dev, + struct st_sensors_platform_data *pdata) { int err; struct st_sensor_data *gdata = iio_priv(indio_dev); @@ -324,7 +325,7 @@ int st_gyro_common_probe(struct iio_dev *indio_dev) &gdata->sensor->fs.fs_avl[0]; gdata->odr = gdata->sensor->odr.odr_avl[0].hz; - err = st_sensors_init_sensor(indio_dev); + err = st_sensors_init_sensor(indio_dev, pdata); if (err < 0) goto st_gyro_common_probe_error; diff --git a/drivers/iio/gyro/st_gyro_i2c.c b/drivers/iio/gyro/st_gyro_i2c.c index 8a310500573d..c7a29a4d7e82 100644 --- a/drivers/iio/gyro/st_gyro_i2c.c +++ b/drivers/iio/gyro/st_gyro_i2c.c @@ -36,7 +36,8 @@ static int st_gyro_i2c_probe(struct i2c_client *client, st_sensors_i2c_configure(indio_dev, client, gdata); - err = st_gyro_common_probe(indio_dev); + err = st_gyro_common_probe(indio_dev, + (struct st_sensors_platform_data *)&gyro_pdata); if (err < 0) goto st_gyro_common_probe_error; diff --git a/drivers/iio/gyro/st_gyro_spi.c b/drivers/iio/gyro/st_gyro_spi.c index f3540390eb22..14b0762847f5 100644 --- a/drivers/iio/gyro/st_gyro_spi.c +++ b/drivers/iio/gyro/st_gyro_spi.c @@ -35,7 +35,8 @@ static int st_gyro_spi_probe(struct spi_device *spi) st_sensors_spi_configure(indio_dev, spi, gdata); - err = st_gyro_common_probe(indio_dev); + err = st_gyro_common_probe(indio_dev, + (struct st_sensors_platform_data *)&gyro_pdata); if (err < 0) goto st_gyro_common_probe_error; diff --git a/drivers/iio/magnetometer/st_magn.h b/drivers/iio/magnetometer/st_magn.h index 7e81d00ef0c3..694e33e0fb72 100644 --- a/drivers/iio/magnetometer/st_magn.h +++ b/drivers/iio/magnetometer/st_magn.h @@ -18,7 +18,8 @@ #define LSM303DLM_MAGN_DEV_NAME "lsm303dlm_magn" #define LIS3MDL_MAGN_DEV_NAME "lis3mdl" -int st_magn_common_probe(struct iio_dev *indio_dev); +int st_magn_common_probe(struct iio_dev *indio_dev, + struct st_sensors_platform_data *pdata); void st_magn_common_remove(struct iio_dev *indio_dev); #ifdef CONFIG_IIO_BUFFER diff --git a/drivers/iio/magnetometer/st_magn_core.c b/drivers/iio/magnetometer/st_magn_core.c index ebfe8f11a0c2..7cd784f522e5 100644 --- a/drivers/iio/magnetometer/st_magn_core.c +++ b/drivers/iio/magnetometer/st_magn_core.c @@ -345,7 +345,8 @@ static const struct iio_info magn_info = { .write_raw = &st_magn_write_raw, }; -int st_magn_common_probe(struct iio_dev *indio_dev) +int st_magn_common_probe(struct iio_dev *indio_dev, + struct st_sensors_platform_data *pdata) { int err; struct st_sensor_data *mdata = iio_priv(indio_dev); @@ -367,7 +368,7 @@ int st_magn_common_probe(struct iio_dev *indio_dev) &mdata->sensor->fs.fs_avl[0]; mdata->odr = mdata->sensor->odr.odr_avl[0].hz; - err = st_sensors_init_sensor(indio_dev); + err = st_sensors_init_sensor(indio_dev, pdata); if (err < 0) goto st_magn_common_probe_error; diff --git a/drivers/iio/magnetometer/st_magn_i2c.c b/drivers/iio/magnetometer/st_magn_i2c.c index e6adc4a86425..1bed117714c5 100644 --- a/drivers/iio/magnetometer/st_magn_i2c.c +++ b/drivers/iio/magnetometer/st_magn_i2c.c @@ -36,7 +36,7 @@ static int st_magn_i2c_probe(struct i2c_client *client, st_sensors_i2c_configure(indio_dev, client, mdata); - err = st_magn_common_probe(indio_dev); + err = st_magn_common_probe(indio_dev, NULL); if (err < 0) goto st_magn_common_probe_error; diff --git a/drivers/iio/magnetometer/st_magn_spi.c b/drivers/iio/magnetometer/st_magn_spi.c index 51adb797cb7d..a2333a1e6061 100644 --- a/drivers/iio/magnetometer/st_magn_spi.c +++ b/drivers/iio/magnetometer/st_magn_spi.c @@ -35,7 +35,7 @@ static int st_magn_spi_probe(struct spi_device *spi) st_sensors_spi_configure(indio_dev, spi, mdata); - err = st_magn_common_probe(indio_dev); + err = st_magn_common_probe(indio_dev, NULL); if (err < 0) goto st_magn_common_probe_error; diff --git a/drivers/iio/pressure/st_pressure.h b/drivers/iio/pressure/st_pressure.h index 414e45ac9b9b..b0b630688da6 100644 --- a/drivers/iio/pressure/st_pressure.h +++ b/drivers/iio/pressure/st_pressure.h @@ -16,7 +16,16 @@ #define LPS331AP_PRESS_DEV_NAME "lps331ap" -int st_press_common_probe(struct iio_dev *indio_dev); +/** + * struct st_sensors_platform_data - default press platform data + * @drdy_int_pin: default press DRDY is available on INT1 pin. + */ +static const struct st_sensors_platform_data default_press_pdata = { + .drdy_int_pin = 1, +}; + +int st_press_common_probe(struct iio_dev *indio_dev, + struct st_sensors_platform_data *pdata); void st_press_common_remove(struct iio_dev *indio_dev); #ifdef CONFIG_IIO_BUFFER diff --git a/drivers/iio/pressure/st_pressure_core.c b/drivers/iio/pressure/st_pressure_core.c index 3ffbc56917b4..81e2d5b030a6 100644 --- a/drivers/iio/pressure/st_pressure_core.c +++ b/drivers/iio/pressure/st_pressure_core.c @@ -58,7 +58,8 @@ #define ST_PRESS_1_BDU_ADDR 0x20 #define ST_PRESS_1_BDU_MASK 0x04 #define ST_PRESS_1_DRDY_IRQ_ADDR 0x22 -#define ST_PRESS_1_DRDY_IRQ_MASK 0x04 +#define ST_PRESS_1_DRDY_IRQ_INT1_MASK 0x04 +#define ST_PRESS_1_DRDY_IRQ_INT2_MASK 0x20 #define ST_PRESS_1_MULTIREAD_BIT true #define ST_PRESS_1_TEMP_OFFSET 42500 @@ -116,7 +117,8 @@ static const struct st_sensors st_press_sensors[] = { }, .drdy_irq = { .addr = ST_PRESS_1_DRDY_IRQ_ADDR, - .mask = ST_PRESS_1_DRDY_IRQ_MASK, + .mask_int1 = ST_PRESS_1_DRDY_IRQ_INT1_MASK, + .mask_int2 = ST_PRESS_1_DRDY_IRQ_INT2_MASK, }, .multi_read_bit = ST_PRESS_1_MULTIREAD_BIT, .bootime = 2, @@ -202,7 +204,8 @@ static const struct iio_trigger_ops st_press_trigger_ops = { #define ST_PRESS_TRIGGER_OPS NULL #endif -int st_press_common_probe(struct iio_dev *indio_dev) +int st_press_common_probe(struct iio_dev *indio_dev, + struct st_sensors_platform_data *plat_data) { int err; struct st_sensor_data *pdata = iio_priv(indio_dev); @@ -224,7 +227,11 @@ int st_press_common_probe(struct iio_dev *indio_dev) &pdata->sensor->fs.fs_avl[0]; pdata->odr = pdata->sensor->odr.odr_avl[0].hz; - err = st_sensors_init_sensor(indio_dev); + if (!plat_data) + plat_data = + (struct st_sensors_platform_data *)&default_press_pdata; + + err = st_sensors_init_sensor(indio_dev, plat_data); if (err < 0) goto st_press_common_probe_error; diff --git a/drivers/iio/pressure/st_pressure_i2c.c b/drivers/iio/pressure/st_pressure_i2c.c index 7cebcc73bfb0..306599307a96 100644 --- a/drivers/iio/pressure/st_pressure_i2c.c +++ b/drivers/iio/pressure/st_pressure_i2c.c @@ -36,7 +36,7 @@ static int st_press_i2c_probe(struct i2c_client *client, st_sensors_i2c_configure(indio_dev, client, pdata); - err = st_press_common_probe(indio_dev); + err = st_press_common_probe(indio_dev, client->dev.platform_data); if (err < 0) goto st_press_common_probe_error; diff --git a/drivers/iio/pressure/st_pressure_spi.c b/drivers/iio/pressure/st_pressure_spi.c index 17a14907940a..b2aded6d2108 100644 --- a/drivers/iio/pressure/st_pressure_spi.c +++ b/drivers/iio/pressure/st_pressure_spi.c @@ -35,7 +35,7 @@ static int st_press_spi_probe(struct spi_device *spi) st_sensors_spi_configure(indio_dev, spi, pdata); - err = st_press_common_probe(indio_dev); + err = st_press_common_probe(indio_dev, spi->dev.platform_data); if (err < 0) goto st_press_common_probe_error; diff --git a/include/linux/iio/common/st_sensors.h b/include/linux/iio/common/st_sensors.h index 72b26940730d..e51f65480ea5 100644 --- a/include/linux/iio/common/st_sensors.h +++ b/include/linux/iio/common/st_sensors.h @@ -17,6 +17,8 @@ #include #include +#include + #define ST_SENSORS_TX_MAX_LENGTH 2 #define ST_SENSORS_RX_MAX_LENGTH 6 @@ -118,14 +120,16 @@ struct st_sensor_bdu { /** * struct st_sensor_data_ready_irq - ST sensor device data-ready interrupt * @addr: address of the register. - * @mask: mask to write the on/off value. + * @mask_int1: mask to enable/disable IRQ on INT1 pin. + * @mask_int2: mask to enable/disable IRQ on INT2 pin. * struct ig1 - represents the Interrupt Generator 1 of sensors. * @en_addr: address of the enable ig1 register. * @en_mask: mask to write the on/off value for enable. */ struct st_sensor_data_ready_irq { u8 addr; - u8 mask; + u8 mask_int1; + u8 mask_int2; struct { u8 en_addr; u8 en_mask; @@ -201,6 +205,7 @@ struct st_sensors { * @buffer_data: Data used by buffer part. * @odr: Output data rate of the sensor [Hz]. * num_data_channels: Number of data channels used in buffer. + * @drdy_int_pin: Redirect DRDY on pin 1 (1) or pin 2 (2). * @get_irq_data_ready: Function to get the IRQ used for data ready signal. * @tf: Transfer function structure used by I/O operations. * @tb: Transfer buffers and mutex used by I/O operations. @@ -219,6 +224,8 @@ struct st_sensor_data { unsigned int odr; unsigned int num_data_channels; + u8 drdy_int_pin; + unsigned int (*get_irq_data_ready) (struct iio_dev *indio_dev); const struct st_sensor_transfer_function *tf; @@ -249,7 +256,8 @@ static inline void st_sensors_deallocate_trigger(struct iio_dev *indio_dev) } #endif -int st_sensors_init_sensor(struct iio_dev *indio_dev); +int st_sensors_init_sensor(struct iio_dev *indio_dev, + struct st_sensors_platform_data *pdata); int st_sensors_set_enable(struct iio_dev *indio_dev, bool enable); diff --git a/include/linux/platform_data/st_sensors_pdata.h b/include/linux/platform_data/st_sensors_pdata.h new file mode 100644 index 000000000000..753839187ba0 --- /dev/null +++ b/include/linux/platform_data/st_sensors_pdata.h @@ -0,0 +1,24 @@ +/* + * STMicroelectronics sensors platform-data driver + * + * Copyright 2013 STMicroelectronics Inc. + * + * Denis Ciocca + * + * Licensed under the GPL-2. + */ + +#ifndef ST_SENSORS_PDATA_H +#define ST_SENSORS_PDATA_H + +/** + * struct st_sensors_platform_data - Platform data for the ST sensors + * @drdy_int_pin: Redirect DRDY on pin 1 (1) or pin 2 (2). + * Available only for accelerometer and pressure sensors. + * Accelerometer DRDY on LSM330 available only on pin 1 (see datasheet). + */ +struct st_sensors_platform_data { + u8 drdy_int_pin; +}; + +#endif /* ST_SENSORS_PDATA_H */ -- cgit v1.2.3-71-gd317 From 71b94e2e866aa35f40945d9e820fc3214b792d1f Mon Sep 17 00:00:00 2001 From: Josh Wu Date: Thu, 9 May 2013 15:34:54 +0800 Subject: mtd: atmel_nand: replace cpu_is_at32ap7000() with a nand platform data The nand driver use cpu_is_at32ap7000() macro for a workaround. For the multi-platform support, we will remove this cpu_is_xxx() macro. This patch adds a boolean variable need_reset_workaround in structure atmel_nand_data. Using this variable we can remove cpu_is_at32ap7000() macro. Hans-Christian: Feel free to push this through the mtd tree, if they won't accept it I'm working on getting my workflow up on the linux-avr32.git tree. Signed-off-by: Josh Wu Acked-by: Hans-Christian Egtvedt Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse --- arch/avr32/mach-at32ap/at32ap700x.c | 3 +++ drivers/mtd/nand/atmel_nand.c | 13 ++++++------- include/linux/platform_data/atmel.h | 3 +++ 3 files changed, 12 insertions(+), 7 deletions(-) (limited to 'include/linux/platform_data') diff --git a/arch/avr32/mach-at32ap/at32ap700x.c b/arch/avr32/mach-at32ap/at32ap700x.c index 7f8759a8a92a..a68f3cf7c3c1 100644 --- a/arch/avr32/mach-at32ap/at32ap700x.c +++ b/arch/avr32/mach-at32ap/at32ap700x.c @@ -1983,6 +1983,9 @@ at32_add_device_nand(unsigned int id, struct atmel_nand_data *data) ARRAY_SIZE(smc_cs3_resource))) goto fail; + /* For at32ap7000, we use the reset workaround for nand driver */ + data->need_reset_workaround = true; + if (platform_device_add_data(pdev, data, sizeof(struct atmel_nand_data))) goto fail; diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c index 2d23d2929438..7bf912b5b969 100644 --- a/drivers/mtd/nand/atmel_nand.c +++ b/drivers/mtd/nand/atmel_nand.c @@ -1174,10 +1174,9 @@ static int atmel_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip, * Workaround: Reset the parity registers before reading the * actual data. */ - if (cpu_is_at32ap7000()) { - struct atmel_nand_host *host = chip->priv; + struct atmel_nand_host *host = chip->priv; + if (host->board.need_reset_workaround) ecc_writel(host->ecc, CR, ATMEL_ECC_RST); - } /* read the page */ chip->read_buf(mtd, p, eccsize); @@ -1298,11 +1297,11 @@ static int atmel_nand_correct(struct mtd_info *mtd, u_char *dat, */ static void atmel_nand_hwctl(struct mtd_info *mtd, int mode) { - if (cpu_is_at32ap7000()) { - struct nand_chip *nand_chip = mtd->priv; - struct atmel_nand_host *host = nand_chip->priv; + struct nand_chip *nand_chip = mtd->priv; + struct atmel_nand_host *host = nand_chip->priv; + + if (host->board.need_reset_workaround) ecc_writel(host->ecc, CR, ATMEL_ECC_RST); - } } #if defined(CONFIG_OF) diff --git a/include/linux/platform_data/atmel.h b/include/linux/platform_data/atmel.h index 6a293b7fff3b..59f558d9b81e 100644 --- a/include/linux/platform_data/atmel.h +++ b/include/linux/platform_data/atmel.h @@ -71,6 +71,9 @@ struct atmel_nand_data { u8 on_flash_bbt; /* bbt on flash */ struct mtd_partition *parts; unsigned int num_parts; + + /* default is false, only for at32ap7000 chip is true */ + bool need_reset_workaround; }; /* Serial */ -- cgit v1.2.3-71-gd317 From 1b7192658a08f70df0f290634fd7cd2ecb629fc9 Mon Sep 17 00:00:00 2001 From: Josh Wu Date: Thu, 9 May 2013 15:34:55 +0800 Subject: mtd: atmel_nand: add a new dt binding item for nand dma support This patch will set the nand dma support in dts. Since we will not use cpu_is_xxx() in nand driver. We needn't include the mach/cpu.h any more. Signed-off-by: Josh Wu Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse --- Documentation/devicetree/bindings/mtd/atmel-nand.txt | 1 + drivers/mtd/nand/atmel_nand.c | 11 +++-------- include/linux/platform_data/atmel.h | 1 + 3 files changed, 5 insertions(+), 8 deletions(-) (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/mtd/atmel-nand.txt b/Documentation/devicetree/bindings/mtd/atmel-nand.txt index d555421ea49f..b6eb484366a5 100644 --- a/Documentation/devicetree/bindings/mtd/atmel-nand.txt +++ b/Documentation/devicetree/bindings/mtd/atmel-nand.txt @@ -15,6 +15,7 @@ Required properties: optional gpio and may be set to 0 if not present. Optional properties: +- atmel,nand-has-dma : boolean to support dma transfer for nand read/write. - nand-ecc-mode : String, operation mode of the NAND ecc mode, soft by default. Supported values are: "none", "soft", "hw", "hw_syndrome", "hw_oob_first", "soft_bch". diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c index 7bf912b5b969..61d38697986e 100644 --- a/drivers/mtd/nand/atmel_nand.c +++ b/drivers/mtd/nand/atmel_nand.c @@ -43,8 +43,6 @@ #include #include -#include - static int use_dma = 1; module_param(use_dma, int, 0); @@ -128,11 +126,6 @@ struct atmel_nand_host { static struct nand_ecclayout atmel_pmecc_oobinfo; -static int cpu_has_dma(void) -{ - return cpu_is_at91sam9rl() || cpu_is_at91sam9g45(); -} - /* * Enable NAND. */ @@ -1336,6 +1329,8 @@ static int atmel_of_init_port(struct atmel_nand_host *host, board->on_flash_bbt = of_get_nand_on_flash_bbt(np); + board->has_dma = of_property_read_bool(np, "atmel,nand-has-dma"); + if (of_get_nand_bus_width(np) == 16) board->bus_width_16 = 1; @@ -1600,7 +1595,7 @@ static int __init atmel_nand_probe(struct platform_device *pdev) nand_chip->bbt_options |= NAND_BBT_USE_FLASH; } - if (!cpu_has_dma()) + if (!host->board.has_dma) use_dma = 0; if (use_dma) { diff --git a/include/linux/platform_data/atmel.h b/include/linux/platform_data/atmel.h index 59f558d9b81e..cea9f70133c5 100644 --- a/include/linux/platform_data/atmel.h +++ b/include/linux/platform_data/atmel.h @@ -71,6 +71,7 @@ struct atmel_nand_data { u8 on_flash_bbt; /* bbt on flash */ struct mtd_partition *parts; unsigned int num_parts; + bool has_dma; /* support dma transfer */ /* default is false, only for at32ap7000 chip is true */ bool need_reset_workaround; -- cgit v1.2.3-71-gd317 From bf4dae5ce1b95a5932e43036edcf3f1b324758c6 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 7 Aug 2013 22:21:57 +0200 Subject: pinctrl: nomadik: delete ancient pin control API The pin control subsystem was created to do away with custom pin control APIs such as this one. It was kept for backward-compatibility but is completely unused in the current kernel, so let's delete it. Signed-off-by: Linus Walleij --- drivers/pinctrl/pinctrl-nomadik.c | 295 -------------------------- include/linux/platform_data/pinctrl-nomadik.h | 24 --- 2 files changed, 319 deletions(-) (limited to 'include/linux/platform_data') diff --git a/drivers/pinctrl/pinctrl-nomadik.c b/drivers/pinctrl/pinctrl-nomadik.c index 4a1cfdce2232..89280bc8123c 100644 --- a/drivers/pinctrl/pinctrl-nomadik.c +++ b/drivers/pinctrl/pinctrl-nomadik.c @@ -337,97 +337,6 @@ static void nmk_prcm_altcx_set_mode(struct nmk_pinctrl *npct, nmk_write_masked(npct->prcm_base + reg, BIT(bit), BIT(bit)); } -static void __nmk_config_pin(struct nmk_gpio_chip *nmk_chip, unsigned offset, - pin_cfg_t cfg, bool sleep, unsigned int *slpmregs) -{ - static const char *afnames[] = { - [NMK_GPIO_ALT_GPIO] = "GPIO", - [NMK_GPIO_ALT_A] = "A", - [NMK_GPIO_ALT_B] = "B", - [NMK_GPIO_ALT_C] = "C" - }; - static const char *pullnames[] = { - [NMK_GPIO_PULL_NONE] = "none", - [NMK_GPIO_PULL_UP] = "up", - [NMK_GPIO_PULL_DOWN] = "down", - [3] /* illegal */ = "??" - }; - static const char *slpmnames[] = { - [NMK_GPIO_SLPM_INPUT] = "input/wakeup", - [NMK_GPIO_SLPM_NOCHANGE] = "no-change/no-wakeup", - }; - - int pin = PIN_NUM(cfg); - int pull = PIN_PULL(cfg); - int af = PIN_ALT(cfg); - int slpm = PIN_SLPM(cfg); - int output = PIN_DIR(cfg); - int val = PIN_VAL(cfg); - bool glitch = af == NMK_GPIO_ALT_C; - - dev_dbg(nmk_chip->chip.dev, "pin %d [%#lx]: af %s, pull %s, slpm %s (%s%s)\n", - pin, cfg, afnames[af], pullnames[pull], slpmnames[slpm], - output ? "output " : "input", - output ? (val ? "high" : "low") : ""); - - if (sleep) { - int slpm_pull = PIN_SLPM_PULL(cfg); - int slpm_output = PIN_SLPM_DIR(cfg); - int slpm_val = PIN_SLPM_VAL(cfg); - - af = NMK_GPIO_ALT_GPIO; - - /* - * The SLPM_* values are normal values + 1 to allow zero to - * mean "same as normal". - */ - if (slpm_pull) - pull = slpm_pull - 1; - if (slpm_output) - output = slpm_output - 1; - if (slpm_val) - val = slpm_val - 1; - - dev_dbg(nmk_chip->chip.dev, "pin %d: sleep pull %s, dir %s, val %s\n", - pin, - slpm_pull ? pullnames[pull] : "same", - slpm_output ? (output ? "output" : "input") : "same", - slpm_val ? (val ? "high" : "low") : "same"); - } - - if (output) - __nmk_gpio_make_output(nmk_chip, offset, val); - else { - __nmk_gpio_make_input(nmk_chip, offset); - __nmk_gpio_set_pull(nmk_chip, offset, pull); - } - - __nmk_gpio_set_lowemi(nmk_chip, offset, PIN_LOWEMI(cfg)); - - /* - * If the pin is switching to altfunc, and there was an interrupt - * installed on it which has been lazy disabled, actually mask the - * interrupt to prevent spurious interrupts that would occur while the - * pin is under control of the peripheral. Only SKE does this. - */ - if (af != NMK_GPIO_ALT_GPIO) - nmk_gpio_disable_lazy_irq(nmk_chip, offset); - - /* - * If we've backed up the SLPM registers (glitch workaround), modify - * the backups since they will be restored. - */ - if (slpmregs) { - if (slpm == NMK_GPIO_SLPM_NOCHANGE) - slpmregs[nmk_chip->bank] |= BIT(offset); - else - slpmregs[nmk_chip->bank] &= ~BIT(offset); - } else - __nmk_gpio_set_slpm(nmk_chip, offset, slpm); - - __nmk_gpio_set_mode_safe(nmk_chip, offset, af, glitch); -} - /* * Safe sequence used to switch IOs between GPIO and Alternate-C mode: * - Save SLPM registers @@ -474,210 +383,6 @@ static void nmk_gpio_glitch_slpm_restore(unsigned int *slpm) } } -static int __nmk_config_pins(pin_cfg_t *cfgs, int num, bool sleep) -{ - static unsigned int slpm[NUM_BANKS]; - unsigned long flags; - bool glitch = false; - int ret = 0; - int i; - - for (i = 0; i < num; i++) { - if (PIN_ALT(cfgs[i]) == NMK_GPIO_ALT_C) { - glitch = true; - break; - } - } - - spin_lock_irqsave(&nmk_gpio_slpm_lock, flags); - - if (glitch) { - memset(slpm, 0xff, sizeof(slpm)); - - for (i = 0; i < num; i++) { - int pin = PIN_NUM(cfgs[i]); - int offset = pin % NMK_GPIO_PER_CHIP; - - if (PIN_ALT(cfgs[i]) == NMK_GPIO_ALT_C) - slpm[pin / NMK_GPIO_PER_CHIP] &= ~BIT(offset); - } - - nmk_gpio_glitch_slpm_init(slpm); - } - - for (i = 0; i < num; i++) { - struct nmk_gpio_chip *nmk_chip; - int pin = PIN_NUM(cfgs[i]); - - nmk_chip = nmk_gpio_chips[pin / NMK_GPIO_PER_CHIP]; - if (!nmk_chip) { - ret = -EINVAL; - break; - } - - clk_enable(nmk_chip->clk); - spin_lock(&nmk_chip->lock); - __nmk_config_pin(nmk_chip, pin % NMK_GPIO_PER_CHIP, - cfgs[i], sleep, glitch ? slpm : NULL); - spin_unlock(&nmk_chip->lock); - clk_disable(nmk_chip->clk); - } - - if (glitch) - nmk_gpio_glitch_slpm_restore(slpm); - - spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags); - - return ret; -} - -/** - * nmk_config_pin - configure a pin's mux attributes - * @cfg: pin confguration - * @sleep: Non-zero to apply the sleep mode configuration - * Configures a pin's mode (alternate function or GPIO), its pull up status, - * and its sleep mode based on the specified configuration. The @cfg is - * usually one of the SoC specific macros defined in mach/-pins.h. These - * are constructed using, and can be further enhanced with, the macros in - * - * - * If a pin's mode is set to GPIO, it is configured as an input to avoid - * side-effects. The gpio can be manipulated later using standard GPIO API - * calls. - */ -int nmk_config_pin(pin_cfg_t cfg, bool sleep) -{ - return __nmk_config_pins(&cfg, 1, sleep); -} -EXPORT_SYMBOL(nmk_config_pin); - -/** - * nmk_config_pins - configure several pins at once - * @cfgs: array of pin configurations - * @num: number of elments in the array - * - * Configures several pins using nmk_config_pin(). Refer to that function for - * further information. - */ -int nmk_config_pins(pin_cfg_t *cfgs, int num) -{ - return __nmk_config_pins(cfgs, num, false); -} -EXPORT_SYMBOL(nmk_config_pins); - -int nmk_config_pins_sleep(pin_cfg_t *cfgs, int num) -{ - return __nmk_config_pins(cfgs, num, true); -} -EXPORT_SYMBOL(nmk_config_pins_sleep); - -/** - * nmk_gpio_set_slpm() - configure the sleep mode of a pin - * @gpio: pin number - * @mode: NMK_GPIO_SLPM_INPUT or NMK_GPIO_SLPM_NOCHANGE, - * - * This register is actually in the pinmux layer, not the GPIO block itself. - * The GPIO1B_SLPM register defines the GPIO mode when SLEEP/DEEP-SLEEP - * mode is entered (i.e. when signal IOFORCE is HIGH by the platform code). - * Each GPIO can be configured to be forced into GPIO mode when IOFORCE is - * HIGH, overriding the normal setting defined by GPIO_AFSELx registers. - * When IOFORCE returns LOW (by software, after SLEEP/DEEP-SLEEP exit), - * the GPIOs return to the normal setting defined by GPIO_AFSELx registers. - * - * If @mode is NMK_GPIO_SLPM_INPUT, the corresponding GPIO is switched to GPIO - * mode when signal IOFORCE is HIGH (i.e. when SLEEP/DEEP-SLEEP mode is - * entered) regardless of the altfunction selected. Also wake-up detection is - * ENABLED. - * - * If @mode is NMK_GPIO_SLPM_NOCHANGE, the corresponding GPIO remains - * controlled by NMK_GPIO_DATC, NMK_GPIO_DATS, NMK_GPIO_DIR, NMK_GPIO_PDIS - * (for altfunction GPIO) or respective on-chip peripherals (for other - * altfuncs) when IOFORCE is HIGH. Also wake-up detection DISABLED. - * - * Note that enable_irq_wake() will automatically enable wakeup detection. - */ -int nmk_gpio_set_slpm(int gpio, enum nmk_gpio_slpm mode) -{ - struct nmk_gpio_chip *nmk_chip; - unsigned long flags; - - nmk_chip = nmk_gpio_chips[gpio / NMK_GPIO_PER_CHIP]; - if (!nmk_chip) - return -EINVAL; - - clk_enable(nmk_chip->clk); - spin_lock_irqsave(&nmk_gpio_slpm_lock, flags); - spin_lock(&nmk_chip->lock); - - __nmk_gpio_set_slpm(nmk_chip, gpio % NMK_GPIO_PER_CHIP, mode); - - spin_unlock(&nmk_chip->lock); - spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags); - clk_disable(nmk_chip->clk); - - return 0; -} - -/** - * nmk_gpio_set_pull() - enable/disable pull up/down on a gpio - * @gpio: pin number - * @pull: one of NMK_GPIO_PULL_DOWN, NMK_GPIO_PULL_UP, and NMK_GPIO_PULL_NONE - * - * Enables/disables pull up/down on a specified pin. This only takes effect if - * the pin is configured as an input (either explicitly or by the alternate - * function). - * - * NOTE: If enabling the pull up/down, the caller must ensure that the GPIO is - * configured as an input. Otherwise, due to the way the controller registers - * work, this function will change the value output on the pin. - */ -int nmk_gpio_set_pull(int gpio, enum nmk_gpio_pull pull) -{ - struct nmk_gpio_chip *nmk_chip; - unsigned long flags; - - nmk_chip = nmk_gpio_chips[gpio / NMK_GPIO_PER_CHIP]; - if (!nmk_chip) - return -EINVAL; - - clk_enable(nmk_chip->clk); - spin_lock_irqsave(&nmk_chip->lock, flags); - __nmk_gpio_set_pull(nmk_chip, gpio % NMK_GPIO_PER_CHIP, pull); - spin_unlock_irqrestore(&nmk_chip->lock, flags); - clk_disable(nmk_chip->clk); - - return 0; -} - -/* Mode functions */ -/** - * nmk_gpio_set_mode() - set the mux mode of a gpio pin - * @gpio: pin number - * @gpio_mode: one of NMK_GPIO_ALT_GPIO, NMK_GPIO_ALT_A, - * NMK_GPIO_ALT_B, and NMK_GPIO_ALT_C - * - * Sets the mode of the specified pin to one of the alternate functions or - * plain GPIO. - */ -int nmk_gpio_set_mode(int gpio, int gpio_mode) -{ - struct nmk_gpio_chip *nmk_chip; - unsigned long flags; - - nmk_chip = nmk_gpio_chips[gpio / NMK_GPIO_PER_CHIP]; - if (!nmk_chip) - return -EINVAL; - - clk_enable(nmk_chip->clk); - spin_lock_irqsave(&nmk_chip->lock, flags); - __nmk_gpio_set_mode(nmk_chip, gpio % NMK_GPIO_PER_CHIP, gpio_mode); - spin_unlock_irqrestore(&nmk_chip->lock, flags); - clk_disable(nmk_chip->clk); - - return 0; -} -EXPORT_SYMBOL(nmk_gpio_set_mode); - static int __maybe_unused nmk_prcm_gpiocr_get_mode(struct pinctrl_dev *pctldev, int gpio) { int i; diff --git a/include/linux/platform_data/pinctrl-nomadik.h b/include/linux/platform_data/pinctrl-nomadik.h index f73b2f0c55b7..abf5bed84df3 100644 --- a/include/linux/platform_data/pinctrl-nomadik.h +++ b/include/linux/platform_data/pinctrl-nomadik.h @@ -226,30 +226,6 @@ enum nmk_gpio_slpm { NMK_GPIO_SLPM_WAKEUP_DISABLE = NMK_GPIO_SLPM_NOCHANGE, }; -/* Older deprecated pin config API that should go away soon */ -extern int nmk_config_pin(pin_cfg_t cfg, bool sleep); -extern int nmk_config_pins(pin_cfg_t *cfgs, int num); -extern int nmk_config_pins_sleep(pin_cfg_t *cfgs, int num); -extern int nmk_gpio_set_slpm(int gpio, enum nmk_gpio_slpm mode); -extern int nmk_gpio_set_pull(int gpio, enum nmk_gpio_pull pull); -#ifdef CONFIG_PINCTRL_NOMADIK -extern int nmk_gpio_set_mode(int gpio, int gpio_mode); -#else -static inline int nmk_gpio_set_mode(int gpio, int gpio_mode) -{ - return -ENODEV; -} -#endif -extern int nmk_gpio_get_mode(int gpio); - -extern void nmk_gpio_wakeups_suspend(void); -extern void nmk_gpio_wakeups_resume(void); - -extern void nmk_gpio_clocks_enable(void); -extern void nmk_gpio_clocks_disable(void); - -extern void nmk_gpio_read_pull(int gpio_bank, u32 *pull_up); - /* * Platform data to register a block: only the initial gpio/irq number. */ -- cgit v1.2.3-71-gd317 From 86f8973c1053cb03e1b1b45989a4e144e05b1735 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Thu, 8 Aug 2013 16:09:50 +0200 Subject: spi: new controller driver for efm32 SoCs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Uwe Kleine-König Signed-off-by: Mark Brown --- .../devicetree/bindings/spi/efm32-spi.txt | 34 ++ drivers/spi/Kconfig | 7 + drivers/spi/Makefile | 1 + drivers/spi/spi-efm32.c | 517 +++++++++++++++++++++ include/linux/platform_data/efm32-spi.h | 14 + 5 files changed, 573 insertions(+) create mode 100644 Documentation/devicetree/bindings/spi/efm32-spi.txt create mode 100644 drivers/spi/spi-efm32.c create mode 100644 include/linux/platform_data/efm32-spi.h (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/spi/efm32-spi.txt b/Documentation/devicetree/bindings/spi/efm32-spi.txt new file mode 100644 index 000000000000..a590ca51be75 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/efm32-spi.txt @@ -0,0 +1,34 @@ +* Energy Micro EFM32 SPI + +Required properties: +- #address-cells: see spi-bus.txt +- #size-cells: see spi-bus.txt +- compatible: should be "efm32,spi" +- reg: Offset and length of the register set for the controller +- interrupts: pair specifying rx and tx irq +- clocks: phandle to the spi clock +- cs-gpios: see spi-bus.txt +- location: Value to write to the ROUTE register's LOCATION bitfield to configure the pinmux for the device, see datasheet for values. + +Example: + +spi1: spi@0x4000c400 { /* USART1 */ + #address-cells = <1>; + #size-cells = <0>; + compatible = "efm32,spi"; + reg = <0x4000c400 0x400>; + interrupts = <15 16>; + clocks = <&cmu 20>; + cs-gpios = <&gpio 51 1>; // D3 + location = <1>; + status = "ok"; + + ks8851@0 { + compatible = "ks8851"; + spi-max-frequency = <6000000>; + reg = <0>; + interrupt-parent = <&boardfpga>; + interrupts = <4>; + status = "ok"; + }; +}; diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 89cbbabaff44..f84a77c816bc 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -157,6 +157,13 @@ config SPI_DAVINCI help SPI master controller for DaVinci/DA8x/OMAP-L/AM1x SPI modules. +config SPI_EFM32 + tristate "EFM32 SPI controller" + depends on OF && ARM && (ARCH_EFM32 || COMPILE_TEST) + select SPI_BITBANG + help + Driver for the spi controller found on Energy Micro's EFM32 SoCs. + config SPI_EP93XX tristate "Cirrus Logic EP93xx SPI controller" depends on ARCH_EP93XX diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 33f9c09561e7..4a1cfb247292 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_SPI_DESIGNWARE) += spi-dw.o obj-$(CONFIG_SPI_DW_MMIO) += spi-dw-mmio.o obj-$(CONFIG_SPI_DW_PCI) += spi-dw-midpci.o spi-dw-midpci-objs := spi-dw-pci.o spi-dw-mid.o +obj-$(CONFIG_SPI_EFM32) += spi-efm32.o obj-$(CONFIG_SPI_EP93XX) += spi-ep93xx.o obj-$(CONFIG_SPI_FALCON) += spi-falcon.o obj-$(CONFIG_SPI_FSL_CPM) += spi-fsl-cpm.o diff --git a/drivers/spi/spi-efm32.c b/drivers/spi/spi-efm32.c new file mode 100644 index 000000000000..ba38452f646f --- /dev/null +++ b/drivers/spi/spi-efm32.c @@ -0,0 +1,517 @@ +/* + * Copyright (C) 2012-2013 Uwe Kleine-Koenig for Pengutronix + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "efm32-spi" + +#define MASK_VAL(mask, val) ((val << __ffs(mask)) & mask) + +#define REG_CTRL 0x00 +#define REG_CTRL_SYNC 0x0001 +#define REG_CTRL_CLKPOL 0x0100 +#define REG_CTRL_CLKPHA 0x0200 +#define REG_CTRL_MSBF 0x0400 +#define REG_CTRL_TXBIL 0x1000 + +#define REG_FRAME 0x04 +#define REG_FRAME_DATABITS__MASK 0x000f +#define REG_FRAME_DATABITS(n) ((n) - 3) + +#define REG_CMD 0x0c +#define REG_CMD_RXEN 0x0001 +#define REG_CMD_RXDIS 0x0002 +#define REG_CMD_TXEN 0x0004 +#define REG_CMD_TXDIS 0x0008 +#define REG_CMD_MASTEREN 0x0010 + +#define REG_STATUS 0x10 +#define REG_STATUS_TXENS 0x0002 +#define REG_STATUS_TXC 0x0020 +#define REG_STATUS_TXBL 0x0040 +#define REG_STATUS_RXDATAV 0x0080 + +#define REG_CLKDIV 0x14 + +#define REG_RXDATAX 0x18 +#define REG_RXDATAX_RXDATA__MASK 0x01ff +#define REG_RXDATAX_PERR 0x4000 +#define REG_RXDATAX_FERR 0x8000 + +#define REG_TXDATA 0x34 + +#define REG_IF 0x40 +#define REG_IF_TXBL 0x0002 +#define REG_IF_RXDATAV 0x0004 + +#define REG_IFS 0x44 +#define REG_IFC 0x48 +#define REG_IEN 0x4c + +#define REG_ROUTE 0x54 +#define REG_ROUTE_RXPEN 0x0001 +#define REG_ROUTE_TXPEN 0x0002 +#define REG_ROUTE_CLKPEN 0x0008 +#define REG_ROUTE_LOCATION__MASK 0x0700 +#define REG_ROUTE_LOCATION(n) MASK_VAL(REG_ROUTE_LOCATION__MASK, (n)) + +struct efm32_spi_ddata { + struct spi_bitbang bitbang; + + spinlock_t lock; + + struct clk *clk; + void __iomem *base; + unsigned int rxirq, txirq; + struct efm32_spi_pdata pdata; + + /* irq data */ + struct completion done; + const u8 *tx_buf; + u8 *rx_buf; + unsigned tx_len, rx_len; + + /* chip selects */ + unsigned csgpio[]; +}; + +#define ddata_to_dev(ddata) (&(ddata->bitbang.master->dev)) +#define efm32_spi_vdbg(ddata, format, arg...) \ + dev_vdbg(ddata_to_dev(ddata), format, ##arg) + +static void efm32_spi_write32(struct efm32_spi_ddata *ddata, + u32 value, unsigned offset) +{ + writel_relaxed(value, ddata->base + offset); +} + +static u32 efm32_spi_read32(struct efm32_spi_ddata *ddata, unsigned offset) +{ + return readl_relaxed(ddata->base + offset); +} + +static void efm32_spi_chipselect(struct spi_device *spi, int is_on) +{ + struct efm32_spi_ddata *ddata = spi_master_get_devdata(spi->master); + int value = !(spi->mode & SPI_CS_HIGH) == !(is_on == BITBANG_CS_ACTIVE); + + gpio_set_value(ddata->csgpio[spi->chip_select], value); +} + +static int efm32_spi_setup_transfer(struct spi_device *spi, + struct spi_transfer *t) +{ + struct efm32_spi_ddata *ddata = spi_master_get_devdata(spi->master); + + unsigned bpw = t->bits_per_word ?: spi->bits_per_word; + unsigned speed = t->speed_hz ?: spi->max_speed_hz; + unsigned long clkfreq = clk_get_rate(ddata->clk); + u32 clkdiv; + + efm32_spi_write32(ddata, REG_CTRL_SYNC | REG_CTRL_MSBF | + (spi->mode & SPI_CPHA ? REG_CTRL_CLKPHA : 0) | + (spi->mode & SPI_CPOL ? REG_CTRL_CLKPOL : 0), REG_CTRL); + + efm32_spi_write32(ddata, + REG_FRAME_DATABITS(bpw), REG_FRAME); + + if (2 * speed >= clkfreq) + clkdiv = 0; + else + clkdiv = 64 * (DIV_ROUND_UP(2 * clkfreq, speed) - 4); + + if (clkdiv > (1U << 21)) + return -EINVAL; + + efm32_spi_write32(ddata, clkdiv, REG_CLKDIV); + efm32_spi_write32(ddata, REG_CMD_MASTEREN, REG_CMD); + efm32_spi_write32(ddata, REG_CMD_RXEN | REG_CMD_TXEN, REG_CMD); + + return 0; +} + +static void efm32_spi_tx_u8(struct efm32_spi_ddata *ddata) +{ + u8 val = 0; + + if (ddata->tx_buf) { + val = *ddata->tx_buf; + ddata->tx_buf++; + } + + ddata->tx_len--; + efm32_spi_write32(ddata, val, REG_TXDATA); + efm32_spi_vdbg(ddata, "%s: tx 0x%x\n", __func__, val); +} + +static void efm32_spi_rx_u8(struct efm32_spi_ddata *ddata) +{ + u32 rxdata = efm32_spi_read32(ddata, REG_RXDATAX); + efm32_spi_vdbg(ddata, "%s: rx 0x%x\n", __func__, rxdata); + + if (ddata->rx_buf) { + *ddata->rx_buf = rxdata; + ddata->rx_buf++; + } + + ddata->rx_len--; +} + +static void efm32_spi_filltx(struct efm32_spi_ddata *ddata) +{ + while (ddata->tx_len && + ddata->tx_len + 2 > ddata->rx_len && + efm32_spi_read32(ddata, REG_STATUS) & REG_STATUS_TXBL) { + efm32_spi_tx_u8(ddata); + } +} + +static int efm32_spi_txrx_bufs(struct spi_device *spi, struct spi_transfer *t) +{ + struct efm32_spi_ddata *ddata = spi_master_get_devdata(spi->master); + int ret = -EBUSY; + + spin_lock_irq(&ddata->lock); + + if (ddata->tx_buf || ddata->rx_buf) + goto out_unlock; + + ddata->tx_buf = t->tx_buf; + ddata->rx_buf = t->rx_buf; + ddata->tx_len = ddata->rx_len = + t->len * DIV_ROUND_UP(t->bits_per_word, 8); + + efm32_spi_filltx(ddata); + + init_completion(&ddata->done); + + efm32_spi_write32(ddata, REG_IF_TXBL | REG_IF_RXDATAV, REG_IEN); + + spin_unlock_irq(&ddata->lock); + + wait_for_completion(&ddata->done); + + spin_lock_irq(&ddata->lock); + + ret = t->len - max(ddata->tx_len, ddata->rx_len); + + efm32_spi_write32(ddata, 0, REG_IEN); + ddata->tx_buf = ddata->rx_buf = NULL; + +out_unlock: + spin_unlock_irq(&ddata->lock); + + return ret; +} + +static irqreturn_t efm32_spi_rxirq(int irq, void *data) +{ + struct efm32_spi_ddata *ddata = data; + irqreturn_t ret = IRQ_NONE; + + spin_lock(&ddata->lock); + + while (ddata->rx_len > 0 && + efm32_spi_read32(ddata, REG_STATUS) & + REG_STATUS_RXDATAV) { + efm32_spi_rx_u8(ddata); + + ret = IRQ_HANDLED; + } + + if (!ddata->rx_len) { + u32 ien = efm32_spi_read32(ddata, REG_IEN); + + ien &= ~REG_IF_RXDATAV; + + efm32_spi_write32(ddata, ien, REG_IEN); + + complete(&ddata->done); + } + + spin_unlock(&ddata->lock); + + return ret; +} + +static irqreturn_t efm32_spi_txirq(int irq, void *data) +{ + struct efm32_spi_ddata *ddata = data; + + efm32_spi_vdbg(ddata, + "%s: txlen = %u, rxlen = %u, if=0x%08x, stat=0x%08x\n", + __func__, ddata->tx_len, ddata->rx_len, + efm32_spi_read32(ddata, REG_IF), + efm32_spi_read32(ddata, REG_STATUS)); + + spin_lock(&ddata->lock); + + efm32_spi_filltx(ddata); + + efm32_spi_vdbg(ddata, "%s: txlen = %u, rxlen = %u\n", + __func__, ddata->tx_len, ddata->rx_len); + + if (!ddata->tx_len) { + u32 ien = efm32_spi_read32(ddata, REG_IEN); + + ien &= ~REG_IF_TXBL; + + efm32_spi_write32(ddata, ien, REG_IEN); + efm32_spi_vdbg(ddata, "disable TXBL\n"); + } + + spin_unlock(&ddata->lock); + + return IRQ_HANDLED; +} + +static const struct efm32_spi_pdata efm32_spi_pdata_default = { + .location = 1, +}; + +static u32 efm32_spi_get_configured_location(struct efm32_spi_ddata *ddata) +{ + u32 reg = efm32_spi_read32(ddata, REG_ROUTE); + + return (reg & REG_ROUTE_LOCATION__MASK) >> __ffs(REG_ROUTE_LOCATION__MASK); +} + +static int efm32_spi_probe_dt(struct platform_device *pdev, + struct spi_master *master, struct efm32_spi_ddata *ddata) +{ + struct device_node *np = pdev->dev.of_node; + u32 location; + int ret; + + if (!np) + return 1; + + ret = of_property_read_u32(np, "location", &location); + if (!ret) { + dev_dbg(&pdev->dev, "using location %u\n", location); + } else { + /* default to location configured in hardware */ + location = efm32_spi_get_configured_location(ddata); + + dev_info(&pdev->dev, "fall back to location %u\n", location); + } + + ddata->pdata.location = location; + + /* spi core takes care about the bus number using an alias */ + master->bus_num = -1; + + return 0; +} + +static int efm32_spi_probe(struct platform_device *pdev) +{ + struct efm32_spi_ddata *ddata; + struct resource *res; + int ret; + struct spi_master *master; + struct device_node *np = pdev->dev.of_node; + unsigned int num_cs, i; + + num_cs = of_gpio_named_count(np, "cs-gpios"); + + master = spi_alloc_master(&pdev->dev, + sizeof(*ddata) + num_cs * sizeof(unsigned)); + if (!master) { + dev_dbg(&pdev->dev, + "failed to allocate spi master controller\n"); + return -ENOMEM; + } + platform_set_drvdata(pdev, master); + + master->dev.of_node = pdev->dev.of_node; + + master->num_chipselect = num_cs; + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; + master->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 16); + + ddata = spi_master_get_devdata(master); + + ddata->bitbang.master = spi_master_get(master); + ddata->bitbang.chipselect = efm32_spi_chipselect; + ddata->bitbang.setup_transfer = efm32_spi_setup_transfer; + ddata->bitbang.txrx_bufs = efm32_spi_txrx_bufs; + + spin_lock_init(&ddata->lock); + + ddata->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(ddata->clk)) { + ret = PTR_ERR(ddata->clk); + dev_err(&pdev->dev, "failed to get clock: %d\n", ret); + goto err; + } + + for (i = 0; i < num_cs; ++i) { + ret = of_get_named_gpio(np, "cs-gpios", i); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get csgpio#%u (%d)\n", + i, ret); + goto err; + } + ddata->csgpio[i] = ret; + dev_dbg(&pdev->dev, "csgpio#%u = %u\n", i, ddata->csgpio[i]); + ret = devm_gpio_request_one(&pdev->dev, ddata->csgpio[i], + GPIOF_OUT_INIT_LOW, DRIVER_NAME); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to configure csgpio#%u (%d)\n", + i, ret); + goto err; + } + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + dev_err(&pdev->dev, "failed to determine base address\n"); + goto err; + } + + if (resource_size(res) < 60) { + ret = -EINVAL; + dev_err(&pdev->dev, "memory resource too small\n"); + goto err; + } + + ddata->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ddata->base)) { + ret = PTR_ERR(ddata->base); + dev_err(&pdev->dev, "failed to remap memory\n"); + goto err; + } + + ret = platform_get_irq(pdev, 0); + if (ret <= 0) { + dev_err(&pdev->dev, "failed to get rx irq (%d)\n", ret); + goto err; + } + + ddata->rxirq = ret; + + ret = platform_get_irq(pdev, 1); + if (ret <= 0) + ret = ddata->rxirq + 1; + + ddata->txirq = ret; + + ret = clk_prepare_enable(ddata->clk); + if (ret < 0) { + dev_err(&pdev->dev, "failed to enable clock (%d)\n", ret); + goto err; + } + + ret = efm32_spi_probe_dt(pdev, master, ddata); + if (ret > 0) { + /* not created by device tree */ + const struct efm32_spi_pdata *pdata = + dev_get_platdata(&pdev->dev); + + if (pdata) + ddata->pdata = *pdata; + else + ddata->pdata.location = + efm32_spi_get_configured_location(ddata); + + master->bus_num = pdev->id; + + } else if (ret < 0) { + goto err_disable_clk; + } + + efm32_spi_write32(ddata, 0, REG_IEN); + efm32_spi_write32(ddata, REG_ROUTE_TXPEN | REG_ROUTE_RXPEN | + REG_ROUTE_CLKPEN | + REG_ROUTE_LOCATION(ddata->pdata.location), REG_ROUTE); + + ret = request_irq(ddata->rxirq, efm32_spi_rxirq, + 0, DRIVER_NAME " rx", ddata); + if (ret) { + dev_err(&pdev->dev, "failed to register rxirq (%d)\n", ret); + goto err_disable_clk; + } + + ret = request_irq(ddata->txirq, efm32_spi_txirq, + 0, DRIVER_NAME " tx", ddata); + if (ret) { + dev_err(&pdev->dev, "failed to register txirq (%d)\n", ret); + goto err_free_rx_irq; + } + + ret = spi_bitbang_start(&ddata->bitbang); + if (ret) { + dev_err(&pdev->dev, "spi_bitbang_start failed (%d)\n", ret); + + free_irq(ddata->txirq, ddata); +err_free_rx_irq: + free_irq(ddata->rxirq, ddata); +err_disable_clk: + clk_disable_unprepare(ddata->clk); +err: + spi_master_put(master); + kfree(master); + } + + return ret; +} + +static int efm32_spi_remove(struct platform_device *pdev) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct efm32_spi_ddata *ddata = spi_master_get_devdata(master); + + efm32_spi_write32(ddata, 0, REG_IEN); + + free_irq(ddata->txirq, ddata); + free_irq(ddata->rxirq, ddata); + clk_disable_unprepare(ddata->clk); + spi_master_put(master); + kfree(master); + + return 0; +} + +static const struct of_device_id efm32_spi_dt_ids[] = { + { + .compatible = "efm32,spi", + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, efm32_uart_dt_ids); + +static struct platform_driver efm32_spi_driver = { + .probe = efm32_spi_probe, + .remove = efm32_spi_remove, + + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = efm32_spi_dt_ids, + }, +}; +module_platform_driver(efm32_spi_driver); + +MODULE_AUTHOR("Uwe Kleine-Koenig "); +MODULE_DESCRIPTION("EFM32 SPI driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/include/linux/platform_data/efm32-spi.h b/include/linux/platform_data/efm32-spi.h new file mode 100644 index 000000000000..31b19ca1d73a --- /dev/null +++ b/include/linux/platform_data/efm32-spi.h @@ -0,0 +1,14 @@ +#ifndef __LINUX_PLATFORM_DATA_EFM32_SPI_H__ +#define __LINUX_PLATFORM_DATA_EFM32_SPI_H__ + +#include + +/** + * struct efm32_spi_pdata + * @location: pinmux location for the I/O pins (to be written to the ROUTE + * register) + */ +struct efm32_spi_pdata { + u8 location; +}; +#endif /* ifndef __LINUX_PLATFORM_DATA_EFM32_SPI_H__ */ -- cgit v1.2.3-71-gd317 From 9194731c5f9b2664c882a515b3398a29384a6864 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Jun 2013 16:25:35 +0200 Subject: drm/rcar-du: Rename platform data fields to match what they describe The struct rcar_du_encoder_data encoder::field describes the encoder type, and the rcar_du_encoder_lvds_data and rcar_du_encoder_vga_data structures describe connector properties. Rename them accordingly. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/rcar-du/rcar_du_encoder.c | 2 +- drivers/gpu/drm/rcar-du/rcar_du_encoder.h | 3 ++- drivers/gpu/drm/rcar-du/rcar_du_kms.c | 5 ++--- include/linux/platform_data/rcar-du.h | 19 +++++++++++++------ 4 files changed, 18 insertions(+), 11 deletions(-) (limited to 'include/linux/platform_data') diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c index 15a56433c80c..0d0375c7ee44 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c @@ -137,7 +137,7 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu, switch (type) { case RCAR_DU_ENCODER_LVDS: return rcar_du_lvds_connector_init(rcdu, renc, - &data->u.lvds.panel); + &data->connector.lvds.panel); case RCAR_DU_ENCODER_VGA: return rcar_du_vga_connector_init(rcdu, renc); diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.h b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h index 4f76e16bca88..08cde1293892 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.h +++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h @@ -14,10 +14,11 @@ #ifndef __RCAR_DU_ENCODER_H__ #define __RCAR_DU_ENCODER_H__ +#include + #include struct rcar_du_device; -struct rcar_du_encoder_data; struct rcar_du_encoder { struct drm_encoder encoder; diff --git a/drivers/gpu/drm/rcar-du/rcar_du_kms.c b/drivers/gpu/drm/rcar-du/rcar_du_kms.c index 3f8483cc0483..a8eef167d51a 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_kms.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_kms.c @@ -191,7 +191,7 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu) const struct rcar_du_encoder_data *pdata = &rcdu->pdata->encoders[i]; - if (pdata->encoder == RCAR_DU_ENCODER_UNUSED) + if (pdata->type == RCAR_DU_ENCODER_UNUSED) continue; if (pdata->output >= ARRAY_SIZE(rcdu->crtcs)) { @@ -201,8 +201,7 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu) continue; } - rcar_du_encoder_init(rcdu, pdata->encoder, pdata->output, - pdata); + rcar_du_encoder_init(rcdu, pdata->type, pdata->output, pdata); } /* Set the possible CRTCs and possible clones. All encoders can be diff --git a/include/linux/platform_data/rcar-du.h b/include/linux/platform_data/rcar-du.h index 80587fdbba3e..64cd8635e6e6 100644 --- a/include/linux/platform_data/rcar-du.h +++ b/include/linux/platform_data/rcar-du.h @@ -28,22 +28,29 @@ struct rcar_du_panel_data { struct drm_mode_modeinfo mode; }; -struct rcar_du_encoder_lvds_data { +struct rcar_du_connector_lvds_data { struct rcar_du_panel_data panel; }; -struct rcar_du_encoder_vga_data { +struct rcar_du_connector_vga_data { /* TODO: Add DDC information for EDID retrieval */ }; +/* + * struct rcar_du_encoder_data - Encoder platform data + * @type: the encoder type (RCAR_DU_ENCODER_*) + * @output: the DU output the connector is connected to + * @connector.lvds: platform data for LVDS connectors + * @connector.vga: platform data for VGA connectors + */ struct rcar_du_encoder_data { - enum rcar_du_encoder_type encoder; + enum rcar_du_encoder_type type; unsigned int output; union { - struct rcar_du_encoder_lvds_data lvds; - struct rcar_du_encoder_vga_data vga; - } u; + struct rcar_du_connector_lvds_data lvds; + struct rcar_du_connector_vga_data vga; + } connector; }; struct rcar_du_platform_data { -- cgit v1.2.3-71-gd317 From ef67a902e946ad1ef51040cf287a45cc4714e2b5 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Mon, 17 Jun 2013 03:13:11 +0200 Subject: drm/rcar-du: Rework output routing support Split the output routing specification between SoC-internal data, specified in the rcar_du_device_info structure, and board data, passed through platform data. The DU has 5 possible outputs (DPAD0/1, LVDS0/1, TCON). SoC-internal output routing data specify which output are valid, which CRTCs can be connected to the valid outputs, and the type of in-SoC encoder for the output. Platform data then specifies external encoders and the output they are connected to. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/rcar-du/rcar_du_crtc.c | 6 ++++-- drivers/gpu/drm/rcar-du/rcar_du_crtc.h | 4 +++- drivers/gpu/drm/rcar-du/rcar_du_drv.c | 30 ++++++++++++++++++++++++++++++ drivers/gpu/drm/rcar-du/rcar_du_drv.h | 16 ++++++++++++++++ drivers/gpu/drm/rcar-du/rcar_du_encoder.c | 26 +++++++++++++++++++++----- drivers/gpu/drm/rcar-du/rcar_du_encoder.h | 5 +++-- drivers/gpu/drm/rcar-du/rcar_du_group.c | 8 ++++---- drivers/gpu/drm/rcar-du/rcar_du_kms.c | 17 +++++++++++------ include/linux/platform_data/rcar-du.h | 17 +++++++++++++++-- 9 files changed, 107 insertions(+), 22 deletions(-) (limited to 'include/linux/platform_data') diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c index a340224e08e6..680606ef11d8 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c @@ -129,14 +129,16 @@ static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc) rcar_du_crtc_write(rcrtc, DEWR, mode->hdisplay); } -void rcar_du_crtc_route_output(struct drm_crtc *crtc, unsigned int output) +void rcar_du_crtc_route_output(struct drm_crtc *crtc, + enum rcar_du_output output) { struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + struct rcar_du_device *rcdu = rcrtc->group->dev; /* Store the route from the CRTC output to the DU output. The DU will be * configured when starting the CRTC. */ - rcrtc->outputs |= 1 << output; + rcrtc->outputs |= BIT(output); } void rcar_du_crtc_update_planes(struct drm_crtc *crtc) diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h index 542a7feceb20..39a983d13afb 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h @@ -15,6 +15,7 @@ #define __RCAR_DU_CRTC_H__ #include +#include #include #include @@ -45,7 +46,8 @@ void rcar_du_crtc_cancel_page_flip(struct rcar_du_crtc *rcrtc, void rcar_du_crtc_suspend(struct rcar_du_crtc *rcrtc); void rcar_du_crtc_resume(struct rcar_du_crtc *rcrtc); -void rcar_du_crtc_route_output(struct drm_crtc *crtc, unsigned int output); +void rcar_du_crtc_route_output(struct drm_crtc *crtc, + enum rcar_du_output output); void rcar_du_crtc_update_planes(struct drm_crtc *crtc); #endif /* __RCAR_DU_CRTC_H__ */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.c b/drivers/gpu/drm/rcar-du/rcar_du_drv.c index f8785357b599..4bc399734490 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_drv.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c @@ -219,12 +219,42 @@ static int rcar_du_remove(struct platform_device *pdev) static const struct rcar_du_device_info rcar_du_r8a7779_info = { .features = 0, .num_crtcs = 2, + .routes = { + /* R8A7779 has two RGB outputs and one (currently unsupported) + * TCON output. + */ + [RCAR_DU_OUTPUT_DPAD0] = { + .possible_crtcs = BIT(0), + .encoder_type = DRM_MODE_ENCODER_NONE, + }, + [RCAR_DU_OUTPUT_DPAD1] = { + .possible_crtcs = BIT(1) | BIT(0), + .encoder_type = DRM_MODE_ENCODER_NONE, + }, + }, }; static const struct rcar_du_device_info rcar_du_r8a7790_info = { .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK | RCAR_DU_FEATURE_ALIGN_128B | RCAR_DU_FEATURE_DEFR8, .num_crtcs = 3, + .routes = { + /* R8A7790 has one RGB output, two LVDS outputs and one + * (currently unsupported) TCON output. + */ + [RCAR_DU_OUTPUT_DPAD0] = { + .possible_crtcs = BIT(2) | BIT(1) | BIT(0), + .encoder_type = DRM_MODE_ENCODER_NONE, + }, + [RCAR_DU_OUTPUT_LVDS0] = { + .possible_crtcs = BIT(0), + .encoder_type = DRM_MODE_ENCODER_LVDS, + }, + [RCAR_DU_OUTPUT_LVDS1] = { + .possible_crtcs = BIT(2) | BIT(1), + .encoder_type = DRM_MODE_ENCODER_LVDS, + }, + }, }; static const struct platform_device_id rcar_du_id_table[] = { diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.h b/drivers/gpu/drm/rcar-du/rcar_du_drv.h index 70c335f51136..d5243f493903 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_drv.h +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.h @@ -29,14 +29,30 @@ struct rcar_du_device; #define RCAR_DU_FEATURE_ALIGN_128B (1 << 1) /* Align pitches to 128 bytes */ #define RCAR_DU_FEATURE_DEFR8 (1 << 2) /* Has DEFR8 register */ +/* + * struct rcar_du_output_routing - Output routing specification + * @possible_crtcs: bitmask of possible CRTCs for the output + * @encoder_type: DRM type of the internal encoder associated with the output + * + * The DU has 5 possible outputs (DPAD0/1, LVDS0/1, TCON). Output routing data + * specify the valid SoC outputs, which CRTCs can drive the output, and the type + * of in-SoC encoder for the output. + */ +struct rcar_du_output_routing { + unsigned int possible_crtcs; + unsigned int encoder_type; +}; + /* * struct rcar_du_device_info - DU model-specific information * @features: device features (RCAR_DU_FEATURE_*) * @num_crtcs: total number of CRTCs + * @routes: array of CRTC to output routes, indexed by output (RCAR_DU_OUTPUT_*) */ struct rcar_du_device_info { unsigned int features; unsigned int num_crtcs; + struct rcar_du_output_routing routes[RCAR_DU_OUTPUT_MAX]; }; struct rcar_du_device { diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c index 0d0375c7ee44..2aac28d21f87 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c @@ -115,10 +115,12 @@ static const struct drm_encoder_funcs encoder_funcs = { }; int rcar_du_encoder_init(struct rcar_du_device *rcdu, - enum rcar_du_encoder_type type, unsigned int output, + enum rcar_du_encoder_type type, + enum rcar_du_output output, const struct rcar_du_encoder_data *data) { struct rcar_du_encoder *renc; + unsigned int encoder_type; int ret; renc = devm_kzalloc(rcdu->dev, sizeof(*renc), GFP_KERNEL); @@ -127,19 +129,33 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu, renc->output = output; + switch (type) { + case RCAR_DU_ENCODER_VGA: + encoder_type = DRM_MODE_ENCODER_DAC; + break; + case RCAR_DU_ENCODER_LVDS: + encoder_type = DRM_MODE_ENCODER_LVDS; + break; + case RCAR_DU_ENCODER_NONE: + default: + /* No external encoder, use the internal encoder type. */ + encoder_type = rcdu->info->routes[output].encoder_type; + break; + } + ret = drm_encoder_init(rcdu->ddev, &renc->encoder, &encoder_funcs, - type); + encoder_type); if (ret < 0) return ret; drm_encoder_helper_add(&renc->encoder, &encoder_helper_funcs); - switch (type) { - case RCAR_DU_ENCODER_LVDS: + switch (encoder_type) { + case DRM_MODE_ENCODER_LVDS: return rcar_du_lvds_connector_init(rcdu, renc, &data->connector.lvds.panel); - case RCAR_DU_ENCODER_VGA: + case DRM_MODE_ENCODER_DAC: return rcar_du_vga_connector_init(rcdu, renc); default: diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.h b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h index 08cde1293892..2310416ea21f 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.h +++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h @@ -22,7 +22,7 @@ struct rcar_du_device; struct rcar_du_encoder { struct drm_encoder encoder; - unsigned int output; + enum rcar_du_output output; }; #define to_rcar_encoder(e) \ @@ -40,7 +40,8 @@ struct drm_encoder * rcar_du_connector_best_encoder(struct drm_connector *connector); int rcar_du_encoder_init(struct rcar_du_device *rcdu, - enum rcar_du_encoder_type type, unsigned int output, + enum rcar_du_encoder_type type, + enum rcar_du_output output, const struct rcar_du_encoder_data *data); #endif /* __RCAR_DU_ENCODER_H__ */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_group.c b/drivers/gpu/drm/rcar-du/rcar_du_group.c index f3ba0ca845e2..9df6fb635c96 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_group.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_group.c @@ -135,11 +135,11 @@ void rcar_du_group_set_routing(struct rcar_du_group *rgrp) dorcr &= ~(DORCR_PG2T | DORCR_DK2S | DORCR_PG2D_MASK); - /* Set the DU1 pins sources. Select CRTC 0 if explicitly requested and - * CRTC 1 in all other cases to avoid cloning CRTC 0 to DU0 and DU1 by - * default. + /* Set the DPAD1 pins sources. Select CRTC 0 if explicitly requested and + * CRTC 1 in all other cases to avoid cloning CRTC 0 to DPAD0 and DPAD1 + * by default. */ - if (crtc0->outputs & (1 << 1)) + if (crtc0->outputs & BIT(RCAR_DU_OUTPUT_DPAD1)) dorcr |= DORCR_PG2D_DS1; else dorcr |= DORCR_PG2T | DORCR_DK2S | DORCR_PG2D_DS2; diff --git a/drivers/gpu/drm/rcar-du/rcar_du_kms.c b/drivers/gpu/drm/rcar-du/rcar_du_kms.c index 816963ca1626..2b92e68a09f0 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_kms.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_kms.c @@ -220,11 +220,14 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu) for (i = 0; i < rcdu->pdata->num_encoders; ++i) { const struct rcar_du_encoder_data *pdata = &rcdu->pdata->encoders[i]; + const struct rcar_du_output_routing *route = + &rcdu->info->routes[pdata->output]; if (pdata->type == RCAR_DU_ENCODER_UNUSED) continue; - if (pdata->output >= rcdu->num_crtcs) { + if (pdata->output >= RCAR_DU_OUTPUT_MAX || + route->possible_crtcs == 0) { dev_warn(rcdu->dev, "encoder %u references unexisting output %u, skipping\n", i, pdata->output); @@ -234,15 +237,17 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu) rcar_du_encoder_init(rcdu, pdata->type, pdata->output, pdata); } - /* Set the possible CRTCs and possible clones. All encoders can be - * driven by the CRTC associated with the output they're connected to, - * as well as by CRTC 0. + /* Set the possible CRTCs and possible clones. There's always at least + * one way for all encoders to clone each other, set all bits in the + * possible clones field. */ list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { struct rcar_du_encoder *renc = to_rcar_encoder(encoder); + const struct rcar_du_output_routing *route = + &rcdu->info->routes[renc->output]; - encoder->possible_crtcs = (1 << 0) | (1 << renc->output); - encoder->possible_clones = 1 << 0; + encoder->possible_crtcs = route->possible_crtcs; + encoder->possible_clones = (1 << rcdu->pdata->num_encoders) - 1; } /* Now that the CRTCs have been initialized register the planes. */ diff --git a/include/linux/platform_data/rcar-du.h b/include/linux/platform_data/rcar-du.h index 64cd8635e6e6..1a2e9901a22e 100644 --- a/include/linux/platform_data/rcar-du.h +++ b/include/linux/platform_data/rcar-du.h @@ -16,8 +16,18 @@ #include +enum rcar_du_output { + RCAR_DU_OUTPUT_DPAD0, + RCAR_DU_OUTPUT_DPAD1, + RCAR_DU_OUTPUT_LVDS0, + RCAR_DU_OUTPUT_LVDS1, + RCAR_DU_OUTPUT_TCON, + RCAR_DU_OUTPUT_MAX, +}; + enum rcar_du_encoder_type { RCAR_DU_ENCODER_UNUSED = 0, + RCAR_DU_ENCODER_NONE, RCAR_DU_ENCODER_VGA, RCAR_DU_ENCODER_LVDS, }; @@ -39,13 +49,16 @@ struct rcar_du_connector_vga_data { /* * struct rcar_du_encoder_data - Encoder platform data * @type: the encoder type (RCAR_DU_ENCODER_*) - * @output: the DU output the connector is connected to + * @output: the DU output the connector is connected to (RCAR_DU_OUTPUT_*) * @connector.lvds: platform data for LVDS connectors * @connector.vga: platform data for VGA connectors + * + * Encoder platform data describes an on-board encoder, its associated DU SoC + * output, and the connector. */ struct rcar_du_encoder_data { enum rcar_du_encoder_type type; - unsigned int output; + enum rcar_du_output output; union { struct rcar_du_connector_lvds_data lvds; -- cgit v1.2.3-71-gd317 From 4ca0c0d4784fa82d68733f7793e3487023e12282 Mon Sep 17 00:00:00 2001 From: Padmavathi Venna Date: Mon, 12 Aug 2013 15:19:52 +0530 Subject: ASoC: Samsung: I2S: Modify the I2S driver to support I2S on Exynos5420 Exynos5420 added support for I2S TDM mode. For this, there are some register changes in the I2S controller. This patch adds the relevant register changes to support I2S in normal mode. This patch adds a quirk for TDM mode and if TDM mode is present all the relevent changes will be applied. Signed-off-by: Padmavathi Venna Reviewed-by: Tomasz Figa Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/samsung-i2s.txt | 4 ++ include/linux/platform_data/asoc-s3c.h | 1 + sound/soc/samsung/i2s-regs.h | 15 ++++ sound/soc/samsung/i2s.c | 81 +++++++++++++++++++--- 4 files changed, 93 insertions(+), 8 deletions(-) (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/sound/samsung-i2s.txt b/Documentation/devicetree/bindings/sound/samsung-i2s.txt index 25a0024d1b0a..7386d444ada1 100644 --- a/Documentation/devicetree/bindings/sound/samsung-i2s.txt +++ b/Documentation/devicetree/bindings/sound/samsung-i2s.txt @@ -6,6 +6,10 @@ Required SoC Specific Properties: - samsung,s3c6410-i2s: for 8/16/24bit stereo I2S. - samsung,s5pv210-i2s: for 8/16/24bit multichannel(5.1) I2S with secondary fifo, s/w reset control and internal mux for root clk src. + - samsung,exynos5420-i2s: for 8/16/24bit multichannel(7.1) I2S with + secondary fifo, s/w reset control, internal mux for root clk src and + TDM support. TDM (Time division multiplexing) is to allow transfer of + multiple channel audio data on single data line. - reg: physical base address of the controller and length of memory mapped region. diff --git a/include/linux/platform_data/asoc-s3c.h b/include/linux/platform_data/asoc-s3c.h index 88272591a895..9efc04dd255a 100644 --- a/include/linux/platform_data/asoc-s3c.h +++ b/include/linux/platform_data/asoc-s3c.h @@ -36,6 +36,7 @@ struct samsung_i2s { */ #define QUIRK_NO_MUXPSR (1 << 2) #define QUIRK_NEED_RSTCLR (1 << 3) +#define QUIRK_SUPPORTS_TDM (1 << 4) /* Quirks of the I2S controller */ u32 quirks; dma_addr_t idma_addr; diff --git a/sound/soc/samsung/i2s-regs.h b/sound/soc/samsung/i2s-regs.h index 30513b7ede3a..821a50231002 100644 --- a/sound/soc/samsung/i2s-regs.h +++ b/sound/soc/samsung/i2s-regs.h @@ -31,6 +31,10 @@ #define I2SLVL1ADDR 0x34 #define I2SLVL2ADDR 0x38 #define I2SLVL3ADDR 0x3c +#define I2SSTR1 0x40 +#define I2SVER 0x44 +#define I2SFIC2 0x48 +#define I2STDM 0x4c #define CON_RSTCLR (1 << 31) #define CON_FRXOFSTATUS (1 << 26) @@ -117,6 +121,17 @@ #define MOD_BCLK_MASK 3 #define MOD_8BIT (1 << 0) +#define EXYNOS5420_MOD_LRP_SHIFT 15 +#define EXYNOS5420_MOD_SDF_SHIFT 6 +#define EXYNOS5420_MOD_RCLK_SHIFT 4 +#define EXYNOS5420_MOD_BCLK_SHIFT 0 +#define EXYNOS5420_MOD_BCLK_64FS 4 +#define EXYNOS5420_MOD_BCLK_96FS 5 +#define EXYNOS5420_MOD_BCLK_128FS 6 +#define EXYNOS5420_MOD_BCLK_192FS 7 +#define EXYNOS5420_MOD_BCLK_256FS 8 +#define EXYNOS5420_MOD_BCLK_MASK 0xf + #define MOD_CDCLKCON (1 << 12) #define PSR_PSREN (1 << 15) diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index 3b4835a1bd23..dd995a7ab55c 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -199,7 +199,12 @@ static inline bool is_manager(struct i2s_dai *i2s) /* Read RCLK of I2S (in multiples of LRCLK) */ static inline unsigned get_rfs(struct i2s_dai *i2s) { - u32 rfs = (readl(i2s->addr + I2SMOD) >> MOD_RCLK_SHIFT); + u32 rfs; + + if (i2s->quirks & QUIRK_SUPPORTS_TDM) + rfs = readl(i2s->addr + I2SMOD) >> EXYNOS5420_MOD_RCLK_SHIFT; + else + rfs = (readl(i2s->addr + I2SMOD) >> MOD_RCLK_SHIFT); rfs &= MOD_RCLK_MASK; switch (rfs) { @@ -214,8 +219,12 @@ static inline unsigned get_rfs(struct i2s_dai *i2s) static inline void set_rfs(struct i2s_dai *i2s, unsigned rfs) { u32 mod = readl(i2s->addr + I2SMOD); - int rfs_shift = MOD_RCLK_SHIFT; + int rfs_shift; + if (i2s->quirks & QUIRK_SUPPORTS_TDM) + rfs_shift = EXYNOS5420_MOD_RCLK_SHIFT; + else + rfs_shift = MOD_RCLK_SHIFT; mod &= ~(MOD_RCLK_MASK << rfs_shift); switch (rfs) { @@ -239,10 +248,22 @@ static inline void set_rfs(struct i2s_dai *i2s, unsigned rfs) /* Read Bit-Clock of I2S (in multiples of LRCLK) */ static inline unsigned get_bfs(struct i2s_dai *i2s) { - u32 bfs = readl(i2s->addr + I2SMOD) >> MOD_BCLK_SHIFT; - bfs &= MOD_BCLK_MASK; + u32 bfs; + + if (i2s->quirks & QUIRK_SUPPORTS_TDM) { + bfs = readl(i2s->addr + I2SMOD) >> EXYNOS5420_MOD_BCLK_SHIFT; + bfs &= EXYNOS5420_MOD_BCLK_MASK; + } else { + bfs = readl(i2s->addr + I2SMOD) >> MOD_BCLK_SHIFT; + bfs &= MOD_BCLK_MASK; + } switch (bfs) { + case 8: return 256; + case 7: return 192; + case 6: return 128; + case 5: return 96; + case 4: return 64; case 3: return 24; case 2: return 16; case 1: return 48; @@ -254,9 +275,22 @@ static inline unsigned get_bfs(struct i2s_dai *i2s) static inline void set_bfs(struct i2s_dai *i2s, unsigned bfs) { u32 mod = readl(i2s->addr + I2SMOD); - int bfs_shift = MOD_BCLK_SHIFT; + int bfs_shift; + int tdm = i2s->quirks & QUIRK_SUPPORTS_TDM; - mod &= ~(MOD_BCLK_MASK << bfs_shift); + if (i2s->quirks & QUIRK_SUPPORTS_TDM) { + bfs_shift = EXYNOS5420_MOD_BCLK_SHIFT; + mod &= ~(EXYNOS5420_MOD_BCLK_MASK << bfs_shift); + } else { + bfs_shift = MOD_BCLK_SHIFT; + mod &= ~(MOD_BCLK_MASK << bfs_shift); + } + + /* Non-TDM I2S controllers do not support BCLK > 48 * FS */ + if (!tdm && bfs > 48) { + dev_err(&i2s->pdev->dev, "Unsupported BCLK divider\n"); + return; + } switch (bfs) { case 48: @@ -271,6 +305,21 @@ static inline void set_bfs(struct i2s_dai *i2s, unsigned bfs) case 16: mod |= (MOD_BCLK_16FS << bfs_shift); break; + case 64: + mod |= (EXYNOS5420_MOD_BCLK_64FS << bfs_shift); + break; + case 96: + mod |= (EXYNOS5420_MOD_BCLK_96FS << bfs_shift); + break; + case 128: + mod |= (EXYNOS5420_MOD_BCLK_128FS << bfs_shift); + break; + case 192: + mod |= (EXYNOS5420_MOD_BCLK_192FS << bfs_shift); + break; + case 256: + mod |= (EXYNOS5420_MOD_BCLK_256FS << bfs_shift); + break; default: dev_err(&i2s->pdev->dev, "Wrong BCLK Divider!\n"); return; @@ -496,10 +545,17 @@ static int i2s_set_fmt(struct snd_soc_dai *dai, { struct i2s_dai *i2s = to_info(dai); u32 mod = readl(i2s->addr + I2SMOD); - int lrp_shift = MOD_LRP_SHIFT, sdf_shift = MOD_SDF_SHIFT; - int sdf_mask, lrp_rlow; + int lrp_shift, sdf_shift, sdf_mask, lrp_rlow; u32 tmp = 0; + if (i2s->quirks & QUIRK_SUPPORTS_TDM) { + lrp_shift = EXYNOS5420_MOD_LRP_SHIFT; + sdf_shift = EXYNOS5420_MOD_SDF_SHIFT; + } else { + lrp_shift = MOD_LRP_SHIFT; + sdf_shift = MOD_SDF_SHIFT; + } + sdf_mask = MOD_SDF_MASK << sdf_shift; lrp_rlow = MOD_LR_RLOW << lrp_shift; @@ -1253,6 +1309,12 @@ static const struct samsung_i2s_dai_data i2sv5_dai_type = { .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR, }; +static const struct samsung_i2s_dai_data i2sv6_dai_type = { + .dai_type = TYPE_PRI, + .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR | + QUIRK_SUPPORTS_TDM, +}; + static const struct samsung_i2s_dai_data samsung_dai_type_pri = { .dai_type = TYPE_PRI, }; @@ -1281,6 +1343,9 @@ static const struct of_device_id exynos_i2s_match[] = { }, { .compatible = "samsung,s5pv210-i2s", .data = &i2sv5_dai_type, + }, { + .compatible = "samsung,exynos5420-i2s", + .data = &i2sv6_dai_type, }, {}, }; -- cgit v1.2.3-71-gd317 From 0c1836a6563decc6de8622b2ed71523b3bdceb65 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:27 +0530 Subject: thermal: exynos: Move exynos_thermal.h from include/* to driver/* folder This patch renames and moves include/linux/platform_data/exynos_thermal.h to drivers/thermal/samsung/exynos_tmu.h. This file movement is needed as exynos SOC's are not supporting non-DT based platforms and this file now just contains exynos tmu driver related definations. Also struct freq_clip_table is now moved to exynos_thermal_common.c as it fixes the compilation issue occuring because now this new tmu header file is included in tmu driver c file and not in the common thermal header file. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Acked-by: Eduardo Valentin Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin --- drivers/thermal/samsung/exynos_thermal_common.c | 1 - drivers/thermal/samsung/exynos_thermal_common.h | 16 ++++ drivers/thermal/samsung/exynos_tmu.c | 2 +- drivers/thermal/samsung/exynos_tmu.h | 107 +++++++++++++++++++++ include/linux/platform_data/exynos_thermal.h | 119 ------------------------ 5 files changed, 124 insertions(+), 121 deletions(-) create mode 100644 drivers/thermal/samsung/exynos_tmu.h delete mode 100644 include/linux/platform_data/exynos_thermal.h (limited to 'include/linux/platform_data') diff --git a/drivers/thermal/samsung/exynos_thermal_common.c b/drivers/thermal/samsung/exynos_thermal_common.c index f20f458be0a0..c9edc307dcf3 100644 --- a/drivers/thermal/samsung/exynos_thermal_common.c +++ b/drivers/thermal/samsung/exynos_thermal_common.c @@ -22,7 +22,6 @@ #include #include -#include #include #include diff --git a/drivers/thermal/samsung/exynos_thermal_common.h b/drivers/thermal/samsung/exynos_thermal_common.h index 8df18486078c..57ce33b39b54 100644 --- a/drivers/thermal/samsung/exynos_thermal_common.h +++ b/drivers/thermal/samsung/exynos_thermal_common.h @@ -44,6 +44,22 @@ #define EXYNOS_ZONE_COUNT 3 +/** + * struct freq_clip_table + * @freq_clip_max: maximum frequency allowed for this cooling state. + * @temp_level: Temperature level at which the temperature clipping will + * happen. + * @mask_val: cpumask of the allowed cpu's where the clipping will take place. + * + * This structure is required to be filled and passed to the + * cpufreq_cooling_unregister function. + */ +struct freq_clip_table { + unsigned int freq_clip_max; + unsigned int temp_level; + const struct cpumask *mask_val; +}; + struct thermal_trip_point_conf { int trip_val[MAX_TRIP_COUNT]; int trip_count; diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 2b2cc333cd7d..568c31d0d1cb 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -27,9 +27,9 @@ #include #include #include -#include #include "exynos_thermal_common.h" +#include "exynos_tmu.h" /* Exynos generic registers */ #define EXYNOS_TMU_REG_TRIMINFO 0x0 diff --git a/drivers/thermal/samsung/exynos_tmu.h b/drivers/thermal/samsung/exynos_tmu.h new file mode 100644 index 000000000000..1857609518f2 --- /dev/null +++ b/drivers/thermal/samsung/exynos_tmu.h @@ -0,0 +1,107 @@ +/* + * exynos_tmu.h - Samsung EXYNOS TMU (Thermal Management Unit) + * + * Copyright (C) 2011 Samsung Electronics + * Donggeun Kim + * Amit Daniel Kachhap + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _EXYNOS_TMU_H +#define _EXYNOS_TMU_H +#include + +#include "exynos_thermal_common.h" + +enum calibration_type { + TYPE_ONE_POINT_TRIMMING, + TYPE_TWO_POINT_TRIMMING, + TYPE_NONE, +}; + +enum soc_type { + SOC_ARCH_EXYNOS4210 = 1, + SOC_ARCH_EXYNOS, +}; + +/** + * struct exynos_tmu_platform_data + * @threshold: basic temperature for generating interrupt + * 25 <= threshold <= 125 [unit: degree Celsius] + * @threshold_falling: differntial value for setting threshold + * of temperature falling interrupt. + * @trigger_levels: array for each interrupt levels + * [unit: degree Celsius] + * 0: temperature for trigger_level0 interrupt + * condition for trigger_level0 interrupt: + * current temperature > threshold + trigger_levels[0] + * 1: temperature for trigger_level1 interrupt + * condition for trigger_level1 interrupt: + * current temperature > threshold + trigger_levels[1] + * 2: temperature for trigger_level2 interrupt + * condition for trigger_level2 interrupt: + * current temperature > threshold + trigger_levels[2] + * 3: temperature for trigger_level3 interrupt + * condition for trigger_level3 interrupt: + * current temperature > threshold + trigger_levels[3] + * @trigger_level0_en: + * 1 = enable trigger_level0 interrupt, + * 0 = disable trigger_level0 interrupt + * @trigger_level1_en: + * 1 = enable trigger_level1 interrupt, + * 0 = disable trigger_level1 interrupt + * @trigger_level2_en: + * 1 = enable trigger_level2 interrupt, + * 0 = disable trigger_level2 interrupt + * @trigger_level3_en: + * 1 = enable trigger_level3 interrupt, + * 0 = disable trigger_level3 interrupt + * @gain: gain of amplifier in the positive-TC generator block + * 0 <= gain <= 15 + * @reference_voltage: reference voltage of amplifier + * in the positive-TC generator block + * 0 <= reference_voltage <= 31 + * @noise_cancel_mode: noise cancellation mode + * 000, 100, 101, 110 and 111 can be different modes + * @type: determines the type of SOC + * @efuse_value: platform defined fuse value + * @cal_type: calibration type for temperature + * @freq_clip_table: Table representing frequency reduction percentage. + * @freq_tab_count: Count of the above table as frequency reduction may + * applicable to only some of the trigger levels. + * + * This structure is required for configuration of exynos_tmu driver. + */ +struct exynos_tmu_platform_data { + u8 threshold; + u8 threshold_falling; + u8 trigger_levels[4]; + bool trigger_level0_en; + bool trigger_level1_en; + bool trigger_level2_en; + bool trigger_level3_en; + + u8 gain; + u8 reference_voltage; + u8 noise_cancel_mode; + u32 efuse_value; + + enum calibration_type cal_type; + enum soc_type type; + struct freq_clip_table freq_tab[4]; + unsigned int freq_tab_count; +}; +#endif /* _EXYNOS_TMU_H */ diff --git a/include/linux/platform_data/exynos_thermal.h b/include/linux/platform_data/exynos_thermal.h deleted file mode 100644 index da7e6274b175..000000000000 --- a/include/linux/platform_data/exynos_thermal.h +++ /dev/null @@ -1,119 +0,0 @@ -/* - * exynos_thermal.h - Samsung EXYNOS TMU (Thermal Management Unit) - * - * Copyright (C) 2011 Samsung Electronics - * Donggeun Kim - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifndef _LINUX_EXYNOS_THERMAL_H -#define _LINUX_EXYNOS_THERMAL_H -#include - -enum calibration_type { - TYPE_ONE_POINT_TRIMMING, - TYPE_TWO_POINT_TRIMMING, - TYPE_NONE, -}; - -enum soc_type { - SOC_ARCH_EXYNOS4210 = 1, - SOC_ARCH_EXYNOS, -}; -/** - * struct freq_clip_table - * @freq_clip_max: maximum frequency allowed for this cooling state. - * @temp_level: Temperature level at which the temperature clipping will - * happen. - * @mask_val: cpumask of the allowed cpu's where the clipping will take place. - * - * This structure is required to be filled and passed to the - * cpufreq_cooling_unregister function. - */ -struct freq_clip_table { - unsigned int freq_clip_max; - unsigned int temp_level; - const struct cpumask *mask_val; -}; - -/** - * struct exynos_tmu_platform_data - * @threshold: basic temperature for generating interrupt - * 25 <= threshold <= 125 [unit: degree Celsius] - * @threshold_falling: differntial value for setting threshold - * of temperature falling interrupt. - * @trigger_levels: array for each interrupt levels - * [unit: degree Celsius] - * 0: temperature for trigger_level0 interrupt - * condition for trigger_level0 interrupt: - * current temperature > threshold + trigger_levels[0] - * 1: temperature for trigger_level1 interrupt - * condition for trigger_level1 interrupt: - * current temperature > threshold + trigger_levels[1] - * 2: temperature for trigger_level2 interrupt - * condition for trigger_level2 interrupt: - * current temperature > threshold + trigger_levels[2] - * 3: temperature for trigger_level3 interrupt - * condition for trigger_level3 interrupt: - * current temperature > threshold + trigger_levels[3] - * @trigger_level0_en: - * 1 = enable trigger_level0 interrupt, - * 0 = disable trigger_level0 interrupt - * @trigger_level1_en: - * 1 = enable trigger_level1 interrupt, - * 0 = disable trigger_level1 interrupt - * @trigger_level2_en: - * 1 = enable trigger_level2 interrupt, - * 0 = disable trigger_level2 interrupt - * @trigger_level3_en: - * 1 = enable trigger_level3 interrupt, - * 0 = disable trigger_level3 interrupt - * @gain: gain of amplifier in the positive-TC generator block - * 0 <= gain <= 15 - * @reference_voltage: reference voltage of amplifier - * in the positive-TC generator block - * 0 <= reference_voltage <= 31 - * @noise_cancel_mode: noise cancellation mode - * 000, 100, 101, 110 and 111 can be different modes - * @type: determines the type of SOC - * @efuse_value: platform defined fuse value - * @cal_type: calibration type for temperature - * @freq_clip_table: Table representing frequency reduction percentage. - * @freq_tab_count: Count of the above table as frequency reduction may - * applicable to only some of the trigger levels. - * - * This structure is required for configuration of exynos_tmu driver. - */ -struct exynos_tmu_platform_data { - u8 threshold; - u8 threshold_falling; - u8 trigger_levels[4]; - bool trigger_level0_en; - bool trigger_level1_en; - bool trigger_level2_en; - bool trigger_level3_en; - - u8 gain; - u8 reference_voltage; - u8 noise_cancel_mode; - u32 efuse_value; - - enum calibration_type cal_type; - enum soc_type type; - struct freq_clip_table freq_tab[4]; - unsigned int freq_tab_count; -}; -#endif /* _LINUX_EXYNOS_THERMAL_H */ -- cgit v1.2.3-71-gd317 From b05e92545d9582be15699e4a33d0f93ac00b37dd Mon Sep 17 00:00:00 2001 From: Franky Lin Date: Sat, 10 Aug 2013 12:27:26 +0200 Subject: brcmfmac: abstract tx packet processing functions Abstract brcmf_sdio_txpkt_prep and brcmf_sdio_txpkt_postp as a preparation of chained tx packets for host side tx glomming. Reviewed-by: Hante Meuleman Reviewed-by: Pieter-Paul Giesberts Reviewed-by: Arend van Spriel Signed-off-by: Franky Lin Signed-off-by: Arend van Spriel Signed-off-by: John W. Linville --- drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c | 16 +- drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c | 237 +++++++++++++++------ .../net/wireless/brcm80211/brcmfmac/sdio_host.h | 2 +- include/linux/platform_data/brcmfmac-sdio.h | 6 + 4 files changed, 183 insertions(+), 78 deletions(-) (limited to 'include/linux/platform_data') diff --git a/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c index e3f3c48f86d4..e13b1a65c65f 100644 --- a/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c +++ b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c @@ -592,6 +592,7 @@ brcmf_sdcard_send_buf(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, uint flags, u8 *buf, uint nbytes) { struct sk_buff *mypkt; + struct sk_buff_head pktq; int err; mypkt = brcmu_pkt_buf_get_skb(nbytes); @@ -602,7 +603,10 @@ brcmf_sdcard_send_buf(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, } memcpy(mypkt->data, buf, nbytes); - err = brcmf_sdcard_send_pkt(sdiodev, addr, fn, flags, mypkt); + __skb_queue_head_init(&pktq); + __skb_queue_tail(&pktq, mypkt); + err = brcmf_sdcard_send_pkt(sdiodev, addr, fn, flags, &pktq); + __skb_dequeue_tail(&pktq); brcmu_pkt_buf_free_skb(mypkt); return err; @@ -611,22 +615,18 @@ brcmf_sdcard_send_buf(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, int brcmf_sdcard_send_pkt(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, - uint flags, struct sk_buff *pkt) + uint flags, struct sk_buff_head *pktq) { uint width; int err = 0; - struct sk_buff_head pkt_list; brcmf_dbg(SDIO, "fun = %d, addr = 0x%x, size = %d\n", - fn, addr, pkt->len); + fn, addr, pktq->qlen); width = (flags & SDIO_REQ_4BYTE) ? 4 : 2; brcmf_sdio_addrprep(sdiodev, width, &addr); - skb_queue_head_init(&pkt_list); - skb_queue_tail(&pkt_list, pkt); - err = brcmf_sdio_buffrw(sdiodev, fn, true, addr, &pkt_list); - skb_dequeue_tail(&pkt_list); + err = brcmf_sdio_buffrw(sdiodev, fn, true, addr, pktq); return err; } diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c b/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c index db31312eba6a..aa4cacaf8b03 100644 --- a/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c @@ -510,7 +510,6 @@ struct brcmf_sdio { #ifdef DEBUG static int qcount[NUMPRIO]; -static int tx_packets[NUMPRIO]; #endif /* DEBUG */ #define DEFAULT_SDIO_DRIVE_STRENGTH 6 /* in milliamps */ @@ -1759,85 +1758,185 @@ brcmf_sdbrcm_wait_event_wakeup(struct brcmf_sdio *bus) return; } +/* flag marking a dummy skb added for DMA alignment requirement */ +#define DUMMY_SKB_FLAG 0x10000 +/* bit mask of data length chopped from the previous packet */ +#define DUMMY_SKB_CHOP_LEN_MASK 0xffff +/** + * brcmf_sdio_txpkt_prep - packet preparation for transmit + * @bus: brcmf_sdio structure pointer + * @pktq: packet list pointer + * @chan: virtual channel to transmit the packet + * + * Processes to be applied to the packet + * - Align data buffer pointer + * - Align data buffer length + * - Prepare header + * Return: negative value if there is error + */ +static int +brcmf_sdio_txpkt_prep(struct brcmf_sdio *bus, struct sk_buff_head *pktq, + uint chan) +{ + u16 head_pad, tail_pad, tail_chop, pkt_len; + u16 head_align, sg_align; + u32 sw_header; + int ntail; + struct sk_buff *pkt_next, *pkt_new; + u8 *dat_buf; + unsigned blksize = bus->sdiodev->func[SDIO_FUNC_2]->cur_blksize; + + /* SDIO ADMA requires at least 32 bit alignment */ + head_align = 4; + sg_align = 4; + if (bus->sdiodev->pdata) { + head_align = bus->sdiodev->pdata->sd_head_align > 4 ? + bus->sdiodev->pdata->sd_head_align : 4; + sg_align = bus->sdiodev->pdata->sd_sgentry_align > 4 ? + bus->sdiodev->pdata->sd_sgentry_align : 4; + } + /* sg entry alignment should be a divisor of block size */ + WARN_ON(blksize % sg_align); + + pkt_next = pktq->next; + dat_buf = (u8 *)(pkt_next->data); + + /* Check head padding */ + head_pad = ((unsigned long)dat_buf % head_align); + if (head_pad) { + if (skb_headroom(pkt_next) < head_pad) { + bus->sdiodev->bus_if->tx_realloc++; + head_pad = 0; + if (skb_cow(pkt_next, head_pad)) + return -ENOMEM; + } + skb_push(pkt_next, head_pad); + dat_buf = (u8 *)(pkt_next->data); + memset(dat_buf, 0, head_pad + SDPCM_HDRLEN); + } + + /* Check tail padding */ + pkt_new = NULL; + tail_chop = pkt_next->len % sg_align; + tail_pad = sg_align - tail_chop; + tail_pad += blksize - (pkt_next->len + tail_pad) % blksize; + if (skb_tailroom(pkt_next) < tail_pad && pkt_next->len > blksize) { + pkt_new = brcmu_pkt_buf_get_skb(tail_pad + tail_chop); + if (pkt_new == NULL) + return -ENOMEM; + memcpy(pkt_new->data, + pkt_next->data + pkt_next->len - tail_chop, + tail_chop); + *(u32 *)(pkt_new->cb) = DUMMY_SKB_FLAG + tail_chop; + skb_trim(pkt_next, pkt_next->len - tail_chop); + __skb_queue_after(pktq, pkt_next, pkt_new); + } else { + ntail = pkt_next->data_len + tail_pad - + (pkt_next->end - pkt_next->tail); + if (skb_cloned(pkt_next) || ntail > 0) + if (pskb_expand_head(pkt_next, 0, ntail, GFP_ATOMIC)) + return -ENOMEM; + if (skb_linearize(pkt_next)) + return -ENOMEM; + dat_buf = (u8 *)(pkt_next->data); + __skb_put(pkt_next, tail_pad); + } + + /* Now prep the header */ + /* 4 bytes hardware header (frame tag) + * Byte 0~1: Frame length + * Byte 2~3: Checksum, bit-wise inverse of frame length + */ + if (pkt_new) + pkt_len = pkt_next->len + tail_chop; + else + pkt_len = pkt_next->len - tail_pad; + *(__le16 *)dat_buf = cpu_to_le16(pkt_len); + *(((__le16 *)dat_buf) + 1) = cpu_to_le16(~pkt_len); + /* 8 bytes software header + * Byte 0: Tx sequence number + * Byte 1: 4 MSB Channel number + * Byte 2: Reserved + * Byte 3: Data offset + * Byte 4~7: Reserved + */ + sw_header = bus->tx_seq; + sw_header |= ((chan << SDPCM_CHANNEL_SHIFT) & SDPCM_CHANNEL_MASK); + sw_header |= ((head_pad + SDPCM_HDRLEN) << SDPCM_DOFFSET_SHIFT) & + SDPCM_DOFFSET_MASK; + *(((__le32 *)dat_buf) + 1) = cpu_to_le32(sw_header); + *(((__le32 *)dat_buf) + 2) = 0; + + if (BRCMF_BYTES_ON() && + ((BRCMF_CTL_ON() && chan == SDPCM_CONTROL_CHANNEL) || + (BRCMF_DATA_ON() && chan != SDPCM_CONTROL_CHANNEL))) + brcmf_dbg_hex_dump(true, pkt_next, pkt_len, "Tx Frame:\n"); + else if (BRCMF_HDRS_ON()) + brcmf_dbg_hex_dump(true, pkt_next, head_pad + SDPCM_HDRLEN, + "Tx Header:\n"); + + return 0; +} + +/** + * brcmf_sdio_txpkt_postp - packet post processing for transmit + * @bus: brcmf_sdio structure pointer + * @pktq: packet list pointer + * + * Processes to be applied to the packet + * - Remove head padding + * - Remove tail padding + */ +static void +brcmf_sdio_txpkt_postp(struct brcmf_sdio *bus, struct sk_buff_head *pktq) +{ + u8 *hdr; + u32 dat_offset; + u32 dummy_flags, chop_len; + struct sk_buff *pkt_next, *tmp, *pkt_prev; + + skb_queue_walk_safe(pktq, pkt_next, tmp) { + dummy_flags = *(u32 *)(pkt_next->cb); + if (dummy_flags & DUMMY_SKB_FLAG) { + chop_len = dummy_flags & DUMMY_SKB_CHOP_LEN_MASK; + if (chop_len) { + pkt_prev = pkt_next->prev; + memcpy(pkt_prev->data + pkt_prev->len, + pkt_next->data, chop_len); + skb_put(pkt_prev, chop_len); + } + __skb_unlink(pkt_next, pktq); + brcmu_pkt_buf_free_skb(pkt_next); + } else { + hdr = pkt_next->data + SDPCM_FRAMETAG_LEN; + dat_offset = le32_to_cpu(*(__le32 *)hdr); + dat_offset = (dat_offset & SDPCM_DOFFSET_MASK) >> + SDPCM_DOFFSET_SHIFT; + skb_pull(pkt_next, dat_offset); + } + } +} + /* Writes a HW/SW header into the packet and sends it. */ /* Assumes: (a) header space already there, (b) caller holds lock */ static int brcmf_sdbrcm_txpkt(struct brcmf_sdio *bus, struct sk_buff *pkt, uint chan) { int ret; - u8 *frame; - u16 len, pad = 0; - u32 swheader; int i; + struct sk_buff_head localq; brcmf_dbg(TRACE, "Enter\n"); - frame = (u8 *) (pkt->data); - - /* Add alignment padding, allocate new packet if needed */ - pad = ((unsigned long)frame % BRCMF_SDALIGN); - if (pad) { - if (skb_headroom(pkt) < pad) { - brcmf_dbg(INFO, "insufficient headroom %d for %d pad\n", - skb_headroom(pkt), pad); - bus->sdiodev->bus_if->tx_realloc++; - ret = skb_cow(pkt, BRCMF_SDALIGN); - if (ret) - goto done; - pad = ((unsigned long)frame % BRCMF_SDALIGN); - } - skb_push(pkt, pad); - frame = (u8 *) (pkt->data); - memset(frame, 0, pad + SDPCM_HDRLEN); - } - /* precondition: pad < BRCMF_SDALIGN */ - - /* Hardware tag: 2 byte len followed by 2 byte ~len check (all LE) */ - len = (u16) (pkt->len); - *(__le16 *) frame = cpu_to_le16(len); - *(((__le16 *) frame) + 1) = cpu_to_le16(~len); - - /* Software tag: channel, sequence number, data offset */ - swheader = - ((chan << SDPCM_CHANNEL_SHIFT) & SDPCM_CHANNEL_MASK) | bus->tx_seq | - (((pad + - SDPCM_HDRLEN) << SDPCM_DOFFSET_SHIFT) & SDPCM_DOFFSET_MASK); - - *(((__le32 *) frame) + 1) = cpu_to_le32(swheader); - *(((__le32 *) frame) + 2) = 0; - -#ifdef DEBUG - tx_packets[pkt->priority]++; -#endif - - brcmf_dbg_hex_dump(BRCMF_BYTES_ON() && - ((BRCMF_CTL_ON() && chan == SDPCM_CONTROL_CHANNEL) || - (BRCMF_DATA_ON() && chan != SDPCM_CONTROL_CHANNEL)), - frame, len, "Tx Frame:\n"); - brcmf_dbg_hex_dump(!(BRCMF_BYTES_ON() && - ((BRCMF_CTL_ON() && - chan == SDPCM_CONTROL_CHANNEL) || - (BRCMF_DATA_ON() && - chan != SDPCM_CONTROL_CHANNEL))) && - BRCMF_HDRS_ON(), - frame, min_t(u16, len, 16), "TxHdr:\n"); - - /* Raise len to next SDIO block to eliminate tail command */ - if (bus->roundup && bus->blocksize && (len > bus->blocksize)) { - u16 pad = bus->blocksize - (len % bus->blocksize); - if ((pad <= bus->roundup) && (pad < bus->blocksize)) - len += pad; - } else if (len % BRCMF_SDALIGN) { - len += BRCMF_SDALIGN - (len % BRCMF_SDALIGN); - } - - /* Some controllers have trouble with odd bytes -- round to even */ - if (len & (ALIGNMENT - 1)) - len = roundup(len, ALIGNMENT); + __skb_queue_head_init(&localq); + __skb_queue_tail(&localq, pkt); + ret = brcmf_sdio_txpkt_prep(bus, &localq, chan); + if (ret) + goto done; sdio_claim_host(bus->sdiodev->func[1]); ret = brcmf_sdcard_send_pkt(bus->sdiodev, bus->sdiodev->sbwad, - SDIO_FUNC_2, F2SYNC, pkt); + SDIO_FUNC_2, F2SYNC, &localq); bus->sdcnt.f2txdata++; if (ret < 0) { @@ -1868,8 +1967,8 @@ static int brcmf_sdbrcm_txpkt(struct brcmf_sdio *bus, struct sk_buff *pkt, bus->tx_seq = (bus->tx_seq + 1) % SDPCM_SEQUENCE_WRAP; done: - /* restore pkt buffer pointer before calling tx complete routine */ - skb_pull(pkt, SDPCM_HDRLEN + pad); + brcmf_sdio_txpkt_postp(bus, &localq); + __skb_dequeue_tail(&localq); brcmf_txcomplete(bus->sdiodev->dev, pkt, ret == 0); return ret; } diff --git a/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h b/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h index 09786a539950..2b5407f002e5 100644 --- a/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h +++ b/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h @@ -208,7 +208,7 @@ extern int brcmf_sdio_regrw_helper(struct brcmf_sdio_dev *sdiodev, u32 addr, */ extern int brcmf_sdcard_send_pkt(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, - uint flags, struct sk_buff *pkt); + uint flags, struct sk_buff_head *pktq); extern int brcmf_sdcard_send_buf(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, uint flags, u8 *buf, uint nbytes); diff --git a/include/linux/platform_data/brcmfmac-sdio.h b/include/linux/platform_data/brcmfmac-sdio.h index b7174998c24a..e75dcbf2b230 100644 --- a/include/linux/platform_data/brcmfmac-sdio.h +++ b/include/linux/platform_data/brcmfmac-sdio.h @@ -94,6 +94,10 @@ void __init brcmfmac_init_pdata(void) * Set this to true if the SDIO host controller has higher align requirement * than 32 bytes for each scatterlist item. * + * sd_head_align: alignment requirement for start of data buffer + * + * sd_sgentry_align: length alignment requirement for each sg entry + * * power_on: This function is called by the brcmfmac when the module gets * loaded. This can be particularly useful for low power devices. The platform * spcific routine may for example decide to power up the complete device. @@ -121,6 +125,8 @@ struct brcmfmac_sdio_platform_data { unsigned int oob_irq_nr; unsigned long oob_irq_flags; bool broken_sg_support; + unsigned short sd_head_align; + unsigned short sd_sgentry_align; void (*power_on)(void); void (*power_off)(void); void (*reset)(void); -- cgit v1.2.3-71-gd317 From 26e0ca22c3b85b04f693dd0422f13a61846ccfa9 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Tue, 4 Jun 2013 11:22:30 -0300 Subject: [media] v4l: Renesas R-Car VSP1 driver The VSP1 is a video processing engine that includes a blender, scalers, filters and statistics computation. Configurable data path routing logic allows ordering the internal blocks in a flexible way. Due to the configurable nature of the pipeline the driver implements the media controller API and doesn't use the V4L2 mem-to-mem framework, even though the device usually operates in memory to memory mode. Only the read pixel formatters, up/down scalers, write pixel formatters and LCDC interface are supported at this stage. Signed-off-by: Laurent Pinchart Acked-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- drivers/media/platform/Kconfig | 10 + drivers/media/platform/Makefile | 2 + drivers/media/platform/vsp1/Makefile | 5 + drivers/media/platform/vsp1/vsp1.h | 73 ++ drivers/media/platform/vsp1/vsp1_drv.c | 492 +++++++++++++ drivers/media/platform/vsp1/vsp1_entity.c | 181 +++++ drivers/media/platform/vsp1/vsp1_entity.h | 68 ++ drivers/media/platform/vsp1/vsp1_lif.c | 238 +++++++ drivers/media/platform/vsp1/vsp1_lif.h | 37 + drivers/media/platform/vsp1/vsp1_regs.h | 581 ++++++++++++++++ drivers/media/platform/vsp1/vsp1_rpf.c | 209 ++++++ drivers/media/platform/vsp1/vsp1_rwpf.c | 124 ++++ drivers/media/platform/vsp1/vsp1_rwpf.h | 53 ++ drivers/media/platform/vsp1/vsp1_uds.c | 346 ++++++++++ drivers/media/platform/vsp1/vsp1_uds.h | 40 ++ drivers/media/platform/vsp1/vsp1_video.c | 1071 +++++++++++++++++++++++++++++ drivers/media/platform/vsp1/vsp1_video.h | 144 ++++ drivers/media/platform/vsp1/vsp1_wpf.c | 233 +++++++ include/linux/platform_data/vsp1.h | 25 + 19 files changed, 3932 insertions(+) create mode 100644 drivers/media/platform/vsp1/Makefile create mode 100644 drivers/media/platform/vsp1/vsp1.h create mode 100644 drivers/media/platform/vsp1/vsp1_drv.c create mode 100644 drivers/media/platform/vsp1/vsp1_entity.c create mode 100644 drivers/media/platform/vsp1/vsp1_entity.h create mode 100644 drivers/media/platform/vsp1/vsp1_lif.c create mode 100644 drivers/media/platform/vsp1/vsp1_lif.h create mode 100644 drivers/media/platform/vsp1/vsp1_regs.h create mode 100644 drivers/media/platform/vsp1/vsp1_rpf.c create mode 100644 drivers/media/platform/vsp1/vsp1_rwpf.c create mode 100644 drivers/media/platform/vsp1/vsp1_rwpf.h create mode 100644 drivers/media/platform/vsp1/vsp1_uds.c create mode 100644 drivers/media/platform/vsp1/vsp1_uds.h create mode 100644 drivers/media/platform/vsp1/vsp1_video.c create mode 100644 drivers/media/platform/vsp1/vsp1_video.h create mode 100644 drivers/media/platform/vsp1/vsp1_wpf.c create mode 100644 include/linux/platform_data/vsp1.h (limited to 'include/linux/platform_data') diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 08de865cc399..9a44e06c5b31 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -210,6 +210,16 @@ config VIDEO_SH_VEU Support for the Video Engine Unit (VEU) on SuperH and SH-Mobile SoCs. +config VIDEO_RENESAS_VSP1 + tristate "Renesas VSP1 Video Processing Engine" + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + ---help--- + This is a V4L2 driver for the Renesas VSP1 video processing engine. + + To compile this driver as a module, choose M here: the module + will be called vsp1. + endif # V4L_MEM2MEM_DRIVERS menuconfig V4L_TEST_DRIVERS diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index eee28dd78d7d..4e4da482c522 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -46,6 +46,8 @@ obj-$(CONFIG_VIDEO_SH_VOU) += sh_vou.o obj-$(CONFIG_SOC_CAMERA) += soc_camera/ +obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1/ + obj-y += davinci/ obj-$(CONFIG_ARCH_OMAP) += omap/ diff --git a/drivers/media/platform/vsp1/Makefile b/drivers/media/platform/vsp1/Makefile new file mode 100644 index 000000000000..4da226169e15 --- /dev/null +++ b/drivers/media/platform/vsp1/Makefile @@ -0,0 +1,5 @@ +vsp1-y := vsp1_drv.o vsp1_entity.o vsp1_video.o +vsp1-y += vsp1_rpf.o vsp1_rwpf.o vsp1_wpf.o +vsp1-y += vsp1_lif.o vsp1_uds.o + +obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1.o diff --git a/drivers/media/platform/vsp1/vsp1.h b/drivers/media/platform/vsp1/vsp1.h new file mode 100644 index 000000000000..11ac94bec3a3 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1.h @@ -0,0 +1,73 @@ +/* + * vsp1.h -- R-Car VSP1 Driver + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * 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. + */ +#ifndef __VSP1_H__ +#define __VSP1_H__ + +#include +#include +#include +#include + +#include +#include +#include + +#include "vsp1_regs.h" + +struct clk; +struct device; + +struct vsp1_platform_data; +struct vsp1_lif; +struct vsp1_rwpf; +struct vsp1_uds; + +#define VPS1_MAX_RPF 5 +#define VPS1_MAX_UDS 3 +#define VPS1_MAX_WPF 4 + +struct vsp1_device { + struct device *dev; + struct vsp1_platform_data *pdata; + + void __iomem *mmio; + struct clk *clock; + + struct mutex lock; + int ref_count; + + struct vsp1_lif *lif; + struct vsp1_rwpf *rpf[VPS1_MAX_RPF]; + struct vsp1_uds *uds[VPS1_MAX_UDS]; + struct vsp1_rwpf *wpf[VPS1_MAX_WPF]; + + struct list_head entities; + + struct v4l2_device v4l2_dev; + struct media_device media_dev; +}; + +struct vsp1_device *vsp1_device_get(struct vsp1_device *vsp1); +void vsp1_device_put(struct vsp1_device *vsp1); + +static inline u32 vsp1_read(struct vsp1_device *vsp1, u32 reg) +{ + return ioread32(vsp1->mmio + reg); +} + +static inline void vsp1_write(struct vsp1_device *vsp1, u32 reg, u32 data) +{ + iowrite32(data, vsp1->mmio + reg); +} + +#endif /* __VSP1_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_drv.c b/drivers/media/platform/vsp1/vsp1_drv.c new file mode 100644 index 000000000000..e58e49c88415 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_drv.c @@ -0,0 +1,492 @@ +/* + * vsp1_drv.c -- R-Car VSP1 Driver + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "vsp1.h" +#include "vsp1_lif.h" +#include "vsp1_rwpf.h" +#include "vsp1_uds.h" + +/* ----------------------------------------------------------------------------- + * Interrupt Handling + */ + +static irqreturn_t vsp1_irq_handler(int irq, void *data) +{ + u32 mask = VI6_WFP_IRQ_STA_DFE | VI6_WFP_IRQ_STA_FRE; + struct vsp1_device *vsp1 = data; + irqreturn_t ret = IRQ_NONE; + unsigned int i; + + for (i = 0; i < VPS1_MAX_WPF; ++i) { + struct vsp1_rwpf *wpf = vsp1->wpf[i]; + struct vsp1_pipeline *pipe; + u32 status; + + if (wpf == NULL) + continue; + + pipe = to_vsp1_pipeline(&wpf->entity.subdev.entity); + status = vsp1_read(vsp1, VI6_WPF_IRQ_STA(i)); + vsp1_write(vsp1, VI6_WPF_IRQ_STA(i), ~status & mask); + + if (status & VI6_WFP_IRQ_STA_FRE) { + vsp1_pipeline_frame_end(pipe); + ret = IRQ_HANDLED; + } + } + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Entities + */ + +/* + * vsp1_create_links - Create links from all sources to the given sink + * + * This function creates media links from all valid sources to the given sink + * pad. Links that would be invalid according to the VSP1 hardware capabilities + * are skipped. Those include all links + * + * - from a UDS to a UDS (UDS entities can't be chained) + * - from an entity to itself (no loops are allowed) + */ +static int vsp1_create_links(struct vsp1_device *vsp1, struct vsp1_entity *sink) +{ + struct media_entity *entity = &sink->subdev.entity; + struct vsp1_entity *source; + unsigned int pad; + int ret; + + list_for_each_entry(source, &vsp1->entities, list_dev) { + u32 flags; + + if (source->type == sink->type) + continue; + + if (source->type == VSP1_ENTITY_LIF || + source->type == VSP1_ENTITY_WPF) + continue; + + flags = source->type == VSP1_ENTITY_RPF && + sink->type == VSP1_ENTITY_WPF && + source->index == sink->index + ? MEDIA_LNK_FL_ENABLED : 0; + + for (pad = 0; pad < entity->num_pads; ++pad) { + if (!(entity->pads[pad].flags & MEDIA_PAD_FL_SINK)) + continue; + + ret = media_entity_create_link(&source->subdev.entity, + source->source_pad, + entity, pad, flags); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static void vsp1_destroy_entities(struct vsp1_device *vsp1) +{ + struct vsp1_entity *entity; + struct vsp1_entity *next; + + list_for_each_entry_safe(entity, next, &vsp1->entities, list_dev) { + list_del(&entity->list_dev); + vsp1_entity_destroy(entity); + } + + v4l2_device_unregister(&vsp1->v4l2_dev); + media_device_unregister(&vsp1->media_dev); +} + +static int vsp1_create_entities(struct vsp1_device *vsp1) +{ + struct media_device *mdev = &vsp1->media_dev; + struct v4l2_device *vdev = &vsp1->v4l2_dev; + struct vsp1_entity *entity; + unsigned int i; + int ret; + + mdev->dev = vsp1->dev; + strlcpy(mdev->model, "VSP1", sizeof(mdev->model)); + ret = media_device_register(mdev); + if (ret < 0) { + dev_err(vsp1->dev, "media device registration failed (%d)\n", + ret); + return ret; + } + + vdev->mdev = mdev; + ret = v4l2_device_register(vsp1->dev, vdev); + if (ret < 0) { + dev_err(vsp1->dev, "V4L2 device registration failed (%d)\n", + ret); + goto done; + } + + /* Instantiate all the entities. */ + if (vsp1->pdata->features & VSP1_HAS_LIF) { + vsp1->lif = vsp1_lif_create(vsp1); + if (IS_ERR(vsp1->lif)) { + ret = PTR_ERR(vsp1->lif); + goto done; + } + + list_add_tail(&vsp1->lif->entity.list_dev, &vsp1->entities); + } + + for (i = 0; i < vsp1->pdata->rpf_count; ++i) { + struct vsp1_rwpf *rpf; + + rpf = vsp1_rpf_create(vsp1, i); + if (IS_ERR(rpf)) { + ret = PTR_ERR(rpf); + goto done; + } + + vsp1->rpf[i] = rpf; + list_add_tail(&rpf->entity.list_dev, &vsp1->entities); + } + + for (i = 0; i < vsp1->pdata->uds_count; ++i) { + struct vsp1_uds *uds; + + uds = vsp1_uds_create(vsp1, i); + if (IS_ERR(uds)) { + ret = PTR_ERR(uds); + goto done; + } + + vsp1->uds[i] = uds; + list_add_tail(&uds->entity.list_dev, &vsp1->entities); + } + + for (i = 0; i < vsp1->pdata->wpf_count; ++i) { + struct vsp1_rwpf *wpf; + + wpf = vsp1_wpf_create(vsp1, i); + if (IS_ERR(wpf)) { + ret = PTR_ERR(wpf); + goto done; + } + + vsp1->wpf[i] = wpf; + list_add_tail(&wpf->entity.list_dev, &vsp1->entities); + } + + /* Create links. */ + list_for_each_entry(entity, &vsp1->entities, list_dev) { + if (entity->type == VSP1_ENTITY_LIF || + entity->type == VSP1_ENTITY_RPF) + continue; + + ret = vsp1_create_links(vsp1, entity); + if (ret < 0) + goto done; + } + + if (vsp1->pdata->features & VSP1_HAS_LIF) { + ret = media_entity_create_link( + &vsp1->wpf[0]->entity.subdev.entity, RWPF_PAD_SOURCE, + &vsp1->lif->entity.subdev.entity, LIF_PAD_SINK, 0); + if (ret < 0) + return ret; + } + + /* Register all subdevs. */ + list_for_each_entry(entity, &vsp1->entities, list_dev) { + ret = v4l2_device_register_subdev(&vsp1->v4l2_dev, + &entity->subdev); + if (ret < 0) + goto done; + } + + ret = v4l2_device_register_subdev_nodes(&vsp1->v4l2_dev); + +done: + if (ret < 0) + vsp1_destroy_entities(vsp1); + + return ret; +} + +static int vsp1_device_init(struct vsp1_device *vsp1) +{ + unsigned int i; + u32 status; + + /* Reset any channel that might be running. */ + status = vsp1_read(vsp1, VI6_STATUS); + + for (i = 0; i < VPS1_MAX_WPF; ++i) { + unsigned int timeout; + + if (!(status & VI6_STATUS_SYS_ACT(i))) + continue; + + vsp1_write(vsp1, VI6_SRESET, VI6_SRESET_SRTS(i)); + for (timeout = 10; timeout > 0; --timeout) { + status = vsp1_read(vsp1, VI6_STATUS); + if (!(status & VI6_STATUS_SYS_ACT(i))) + break; + + usleep_range(1000, 2000); + } + + if (!timeout) { + dev_err(vsp1->dev, "failed to reset wpf.%u\n", i); + return -ETIMEDOUT; + } + } + + vsp1_write(vsp1, VI6_CLK_DCSWT, (8 << VI6_CLK_DCSWT_CSTPW_SHIFT) | + (8 << VI6_CLK_DCSWT_CSTRW_SHIFT)); + + for (i = 0; i < VPS1_MAX_RPF; ++i) + vsp1_write(vsp1, VI6_DPR_RPF_ROUTE(i), VI6_DPR_NODE_UNUSED); + + for (i = 0; i < VPS1_MAX_UDS; ++i) + vsp1_write(vsp1, VI6_DPR_UDS_ROUTE(i), VI6_DPR_NODE_UNUSED); + + vsp1_write(vsp1, VI6_DPR_SRU_ROUTE, VI6_DPR_NODE_UNUSED); + vsp1_write(vsp1, VI6_DPR_LUT_ROUTE, VI6_DPR_NODE_UNUSED); + vsp1_write(vsp1, VI6_DPR_CLU_ROUTE, VI6_DPR_NODE_UNUSED); + vsp1_write(vsp1, VI6_DPR_HST_ROUTE, VI6_DPR_NODE_UNUSED); + vsp1_write(vsp1, VI6_DPR_HSI_ROUTE, VI6_DPR_NODE_UNUSED); + vsp1_write(vsp1, VI6_DPR_BRU_ROUTE, VI6_DPR_NODE_UNUSED); + + vsp1_write(vsp1, VI6_DPR_HGO_SMPPT, (7 << VI6_DPR_SMPPT_TGW_SHIFT) | + (VI6_DPR_NODE_UNUSED << VI6_DPR_SMPPT_PT_SHIFT)); + vsp1_write(vsp1, VI6_DPR_HGT_SMPPT, (7 << VI6_DPR_SMPPT_TGW_SHIFT) | + (VI6_DPR_NODE_UNUSED << VI6_DPR_SMPPT_PT_SHIFT)); + + return 0; +} + +/* + * vsp1_device_get - Acquire the VSP1 device + * + * Increment the VSP1 reference count and initialize the device if the first + * reference is taken. + * + * Return a pointer to the VSP1 device or NULL if an error occured. + */ +struct vsp1_device *vsp1_device_get(struct vsp1_device *vsp1) +{ + struct vsp1_device *__vsp1 = vsp1; + int ret; + + mutex_lock(&vsp1->lock); + if (vsp1->ref_count > 0) + goto done; + + ret = clk_prepare_enable(vsp1->clock); + if (ret < 0) { + __vsp1 = NULL; + goto done; + } + + ret = vsp1_device_init(vsp1); + if (ret < 0) { + clk_disable_unprepare(vsp1->clock); + __vsp1 = NULL; + goto done; + } + +done: + if (__vsp1) + vsp1->ref_count++; + + mutex_unlock(&vsp1->lock); + return __vsp1; +} + +/* + * vsp1_device_put - Release the VSP1 device + * + * Decrement the VSP1 reference count and cleanup the device if the last + * reference is released. + */ +void vsp1_device_put(struct vsp1_device *vsp1) +{ + mutex_lock(&vsp1->lock); + + if (--vsp1->ref_count == 0) + clk_disable_unprepare(vsp1->clock); + + mutex_unlock(&vsp1->lock); +} + +/* ----------------------------------------------------------------------------- + * Power Management + */ + +#ifdef CONFIG_PM_SLEEP +static int vsp1_pm_suspend(struct device *dev) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + + WARN_ON(mutex_is_locked(&vsp1->lock)); + + if (vsp1->ref_count == 0) + return 0; + + clk_disable_unprepare(vsp1->clock); + return 0; +} + +static int vsp1_pm_resume(struct device *dev) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + + WARN_ON(mutex_is_locked(&vsp1->lock)); + + if (vsp1->ref_count) + return 0; + + return clk_prepare_enable(vsp1->clock); +} +#endif + +static const struct dev_pm_ops vsp1_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(vsp1_pm_suspend, vsp1_pm_resume) +}; + +/* ----------------------------------------------------------------------------- + * Platform Driver + */ + +static struct vsp1_platform_data * +vsp1_get_platform_data(struct platform_device *pdev) +{ + struct vsp1_platform_data *pdata = pdev->dev.platform_data; + + if (pdata == NULL) { + dev_err(&pdev->dev, "missing platform data\n"); + return NULL; + } + + if (pdata->rpf_count <= 0 || pdata->rpf_count > VPS1_MAX_RPF) { + dev_err(&pdev->dev, "invalid number of RPF (%u)\n", + pdata->rpf_count); + return NULL; + } + + if (pdata->uds_count <= 0 || pdata->uds_count > VPS1_MAX_UDS) { + dev_err(&pdev->dev, "invalid number of UDS (%u)\n", + pdata->uds_count); + return NULL; + } + + if (pdata->wpf_count <= 0 || pdata->wpf_count > VPS1_MAX_WPF) { + dev_err(&pdev->dev, "invalid number of WPF (%u)\n", + pdata->wpf_count); + return NULL; + } + + return pdata; +} + +static int vsp1_probe(struct platform_device *pdev) +{ + struct vsp1_device *vsp1; + struct resource *irq; + struct resource *io; + int ret; + + vsp1 = devm_kzalloc(&pdev->dev, sizeof(*vsp1), GFP_KERNEL); + if (vsp1 == NULL) + return -ENOMEM; + + vsp1->dev = &pdev->dev; + mutex_init(&vsp1->lock); + INIT_LIST_HEAD(&vsp1->entities); + + vsp1->pdata = vsp1_get_platform_data(pdev); + if (vsp1->pdata == NULL) + return -ENODEV; + + /* I/O, IRQ and clock resources */ + io = platform_get_resource(pdev, IORESOURCE_MEM, 0); + vsp1->mmio = devm_ioremap_resource(&pdev->dev, io); + if (IS_ERR((void *)vsp1->mmio)) + return PTR_ERR((void *)vsp1->mmio); + + vsp1->clock = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(vsp1->clock)) { + dev_err(&pdev->dev, "failed to get clock\n"); + return PTR_ERR(vsp1->clock); + } + + irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!irq) { + dev_err(&pdev->dev, "missing IRQ\n"); + return -EINVAL; + } + + ret = devm_request_irq(&pdev->dev, irq->start, vsp1_irq_handler, + IRQF_SHARED, dev_name(&pdev->dev), vsp1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to request IRQ\n"); + return ret; + } + + /* Instanciate entities */ + ret = vsp1_create_entities(vsp1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to create entities\n"); + return ret; + } + + platform_set_drvdata(pdev, vsp1); + + return 0; +} + +static int vsp1_remove(struct platform_device *pdev) +{ + struct vsp1_device *vsp1 = platform_get_drvdata(pdev); + + vsp1_destroy_entities(vsp1); + + return 0; +} + +static struct platform_driver vsp1_platform_driver = { + .probe = vsp1_probe, + .remove = vsp1_remove, + .driver = { + .owner = THIS_MODULE, + .name = "vsp1", + .pm = &vsp1_pm_ops, + }, +}; + +module_platform_driver(vsp1_platform_driver); + +MODULE_ALIAS("vsp1"); +MODULE_AUTHOR("Laurent Pinchart "); +MODULE_DESCRIPTION("Renesas VSP1 Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/vsp1/vsp1_entity.c b/drivers/media/platform/vsp1/vsp1_entity.c new file mode 100644 index 000000000000..9028f9d524f4 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_entity.c @@ -0,0 +1,181 @@ +/* + * vsp1_entity.c -- R-Car VSP1 Base Entity + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * 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. + */ + +#include +#include + +#include +#include + +#include "vsp1.h" +#include "vsp1_entity.h" + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +struct v4l2_mbus_framefmt * +vsp1_entity_get_pad_format(struct vsp1_entity *entity, + struct v4l2_subdev_fh *fh, + unsigned int pad, u32 which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(fh, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &entity->formats[pad]; + default: + return NULL; + } +} + +/* + * vsp1_entity_init_formats - Initialize formats on all pads + * @subdev: V4L2 subdevice + * @fh: V4L2 subdev file handle + * + * Initialize all pad formats with default values. If fh is not NULL, try + * formats are initialized on the file handle. Otherwise active formats are + * initialized on the device. + */ +void vsp1_entity_init_formats(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format; + unsigned int pad; + + for (pad = 0; pad < subdev->entity.num_pads - 1; ++pad) { + memset(&format, 0, sizeof(format)); + + format.pad = pad; + format.which = fh ? V4L2_SUBDEV_FORMAT_TRY + : V4L2_SUBDEV_FORMAT_ACTIVE; + + v4l2_subdev_call(subdev, pad, set_fmt, fh, &format); + } +} + +static int vsp1_entity_open(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh) +{ + vsp1_entity_init_formats(subdev, fh); + + return 0; +} + +const struct v4l2_subdev_internal_ops vsp1_subdev_internal_ops = { + .open = vsp1_entity_open, +}; + +/* ----------------------------------------------------------------------------- + * Media Operations + */ + +static int vsp1_entity_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct vsp1_entity *source; + + if (!(local->flags & MEDIA_PAD_FL_SOURCE)) + return 0; + + source = container_of(local->entity, struct vsp1_entity, subdev.entity); + + if (!source->route) + return 0; + + if (flags & MEDIA_LNK_FL_ENABLED) { + if (source->sink) + return -EBUSY; + source->sink = remote->entity; + } else { + source->sink = NULL; + } + + return 0; +} + +const struct media_entity_operations vsp1_media_ops = { + .link_setup = vsp1_entity_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +/* ----------------------------------------------------------------------------- + * Initialization + */ + +int vsp1_entity_init(struct vsp1_device *vsp1, struct vsp1_entity *entity, + unsigned int num_pads) +{ + static const struct { + unsigned int id; + unsigned int reg; + } routes[] = { + { VI6_DPR_NODE_LIF, 0 }, + { VI6_DPR_NODE_RPF(0), VI6_DPR_RPF_ROUTE(0) }, + { VI6_DPR_NODE_RPF(1), VI6_DPR_RPF_ROUTE(1) }, + { VI6_DPR_NODE_RPF(2), VI6_DPR_RPF_ROUTE(2) }, + { VI6_DPR_NODE_RPF(3), VI6_DPR_RPF_ROUTE(3) }, + { VI6_DPR_NODE_RPF(4), VI6_DPR_RPF_ROUTE(4) }, + { VI6_DPR_NODE_UDS(0), VI6_DPR_UDS_ROUTE(0) }, + { VI6_DPR_NODE_UDS(1), VI6_DPR_UDS_ROUTE(1) }, + { VI6_DPR_NODE_UDS(2), VI6_DPR_UDS_ROUTE(2) }, + { VI6_DPR_NODE_WPF(0), 0 }, + { VI6_DPR_NODE_WPF(1), 0 }, + { VI6_DPR_NODE_WPF(2), 0 }, + { VI6_DPR_NODE_WPF(3), 0 }, + }; + + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(routes); ++i) { + if (routes[i].id == entity->id) { + entity->route = routes[i].reg; + break; + } + } + + if (i == ARRAY_SIZE(routes)) + return -EINVAL; + + entity->vsp1 = vsp1; + entity->source_pad = num_pads - 1; + + /* Allocate formats and pads. */ + entity->formats = devm_kzalloc(vsp1->dev, + num_pads * sizeof(*entity->formats), + GFP_KERNEL); + if (entity->formats == NULL) + return -ENOMEM; + + entity->pads = devm_kzalloc(vsp1->dev, num_pads * sizeof(*entity->pads), + GFP_KERNEL); + if (entity->pads == NULL) + return -ENOMEM; + + /* Initialize pads. */ + for (i = 0; i < num_pads - 1; ++i) + entity->pads[i].flags = MEDIA_PAD_FL_SINK; + + entity->pads[num_pads - 1].flags = MEDIA_PAD_FL_SOURCE; + + /* Initialize the media entity. */ + return media_entity_init(&entity->subdev.entity, num_pads, + entity->pads, 0); +} + +void vsp1_entity_destroy(struct vsp1_entity *entity) +{ + media_entity_cleanup(&entity->subdev.entity); +} diff --git a/drivers/media/platform/vsp1/vsp1_entity.h b/drivers/media/platform/vsp1/vsp1_entity.h new file mode 100644 index 000000000000..c4feab2cbb81 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_entity.h @@ -0,0 +1,68 @@ +/* + * vsp1_entity.h -- R-Car VSP1 Base Entity + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * 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. + */ +#ifndef __VSP1_ENTITY_H__ +#define __VSP1_ENTITY_H__ + +#include + +#include + +struct vsp1_device; + +enum vsp1_entity_type { + VSP1_ENTITY_LIF, + VSP1_ENTITY_RPF, + VSP1_ENTITY_UDS, + VSP1_ENTITY_WPF, +}; + +struct vsp1_entity { + struct vsp1_device *vsp1; + + enum vsp1_entity_type type; + unsigned int index; + unsigned int id; + unsigned int route; + + struct list_head list_dev; + struct list_head list_pipe; + + struct media_pad *pads; + unsigned int source_pad; + + struct media_entity *sink; + + struct v4l2_subdev subdev; + struct v4l2_mbus_framefmt *formats; +}; + +static inline struct vsp1_entity *to_vsp1_entity(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_entity, subdev); +} + +int vsp1_entity_init(struct vsp1_device *vsp1, struct vsp1_entity *entity, + unsigned int num_pads); +void vsp1_entity_destroy(struct vsp1_entity *entity); + +extern const struct v4l2_subdev_internal_ops vsp1_subdev_internal_ops; +extern const struct media_entity_operations vsp1_media_ops; + +struct v4l2_mbus_framefmt * +vsp1_entity_get_pad_format(struct vsp1_entity *entity, + struct v4l2_subdev_fh *fh, + unsigned int pad, u32 which); +void vsp1_entity_init_formats(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh); + +#endif /* __VSP1_ENTITY_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_lif.c b/drivers/media/platform/vsp1/vsp1_lif.c new file mode 100644 index 000000000000..74a32e69ef10 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_lif.c @@ -0,0 +1,238 @@ +/* + * vsp1_lif.c -- R-Car VSP1 LCD Controller Interface + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * 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. + */ + +#include +#include + +#include + +#include "vsp1.h" +#include "vsp1_lif.h" + +#define LIF_MIN_SIZE 2U +#define LIF_MAX_SIZE 2048U + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline u32 vsp1_lif_read(struct vsp1_lif *lif, u32 reg) +{ + return vsp1_read(lif->entity.vsp1, reg); +} + +static inline void vsp1_lif_write(struct vsp1_lif *lif, u32 reg, u32 data) +{ + vsp1_write(lif->entity.vsp1, reg, data); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Core Operations + */ + +static int lif_s_stream(struct v4l2_subdev *subdev, int enable) +{ + const struct v4l2_mbus_framefmt *format; + struct vsp1_lif *lif = to_lif(subdev); + unsigned int hbth = 1300; + unsigned int obth = 400; + unsigned int lbth = 200; + + if (!enable) { + vsp1_lif_write(lif, VI6_LIF_CTRL, 0); + return 0; + } + + format = &lif->entity.formats[LIF_PAD_SOURCE]; + + obth = min(obth, (format->width + 1) / 2 * format->height - 4); + + vsp1_lif_write(lif, VI6_LIF_CSBTH, + (hbth << VI6_LIF_CSBTH_HBTH_SHIFT) | + (lbth << VI6_LIF_CSBTH_LBTH_SHIFT)); + + vsp1_lif_write(lif, VI6_LIF_CTRL, + (obth << VI6_LIF_CTRL_OBTH_SHIFT) | + (format->code == 0 ? VI6_LIF_CTRL_CFMT : 0) | + VI6_LIF_CTRL_REQSEL | VI6_LIF_CTRL_LIF_EN); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Pad Operations + */ + +static int lif_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + static const unsigned int codes[] = { + V4L2_MBUS_FMT_ARGB8888_1X32, + V4L2_MBUS_FMT_AYUV8_1X32, + }; + + if (code->pad == LIF_PAD_SINK) { + if (code->index >= ARRAY_SIZE(codes)) + return -EINVAL; + + code->code = codes[code->index]; + } else { + struct v4l2_mbus_framefmt *format; + + /* The LIF can't perform format conversion, the sink format is + * always identical to the source format. + */ + if (code->index) + return -EINVAL; + + format = v4l2_subdev_get_try_format(fh, LIF_PAD_SINK); + code->code = format->code; + } + + return 0; +} + +static int lif_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct v4l2_mbus_framefmt *format; + + format = v4l2_subdev_get_try_format(fh, LIF_PAD_SINK); + + if (fse->index || fse->code != format->code) + return -EINVAL; + + if (fse->pad == LIF_PAD_SINK) { + fse->min_width = LIF_MIN_SIZE; + fse->max_width = LIF_MAX_SIZE; + fse->min_height = LIF_MIN_SIZE; + fse->max_height = LIF_MAX_SIZE; + } else { + fse->min_width = format->width; + fse->max_width = format->width; + fse->min_height = format->height; + fse->max_height = format->height; + } + + return 0; +} + +static int lif_get_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_lif *lif = to_lif(subdev); + + fmt->format = *vsp1_entity_get_pad_format(&lif->entity, fh, fmt->pad, + fmt->which); + + return 0; +} + +static int lif_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_lif *lif = to_lif(subdev); + struct v4l2_mbus_framefmt *format; + + /* Default to YUV if the requested format is not supported. */ + if (fmt->format.code != V4L2_MBUS_FMT_ARGB8888_1X32 && + fmt->format.code != V4L2_MBUS_FMT_AYUV8_1X32) + fmt->format.code = V4L2_MBUS_FMT_AYUV8_1X32; + + format = vsp1_entity_get_pad_format(&lif->entity, fh, fmt->pad, + fmt->which); + + if (fmt->pad == LIF_PAD_SOURCE) { + /* The LIF source format is always identical to its sink + * format. + */ + fmt->format = *format; + return 0; + } + + format->code = fmt->format.code; + format->width = clamp_t(unsigned int, fmt->format.width, + LIF_MIN_SIZE, LIF_MAX_SIZE); + format->height = clamp_t(unsigned int, fmt->format.height, + LIF_MIN_SIZE, LIF_MAX_SIZE); + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + + fmt->format = *format; + + /* Propagate the format to the source pad. */ + format = vsp1_entity_get_pad_format(&lif->entity, fh, LIF_PAD_SOURCE, + fmt->which); + *format = fmt->format; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static struct v4l2_subdev_video_ops lif_video_ops = { + .s_stream = lif_s_stream, +}; + +static struct v4l2_subdev_pad_ops lif_pad_ops = { + .enum_mbus_code = lif_enum_mbus_code, + .enum_frame_size = lif_enum_frame_size, + .get_fmt = lif_get_format, + .set_fmt = lif_set_format, +}; + +static struct v4l2_subdev_ops lif_ops = { + .video = &lif_video_ops, + .pad = &lif_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_lif *vsp1_lif_create(struct vsp1_device *vsp1) +{ + struct v4l2_subdev *subdev; + struct vsp1_lif *lif; + int ret; + + lif = devm_kzalloc(vsp1->dev, sizeof(*lif), GFP_KERNEL); + if (lif == NULL) + return ERR_PTR(-ENOMEM); + + lif->entity.type = VSP1_ENTITY_LIF; + lif->entity.id = VI6_DPR_NODE_LIF; + + ret = vsp1_entity_init(vsp1, &lif->entity, 2); + if (ret < 0) + return ERR_PTR(ret); + + /* Initialize the V4L2 subdev. */ + subdev = &lif->entity.subdev; + v4l2_subdev_init(subdev, &lif_ops); + + subdev->entity.ops = &vsp1_media_ops; + subdev->internal_ops = &vsp1_subdev_internal_ops; + snprintf(subdev->name, sizeof(subdev->name), "%s lif", + dev_name(vsp1->dev)); + v4l2_set_subdevdata(subdev, lif); + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + vsp1_entity_init_formats(subdev, NULL); + + return lif; +} diff --git a/drivers/media/platform/vsp1/vsp1_lif.h b/drivers/media/platform/vsp1/vsp1_lif.h new file mode 100644 index 000000000000..89b93af56fdc --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_lif.h @@ -0,0 +1,37 @@ +/* + * vsp1_lif.h -- R-Car VSP1 LCD Controller Interface + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * 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. + */ +#ifndef __VSP1_LIF_H__ +#define __VSP1_LIF_H__ + +#include +#include + +#include "vsp1_entity.h" + +struct vsp1_device; + +#define LIF_PAD_SINK 0 +#define LIF_PAD_SOURCE 1 + +struct vsp1_lif { + struct vsp1_entity entity; +}; + +static inline struct vsp1_lif *to_lif(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_lif, entity.subdev); +} + +struct vsp1_lif *vsp1_lif_create(struct vsp1_device *vsp1); + +#endif /* __VSP1_LIF_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_regs.h b/drivers/media/platform/vsp1/vsp1_regs.h new file mode 100644 index 000000000000..1d3304f1365b --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_regs.h @@ -0,0 +1,581 @@ +/* + * vsp1_regs.h -- R-Car VSP1 Registers Definitions + * + * Copyright (C) 2013 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * 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. + */ + +#ifndef __VSP1_REGS_H__ +#define __VSP1_REGS_H__ + +/* ----------------------------------------------------------------------------- + * General Control Registers + */ + +#define VI6_CMD(n) (0x0000 + (n) * 4) +#define VI6_CMD_STRCMD (1 << 0) + +#define VI6_CLK_DCSWT 0x0018 +#define VI6_CLK_DCSWT_CSTPW_MASK (0xff << 8) +#define VI6_CLK_DCSWT_CSTPW_SHIFT 8 +#define VI6_CLK_DCSWT_CSTRW_MASK (0xff << 0) +#define VI6_CLK_DCSWT_CSTRW_SHIFT 0 + +#define VI6_SRESET 0x0028 +#define VI6_SRESET_SRTS(n) (1 << (n)) + +#define VI6_STATUS 0x0038 +#define VI6_STATUS_SYS_ACT(n) (1 << ((n) + 8)) + +#define VI6_WPF_IRQ_ENB(n) (0x0048 + (n) * 12) +#define VI6_WFP_IRQ_ENB_DFEE (1 << 1) +#define VI6_WFP_IRQ_ENB_FREE (1 << 0) + +#define VI6_WPF_IRQ_STA(n) (0x004c + (n) * 12) +#define VI6_WFP_IRQ_STA_DFE (1 << 1) +#define VI6_WFP_IRQ_STA_FRE (1 << 0) + +#define VI6_DISP_IRQ_ENB 0x0078 +#define VI6_DISP_IRQ_ENB_DSTE (1 << 8) +#define VI6_DISP_IRQ_ENB_MAEE (1 << 5) +#define VI6_DISP_IRQ_ENB_LNEE(n) (1 << ((n) + 4)) + +#define VI6_DISP_IRQ_STA 0x007c +#define VI6_DISP_IRQ_STA_DSE (1 << 8) +#define VI6_DISP_IRQ_STA_MAE (1 << 5) +#define VI6_DISP_IRQ_STA_LNE(n) (1 << ((n) + 4)) + +#define VI6_WPF_LINE_COUNT(n) (0x0084 + (n) * 4) +#define VI6_WPF_LINE_COUNT_MASK (0x1fffff << 0) + +/* ----------------------------------------------------------------------------- + * Display List Control Registers + */ + +#define VI6_DL_CTRL 0x0100 +#define VI6_DL_CTRL_AR_WAIT_MASK (0xffff << 16) +#define VI6_DL_CTRL_AR_WAIT_SHIFT 16 +#define VI6_DL_CTRL_DC2 (1 << 12) +#define VI6_DL_CTRL_DC1 (1 << 8) +#define VI6_DL_CTRL_DC0 (1 << 4) +#define VI6_DL_CTRL_CFM0 (1 << 2) +#define VI6_DL_CTRL_NH0 (1 << 1) +#define VI6_DL_CTRL_DLE (1 << 0) + +#define VI6_DL_HDR_ADDR(n) (0x0104 + (n) * 4) + +#define VI6_DL_SWAP 0x0114 +#define VI6_DL_SWAP_LWS (1 << 2) +#define VI6_DL_SWAP_WDS (1 << 1) +#define VI6_DL_SWAP_BTS (1 << 0) + +#define VI6_DL_EXT_CTRL 0x011c +#define VI6_DL_EXT_CTRL_NWE (1 << 16) +#define VI6_DL_EXT_CTRL_POLINT_MASK (0x3f << 8) +#define VI6_DL_EXT_CTRL_POLINT_SHIFT 8 +#define VI6_DL_EXT_CTRL_DLPRI (1 << 5) +#define VI6_DL_EXT_CTRL_EXPRI (1 << 4) +#define VI6_DL_EXT_CTRL_EXT (1 << 0) + +#define VI6_DL_BODY_SIZE 0x0120 +#define VI6_DL_BODY_SIZE_UPD (1 << 24) +#define VI6_DL_BODY_SIZE_BS_MASK (0x1ffff << 0) +#define VI6_DL_BODY_SIZE_BS_SHIFT 0 + +/* ----------------------------------------------------------------------------- + * RPF Control Registers + */ + +#define VI6_RPF_OFFSET 0x100 + +#define VI6_RPF_SRC_BSIZE 0x0300 +#define VI6_RPF_SRC_BSIZE_BHSIZE_MASK (0x1fff << 16) +#define VI6_RPF_SRC_BSIZE_BHSIZE_SHIFT 16 +#define VI6_RPF_SRC_BSIZE_BVSIZE_MASK (0x1fff << 0) +#define VI6_RPF_SRC_BSIZE_BVSIZE_SHIFT 0 + +#define VI6_RPF_SRC_ESIZE 0x0304 +#define VI6_RPF_SRC_ESIZE_EHSIZE_MASK (0x1fff << 16) +#define VI6_RPF_SRC_ESIZE_EHSIZE_SHIFT 16 +#define VI6_RPF_SRC_ESIZE_EVSIZE_MASK (0x1fff << 0) +#define VI6_RPF_SRC_ESIZE_EVSIZE_SHIFT 0 + +#define VI6_RPF_INFMT 0x0308 +#define VI6_RPF_INFMT_VIR (1 << 28) +#define VI6_RPF_INFMT_CIPM (1 << 16) +#define VI6_RPF_INFMT_SPYCS (1 << 15) +#define VI6_RPF_INFMT_SPUVS (1 << 14) +#define VI6_RPF_INFMT_CEXT_ZERO (0 << 12) +#define VI6_RPF_INFMT_CEXT_EXT (1 << 12) +#define VI6_RPF_INFMT_CEXT_ONE (2 << 12) +#define VI6_RPF_INFMT_CEXT_MASK (3 << 12) +#define VI6_RPF_INFMT_RDTM_BT601 (0 << 9) +#define VI6_RPF_INFMT_RDTM_BT601_EXT (1 << 9) +#define VI6_RPF_INFMT_RDTM_BT709 (2 << 9) +#define VI6_RPF_INFMT_RDTM_BT709_EXT (3 << 9) +#define VI6_RPF_INFMT_RDTM_MASK (7 << 9) +#define VI6_RPF_INFMT_CSC (1 << 8) +#define VI6_RPF_INFMT_RDFMT_MASK (0x7f << 0) +#define VI6_RPF_INFMT_RDFMT_SHIFT 0 + +#define VI6_RPF_DSWAP 0x030c +#define VI6_RPF_DSWAP_A_LLS (1 << 11) +#define VI6_RPF_DSWAP_A_LWS (1 << 10) +#define VI6_RPF_DSWAP_A_WDS (1 << 9) +#define VI6_RPF_DSWAP_A_BTS (1 << 8) +#define VI6_RPF_DSWAP_P_LLS (1 << 3) +#define VI6_RPF_DSWAP_P_LWS (1 << 2) +#define VI6_RPF_DSWAP_P_WDS (1 << 1) +#define VI6_RPF_DSWAP_P_BTS (1 << 0) + +#define VI6_RPF_LOC 0x0310 +#define VI6_RPF_LOC_HCOORD_MASK (0x1fff << 16) +#define VI6_RPF_LOC_HCOORD_SHIFT 16 +#define VI6_RPF_LOC_VCOORD_MASK (0x1fff << 0) +#define VI6_RPF_LOC_VCOORD_SHIFT 0 + +#define VI6_RPF_ALPH_SEL 0x0314 +#define VI6_RPF_ALPH_SEL_ASEL_PACKED (0 << 28) +#define VI6_RPF_ALPH_SEL_ASEL_8B_PLANE (1 << 28) +#define VI6_RPF_ALPH_SEL_ASEL_SELECT (2 << 28) +#define VI6_RPF_ALPH_SEL_ASEL_1B_PLANE (3 << 28) +#define VI6_RPF_ALPH_SEL_ASEL_FIXED (4 << 28) +#define VI6_RPF_ALPH_SEL_ASEL_MASK (7 << 28) +#define VI6_RPF_ALPH_SEL_ASEL_SHIFT 28 +#define VI6_RPF_ALPH_SEL_IROP_MASK (0xf << 24) +#define VI6_RPF_ALPH_SEL_IROP_SHIFT 24 +#define VI6_RPF_ALPH_SEL_BSEL (1 << 23) +#define VI6_RPF_ALPH_SEL_AEXT_ZERO (0 << 18) +#define VI6_RPF_ALPH_SEL_AEXT_EXT (1 << 18) +#define VI6_RPF_ALPH_SEL_AEXT_ONE (2 << 18) +#define VI6_RPF_ALPH_SEL_AEXT_MASK (3 << 18) +#define VI6_RPF_ALPH_SEL_ALPHA0_MASK (0xff << 8) +#define VI6_RPF_ALPH_SEL_ALPHA0_SHIFT 8 +#define VI6_RPF_ALPH_SEL_ALPHA1_MASK (0xff << 0) +#define VI6_RPF_ALPH_SEL_ALPHA1_SHIFT 0 + +#define VI6_RPF_VRTCOL_SET 0x0318 +#define VI6_RPF_VRTCOL_SET_LAYA_MASK (0xff << 24) +#define VI6_RPF_VRTCOL_SET_LAYA_SHIFT 24 +#define VI6_RPF_VRTCOL_SET_LAYR_MASK (0xff << 16) +#define VI6_RPF_VRTCOL_SET_LAYR_SHIFT 16 +#define VI6_RPF_VRTCOL_SET_LAYG_MASK (0xff << 8) +#define VI6_RPF_VRTCOL_SET_LAYG_SHIFT 8 +#define VI6_RPF_VRTCOL_SET_LAYB_MASK (0xff << 0) +#define VI6_RPF_VRTCOL_SET_LAYB_SHIFT 0 + +#define VI6_RPF_MSK_CTRL 0x031c +#define VI6_RPF_MSK_CTRL_MSK_EN (1 << 24) +#define VI6_RPF_MSK_CTRL_MGR_MASK (0xff << 16) +#define VI6_RPF_MSK_CTRL_MGR_SHIFT 16 +#define VI6_RPF_MSK_CTRL_MGG_MASK (0xff << 8) +#define VI6_RPF_MSK_CTRL_MGG_SHIFT 8 +#define VI6_RPF_MSK_CTRL_MGB_MASK (0xff << 0) +#define VI6_RPF_MSK_CTRL_MGB_SHIFT 0 + +#define VI6_RPF_MSK_SET0 0x0320 +#define VI6_RPF_MSK_SET1 0x0324 +#define VI6_RPF_MSK_SET_MSA_MASK (0xff << 24) +#define VI6_RPF_MSK_SET_MSA_SHIFT 24 +#define VI6_RPF_MSK_SET_MSR_MASK (0xff << 16) +#define VI6_RPF_MSK_SET_MSR_SHIFT 16 +#define VI6_RPF_MSK_SET_MSG_MASK (0xff << 8) +#define VI6_RPF_MSK_SET_MSG_SHIFT 8 +#define VI6_RPF_MSK_SET_MSB_MASK (0xff << 0) +#define VI6_RPF_MSK_SET_MSB_SHIFT 0 + +#define VI6_RPF_CKEY_CTRL 0x0328 +#define VI6_RPF_CKEY_CTRL_CV (1 << 4) +#define VI6_RPF_CKEY_CTRL_SAPE1 (1 << 1) +#define VI6_RPF_CKEY_CTRL_SAPE0 (1 << 0) + +#define VI6_RPF_CKEY_SET0 0x032c +#define VI6_RPF_CKEY_SET1 0x0330 +#define VI6_RPF_CKEY_SET_AP_MASK (0xff << 24) +#define VI6_RPF_CKEY_SET_AP_SHIFT 24 +#define VI6_RPF_CKEY_SET_R_MASK (0xff << 16) +#define VI6_RPF_CKEY_SET_R_SHIFT 16 +#define VI6_RPF_CKEY_SET_GY_MASK (0xff << 8) +#define VI6_RPF_CKEY_SET_GY_SHIFT 8 +#define VI6_RPF_CKEY_SET_B_MASK (0xff << 0) +#define VI6_RPF_CKEY_SET_B_SHIFT 0 + +#define VI6_RPF_SRCM_PSTRIDE 0x0334 +#define VI6_RPF_SRCM_PSTRIDE_Y_SHIFT 16 +#define VI6_RPF_SRCM_PSTRIDE_C_SHIFT 0 + +#define VI6_RPF_SRCM_ASTRIDE 0x0338 +#define VI6_RPF_SRCM_PSTRIDE_A_SHIFT 0 + +#define VI6_RPF_SRCM_ADDR_Y 0x033c +#define VI6_RPF_SRCM_ADDR_C0 0x0340 +#define VI6_RPF_SRCM_ADDR_C1 0x0344 +#define VI6_RPF_SRCM_ADDR_AI 0x0348 + +/* ----------------------------------------------------------------------------- + * WPF Control Registers + */ + +#define VI6_WPF_OFFSET 0x100 + +#define VI6_WPF_SRCRPF 0x1000 +#define VI6_WPF_SRCRPF_VIRACT_DIS (0 << 28) +#define VI6_WPF_SRCRPF_VIRACT_SUB (1 << 28) +#define VI6_WPF_SRCRPF_VIRACT_MST (2 << 28) +#define VI6_WPF_SRCRPF_VIRACT_MASK (3 << 28) +#define VI6_WPF_SRCRPF_RPF_ACT_DIS(n) (0 << ((n) * 2)) +#define VI6_WPF_SRCRPF_RPF_ACT_SUB(n) (1 << ((n) * 2)) +#define VI6_WPF_SRCRPF_RPF_ACT_MST(n) (2 << ((n) * 2)) +#define VI6_WPF_SRCRPF_RPF_ACT_MASK(n) (3 << ((n) * 2)) + +#define VI6_WPF_HSZCLIP 0x1004 +#define VI6_WPF_VSZCLIP 0x1008 +#define VI6_WPF_SZCLIP_EN (1 << 28) +#define VI6_WPF_SZCLIP_OFST_MASK (0xff << 16) +#define VI6_WPF_SZCLIP_OFST_SHIFT 16 +#define VI6_WPF_SZCLIP_SIZE_MASK (0x1fff << 0) +#define VI6_WPF_SZCLIP_SIZE_SHIFT 0 + +#define VI6_WPF_OUTFMT 0x100c +#define VI6_WPF_OUTFMT_PDV_MASK (0xff << 24) +#define VI6_WPF_OUTFMT_PDV_SHIFT 24 +#define VI6_WPF_OUTFMT_PXA (1 << 23) +#define VI6_WPF_OUTFMT_FLP (1 << 16) +#define VI6_WPF_OUTFMT_SPYCS (1 << 15) +#define VI6_WPF_OUTFMT_SPUVS (1 << 14) +#define VI6_WPF_OUTFMT_DITH_DIS (0 << 12) +#define VI6_WPF_OUTFMT_DITH_EN (3 << 12) +#define VI6_WPF_OUTFMT_DITH_MASK (3 << 12) +#define VI6_WPF_OUTFMT_WRTM_BT601 (0 << 9) +#define VI6_WPF_OUTFMT_WRTM_BT601_EXT (1 << 9) +#define VI6_WPF_OUTFMT_WRTM_BT709 (2 << 9) +#define VI6_WPF_OUTFMT_WRTM_BT709_EXT (3 << 9) +#define VI6_WPF_OUTFMT_WRTM_MASK (7 << 9) +#define VI6_WPF_OUTFMT_CSC (1 << 8) +#define VI6_WPF_OUTFMT_WRFMT_MASK (0x7f << 0) +#define VI6_WPF_OUTFMT_WRFMT_SHIFT 0 + +#define VI6_WPF_DSWAP 0x1010 +#define VI6_WPF_DSWAP_P_LLS (1 << 3) +#define VI6_WPF_DSWAP_P_LWS (1 << 2) +#define VI6_WPF_DSWAP_P_WDS (1 << 1) +#define VI6_WPF_DSWAP_P_BTS (1 << 0) + +#define VI6_WPF_RNDCTRL 0x1014 +#define VI6_WPF_RNDCTRL_CBRM (1 << 28) +#define VI6_WPF_RNDCTRL_ABRM_TRUNC (0 << 24) +#define VI6_WPF_RNDCTRL_ABRM_ROUND (1 << 24) +#define VI6_WPF_RNDCTRL_ABRM_THRESH (2 << 24) +#define VI6_WPF_RNDCTRL_ABRM_MASK (3 << 24) +#define VI6_WPF_RNDCTRL_ATHRESH_MASK (0xff << 16) +#define VI6_WPF_RNDCTRL_ATHRESH_SHIFT 16 +#define VI6_WPF_RNDCTRL_CLMD_FULL (0 << 12) +#define VI6_WPF_RNDCTRL_CLMD_CLIP (1 << 12) +#define VI6_WPF_RNDCTRL_CLMD_EXT (2 << 12) +#define VI6_WPF_RNDCTRL_CLMD_MASK (3 << 12) + +#define VI6_WPF_DSTM_STRIDE_Y 0x101c +#define VI6_WPF_DSTM_STRIDE_C 0x1020 +#define VI6_WPF_DSTM_ADDR_Y 0x1024 +#define VI6_WPF_DSTM_ADDR_C0 0x1028 +#define VI6_WPF_DSTM_ADDR_C1 0x102c + +#define VI6_WPF_WRBCK_CTRL 0x1034 +#define VI6_WPF_WRBCK_CTRL_WBMD (1 << 0) + +/* ----------------------------------------------------------------------------- + * DPR Control Registers + */ + +#define VI6_DPR_RPF_ROUTE(n) (0x2000 + (n) * 4) + +#define VI6_DPR_WPF_FPORCH(n) (0x2014 + (n) * 4) +#define VI6_DPR_WPF_FPORCH_FP_WPFN (5 << 8) + +#define VI6_DPR_SRU_ROUTE 0x2024 +#define VI6_DPR_UDS_ROUTE(n) (0x2028 + (n) * 4) +#define VI6_DPR_LUT_ROUTE 0x203c +#define VI6_DPR_CLU_ROUTE 0x2040 +#define VI6_DPR_HST_ROUTE 0x2044 +#define VI6_DPR_HSI_ROUTE 0x2048 +#define VI6_DPR_BRU_ROUTE 0x204c +#define VI6_DPR_ROUTE_FXA_MASK (0xff << 8) +#define VI6_DPR_ROUTE_FXA_SHIFT 16 +#define VI6_DPR_ROUTE_FP_MASK (0xff << 8) +#define VI6_DPR_ROUTE_FP_SHIFT 8 +#define VI6_DPR_ROUTE_RT_MASK (0x3f << 0) +#define VI6_DPR_ROUTE_RT_SHIFT 0 + +#define VI6_DPR_HGO_SMPPT 0x2050 +#define VI6_DPR_HGT_SMPPT 0x2054 +#define VI6_DPR_SMPPT_TGW_MASK (7 << 8) +#define VI6_DPR_SMPPT_TGW_SHIFT 8 +#define VI6_DPR_SMPPT_PT_MASK (0x3f << 0) +#define VI6_DPR_SMPPT_PT_SHIFT 0 + +#define VI6_DPR_NODE_RPF(n) (n) +#define VI6_DPR_NODE_SRU 16 +#define VI6_DPR_NODE_UDS(n) (17 + (n)) +#define VI6_DPR_NODE_LUT 22 +#define VI6_DPR_NODE_BRU_IN(n) (23 + (n)) +#define VI6_DPR_NODE_BRU_OUT 27 +#define VI6_DPR_NODE_CLU 29 +#define VI6_DPR_NODE_HST 30 +#define VI6_DPR_NODE_HSI 31 +#define VI6_DPR_NODE_LIF 55 +#define VI6_DPR_NODE_WPF(n) (56 + (n)) +#define VI6_DPR_NODE_UNUSED 63 + +/* ----------------------------------------------------------------------------- + * SRU Control Registers + */ + +#define VI6_SRU_CTRL0 0x2200 +#define VI6_SRU_CTRL1 0x2204 +#define VI6_SRU_CTRL2 0x2208 + +/* ----------------------------------------------------------------------------- + * UDS Control Registers + */ + +#define VI6_UDS_OFFSET 0x100 + +#define VI6_UDS_CTRL 0x2300 +#define VI6_UDS_CTRL_AMD (1 << 30) +#define VI6_UDS_CTRL_FMD (1 << 29) +#define VI6_UDS_CTRL_BLADV (1 << 28) +#define VI6_UDS_CTRL_AON (1 << 25) +#define VI6_UDS_CTRL_ATHON (1 << 24) +#define VI6_UDS_CTRL_BC (1 << 20) +#define VI6_UDS_CTRL_NE_A (1 << 19) +#define VI6_UDS_CTRL_NE_RCR (1 << 18) +#define VI6_UDS_CTRL_NE_GY (1 << 17) +#define VI6_UDS_CTRL_NE_BCB (1 << 16) +#define VI6_UDS_CTRL_TDIPC (1 << 1) + +#define VI6_UDS_SCALE 0x2304 +#define VI6_UDS_SCALE_HMANT_MASK (0xf << 28) +#define VI6_UDS_SCALE_HMANT_SHIFT 28 +#define VI6_UDS_SCALE_HFRAC_MASK (0xfff << 16) +#define VI6_UDS_SCALE_HFRAC_SHIFT 16 +#define VI6_UDS_SCALE_VMANT_MASK (0xf << 12) +#define VI6_UDS_SCALE_VMANT_SHIFT 12 +#define VI6_UDS_SCALE_VFRAC_MASK (0xfff << 0) +#define VI6_UDS_SCALE_VFRAC_SHIFT 0 + +#define VI6_UDS_ALPTH 0x2308 +#define VI6_UDS_ALPTH_TH1_MASK (0xff << 8) +#define VI6_UDS_ALPTH_TH1_SHIFT 8 +#define VI6_UDS_ALPTH_TH0_MASK (0xff << 0) +#define VI6_UDS_ALPTH_TH0_SHIFT 0 + +#define VI6_UDS_ALPVAL 0x230c +#define VI6_UDS_ALPVAL_VAL2_MASK (0xff << 16) +#define VI6_UDS_ALPVAL_VAL2_SHIFT 16 +#define VI6_UDS_ALPVAL_VAL1_MASK (0xff << 8) +#define VI6_UDS_ALPVAL_VAL1_SHIFT 8 +#define VI6_UDS_ALPVAL_VAL0_MASK (0xff << 0) +#define VI6_UDS_ALPVAL_VAL0_SHIFT 0 + +#define VI6_UDS_PASS_BWIDTH 0x2310 +#define VI6_UDS_PASS_BWIDTH_H_MASK (0x7f << 16) +#define VI6_UDS_PASS_BWIDTH_H_SHIFT 16 +#define VI6_UDS_PASS_BWIDTH_V_MASK (0x7f << 0) +#define VI6_UDS_PASS_BWIDTH_V_SHIFT 0 + +#define VI6_UDS_IPC 0x2318 +#define VI6_UDS_IPC_FIELD (1 << 27) +#define VI6_UDS_IPC_VEDP_MASK (0xfff << 0) +#define VI6_UDS_IPC_VEDP_SHIFT 0 + +#define VI6_UDS_CLIP_SIZE 0x2324 +#define VI6_UDS_CLIP_SIZE_HSIZE_MASK (0x1fff << 16) +#define VI6_UDS_CLIP_SIZE_HSIZE_SHIFT 16 +#define VI6_UDS_CLIP_SIZE_VSIZE_MASK (0x1fff << 0) +#define VI6_UDS_CLIP_SIZE_VSIZE_SHIFT 0 + +#define VI6_UDS_FILL_COLOR 0x2328 +#define VI6_UDS_FILL_COLOR_RFILC_MASK (0xff << 16) +#define VI6_UDS_FILL_COLOR_RFILC_SHIFT 16 +#define VI6_UDS_FILL_COLOR_GFILC_MASK (0xff << 8) +#define VI6_UDS_FILL_COLOR_GFILC_SHIFT 8 +#define VI6_UDS_FILL_COLOR_BFILC_MASK (0xff << 0) +#define VI6_UDS_FILL_COLOR_BFILC_SHIFT 0 + +/* ----------------------------------------------------------------------------- + * LUT Control Registers + */ + +#define VI6_LUT_CTRL 0x2800 + +/* ----------------------------------------------------------------------------- + * CLU Control Registers + */ + +#define VI6_CLU_CTRL 0x2900 + +/* ----------------------------------------------------------------------------- + * HST Control Registers + */ + +#define VI6_HST_CTRL 0x2a00 + +/* ----------------------------------------------------------------------------- + * HSI Control Registers + */ + +#define VI6_HSI_CTRL 0x2b00 + +/* ----------------------------------------------------------------------------- + * BRU Control Registers + */ + +#define VI6_BRU_INCTRL 0x2c00 +#define VI6_BRU_VIRRPF_SIZE 0x2c04 +#define VI6_BRU_VIRRPF_LOC 0x2c08 +#define VI6_BRU_VIRRPF_COL 0x2c0c +#define VI6_BRU_CTRL(n) (0x2c10 + (n) * 8) +#define VI6_BRU_BLD(n) (0x2c14 + (n) * 8) +#define VI6_BRU_ROP 0x2c30 + +/* ----------------------------------------------------------------------------- + * HGO Control Registers + */ + +#define VI6_HGO_OFFSET 0x3000 +#define VI6_HGO_SIZE 0x3004 +#define VI6_HGO_MODE 0x3008 +#define VI6_HGO_LB_TH 0x300c +#define VI6_HGO_LBn_H(n) (0x3010 + (n) * 8) +#define VI6_HGO_LBn_V(n) (0x3014 + (n) * 8) +#define VI6_HGO_R_HISTO 0x3030 +#define VI6_HGO_R_MAXMIN 0x3130 +#define VI6_HGO_R_SUM 0x3134 +#define VI6_HGO_R_LB_DET 0x3138 +#define VI6_HGO_G_HISTO 0x3140 +#define VI6_HGO_G_MAXMIN 0x3240 +#define VI6_HGO_G_SUM 0x3244 +#define VI6_HGO_G_LB_DET 0x3248 +#define VI6_HGO_B_HISTO 0x3250 +#define VI6_HGO_B_MAXMIN 0x3350 +#define VI6_HGO_B_SUM 0x3354 +#define VI6_HGO_B_LB_DET 0x3358 +#define VI6_HGO_REGRST 0x33fc + +/* ----------------------------------------------------------------------------- + * HGT Control Registers + */ + +#define VI6_HGT_OFFSET 0x3400 +#define VI6_HGT_SIZE 0x3404 +#define VI6_HGT_MODE 0x3408 +#define VI6_HGT_HUE_AREA(n) (0x340c + (n) * 4) +#define VI6_HGT_LB_TH 0x3424 +#define VI6_HGT_LBn_H(n) (0x3438 + (n) * 8) +#define VI6_HGT_LBn_V(n) (0x342c + (n) * 8) +#define VI6_HGT_HISTO(m, n) (0x3450 + (m) * 128 + (n) * 4) +#define VI6_HGT_MAXMIN 0x3750 +#define VI6_HGT_SUM 0x3754 +#define VI6_HGT_LB_DET 0x3758 +#define VI6_HGT_REGRST 0x37fc + +/* ----------------------------------------------------------------------------- + * LIF Control Registers + */ + +#define VI6_LIF_CTRL 0x3b00 +#define VI6_LIF_CTRL_OBTH_MASK (0x7ff << 16) +#define VI6_LIF_CTRL_OBTH_SHIFT 16 +#define VI6_LIF_CTRL_CFMT (1 << 4) +#define VI6_LIF_CTRL_REQSEL (1 << 1) +#define VI6_LIF_CTRL_LIF_EN (1 << 0) + +#define VI6_LIF_CSBTH 0x3b04 +#define VI6_LIF_CSBTH_HBTH_MASK (0x7ff << 16) +#define VI6_LIF_CSBTH_HBTH_SHIFT 16 +#define VI6_LIF_CSBTH_LBTH_MASK (0x7ff << 0) +#define VI6_LIF_CSBTH_LBTH_SHIFT 0 + +/* ----------------------------------------------------------------------------- + * Security Control Registers + */ + +#define VI6_SECURITY_CTRL0 0x3d00 +#define VI6_SECURITY_CTRL1 0x3d04 + +/* ----------------------------------------------------------------------------- + * RPF CLUT Registers + */ + +#define VI6_CLUT_TABLE 0x4000 + +/* ----------------------------------------------------------------------------- + * 1D LUT Registers + */ + +#define VI6_LUT_TABLE 0x7000 + +/* ----------------------------------------------------------------------------- + * 3D LUT Registers + */ + +#define VI6_CLU_ADDR 0x7400 +#define VI6_CLU_DATA 0x7404 + +/* ----------------------------------------------------------------------------- + * Formats + */ + +#define VI6_FMT_RGB_332 0x00 +#define VI6_FMT_XRGB_4444 0x01 +#define VI6_FMT_RGBX_4444 0x02 +#define VI6_FMT_XRGB_1555 0x04 +#define VI6_FMT_RGBX_5551 0x05 +#define VI6_FMT_RGB_565 0x06 +#define VI6_FMT_AXRGB_86666 0x07 +#define VI6_FMT_RGBXA_66668 0x08 +#define VI6_FMT_XRGBA_66668 0x09 +#define VI6_FMT_ARGBX_86666 0x0a +#define VI6_FMT_AXRXGXB_8262626 0x0b +#define VI6_FMT_XRXGXBA_2626268 0x0c +#define VI6_FMT_ARXGXBX_8626262 0x0d +#define VI6_FMT_RXGXBXA_6262628 0x0e +#define VI6_FMT_XRGB_6666 0x0f +#define VI6_FMT_RGBX_6666 0x10 +#define VI6_FMT_XRXGXB_262626 0x11 +#define VI6_FMT_RXGXBX_626262 0x12 +#define VI6_FMT_ARGB_8888 0x13 +#define VI6_FMT_RGBA_8888 0x14 +#define VI6_FMT_RGB_888 0x15 +#define VI6_FMT_XRGXGB_763763 0x16 +#define VI6_FMT_XXRGB_86666 0x17 +#define VI6_FMT_BGR_888 0x18 +#define VI6_FMT_ARGB_4444 0x19 +#define VI6_FMT_RGBA_4444 0x1a +#define VI6_FMT_ARGB_1555 0x1b +#define VI6_FMT_RGBA_5551 0x1c +#define VI6_FMT_ABGR_4444 0x1d +#define VI6_FMT_BGRA_4444 0x1e +#define VI6_FMT_ABGR_1555 0x1f +#define VI6_FMT_BGRA_5551 0x20 +#define VI6_FMT_XBXGXR_262626 0x21 +#define VI6_FMT_ABGR_8888 0x22 +#define VI6_FMT_XXRGB_88565 0x23 + +#define VI6_FMT_Y_UV_444 0x40 +#define VI6_FMT_Y_UV_422 0x41 +#define VI6_FMT_Y_UV_420 0x42 +#define VI6_FMT_YUV_444 0x46 +#define VI6_FMT_YUYV_422 0x47 +#define VI6_FMT_YYUV_422 0x48 +#define VI6_FMT_YUV_420 0x49 +#define VI6_FMT_Y_U_V_444 0x4a +#define VI6_FMT_Y_U_V_422 0x4b +#define VI6_FMT_Y_U_V_420 0x4c + +#endif /* __VSP1_REGS_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_rpf.c b/drivers/media/platform/vsp1/vsp1_rpf.c new file mode 100644 index 000000000000..254871d3423e --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_rpf.c @@ -0,0 +1,209 @@ +/* + * vsp1_rpf.c -- R-Car VSP1 Read Pixel Formatter + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * 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. + */ + +#include + +#include + +#include "vsp1.h" +#include "vsp1_rwpf.h" +#include "vsp1_video.h" + +#define RPF_MAX_WIDTH 8190 +#define RPF_MAX_HEIGHT 8190 + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline u32 vsp1_rpf_read(struct vsp1_rwpf *rpf, u32 reg) +{ + return vsp1_read(rpf->entity.vsp1, + reg + rpf->entity.index * VI6_RPF_OFFSET); +} + +static inline void vsp1_rpf_write(struct vsp1_rwpf *rpf, u32 reg, u32 data) +{ + vsp1_write(rpf->entity.vsp1, + reg + rpf->entity.index * VI6_RPF_OFFSET, data); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Core Operations + */ + +static int rpf_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct vsp1_rwpf *rpf = to_rwpf(subdev); + const struct vsp1_format_info *fmtinfo = rpf->video.fmtinfo; + const struct v4l2_pix_format_mplane *format = &rpf->video.format; + u32 pstride; + u32 infmt; + + if (!enable) + return 0; + + /* Source size and stride. Cropping isn't supported yet. */ + vsp1_rpf_write(rpf, VI6_RPF_SRC_BSIZE, + (format->width << VI6_RPF_SRC_BSIZE_BHSIZE_SHIFT) | + (format->height << VI6_RPF_SRC_BSIZE_BVSIZE_SHIFT)); + vsp1_rpf_write(rpf, VI6_RPF_SRC_ESIZE, + (format->width << VI6_RPF_SRC_ESIZE_EHSIZE_SHIFT) | + (format->height << VI6_RPF_SRC_ESIZE_EVSIZE_SHIFT)); + + pstride = format->plane_fmt[0].bytesperline + << VI6_RPF_SRCM_PSTRIDE_Y_SHIFT; + if (format->num_planes > 1) + pstride |= format->plane_fmt[1].bytesperline + << VI6_RPF_SRCM_PSTRIDE_C_SHIFT; + + vsp1_rpf_write(rpf, VI6_RPF_SRCM_PSTRIDE, pstride); + + /* Format */ + infmt = VI6_RPF_INFMT_CIPM + | (fmtinfo->hwfmt << VI6_RPF_INFMT_RDFMT_SHIFT); + + if (fmtinfo->swap_yc) + infmt |= VI6_RPF_INFMT_SPYCS; + if (fmtinfo->swap_uv) + infmt |= VI6_RPF_INFMT_SPUVS; + + if (rpf->entity.formats[RWPF_PAD_SINK].code != + rpf->entity.formats[RWPF_PAD_SOURCE].code) + infmt |= VI6_RPF_INFMT_CSC; + + vsp1_rpf_write(rpf, VI6_RPF_INFMT, infmt); + vsp1_rpf_write(rpf, VI6_RPF_DSWAP, fmtinfo->swap); + + /* Output location. Composing isn't supported yet. */ + vsp1_rpf_write(rpf, VI6_RPF_LOC, 0); + + /* Disable alpha, mask and color key. Set the alpha channel to a fixed + * value of 255. + */ + vsp1_rpf_write(rpf, VI6_RPF_ALPH_SEL, VI6_RPF_ALPH_SEL_ASEL_FIXED); + vsp1_rpf_write(rpf, VI6_RPF_VRTCOL_SET, + 255 << VI6_RPF_VRTCOL_SET_LAYA_SHIFT); + vsp1_rpf_write(rpf, VI6_RPF_MSK_CTRL, 0); + vsp1_rpf_write(rpf, VI6_RPF_CKEY_CTRL, 0); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static struct v4l2_subdev_video_ops rpf_video_ops = { + .s_stream = rpf_s_stream, +}; + +static struct v4l2_subdev_pad_ops rpf_pad_ops = { + .enum_mbus_code = vsp1_rwpf_enum_mbus_code, + .enum_frame_size = vsp1_rwpf_enum_frame_size, + .get_fmt = vsp1_rwpf_get_format, + .set_fmt = vsp1_rwpf_set_format, +}; + +static struct v4l2_subdev_ops rpf_ops = { + .video = &rpf_video_ops, + .pad = &rpf_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Video Device Operations + */ + +static void rpf_vdev_queue(struct vsp1_video *video, + struct vsp1_video_buffer *buf) +{ + struct vsp1_rwpf *rpf = container_of(video, struct vsp1_rwpf, video); + + vsp1_rpf_write(rpf, VI6_RPF_SRCM_ADDR_Y, buf->addr[0]); + if (buf->buf.num_planes > 1) + vsp1_rpf_write(rpf, VI6_RPF_SRCM_ADDR_C0, buf->addr[1]); + if (buf->buf.num_planes > 2) + vsp1_rpf_write(rpf, VI6_RPF_SRCM_ADDR_C1, buf->addr[2]); +} + +static const struct vsp1_video_operations rpf_vdev_ops = { + .queue = rpf_vdev_queue, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_rwpf *vsp1_rpf_create(struct vsp1_device *vsp1, unsigned int index) +{ + struct v4l2_subdev *subdev; + struct vsp1_video *video; + struct vsp1_rwpf *rpf; + int ret; + + rpf = devm_kzalloc(vsp1->dev, sizeof(*rpf), GFP_KERNEL); + if (rpf == NULL) + return ERR_PTR(-ENOMEM); + + rpf->max_width = RPF_MAX_WIDTH; + rpf->max_height = RPF_MAX_HEIGHT; + + rpf->entity.type = VSP1_ENTITY_RPF; + rpf->entity.index = index; + rpf->entity.id = VI6_DPR_NODE_RPF(index); + + ret = vsp1_entity_init(vsp1, &rpf->entity, 2); + if (ret < 0) + return ERR_PTR(ret); + + /* Initialize the V4L2 subdev. */ + subdev = &rpf->entity.subdev; + v4l2_subdev_init(subdev, &rpf_ops); + + subdev->entity.ops = &vsp1_media_ops; + subdev->internal_ops = &vsp1_subdev_internal_ops; + snprintf(subdev->name, sizeof(subdev->name), "%s rpf.%u", + dev_name(vsp1->dev), index); + v4l2_set_subdevdata(subdev, rpf); + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + vsp1_entity_init_formats(subdev, NULL); + + /* Initialize the video device. */ + video = &rpf->video; + + video->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + video->vsp1 = vsp1; + video->ops = &rpf_vdev_ops; + + ret = vsp1_video_init(video, &rpf->entity); + if (ret < 0) + goto error_video; + + /* Connect the video device to the RPF. */ + ret = media_entity_create_link(&rpf->video.video.entity, 0, + &rpf->entity.subdev.entity, + RWPF_PAD_SINK, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret < 0) + goto error_link; + + return rpf; + +error_link: + vsp1_video_cleanup(video); +error_video: + media_entity_cleanup(&rpf->entity.subdev.entity); + return ERR_PTR(ret); +} diff --git a/drivers/media/platform/vsp1/vsp1_rwpf.c b/drivers/media/platform/vsp1/vsp1_rwpf.c new file mode 100644 index 000000000000..9752d5516ceb --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_rwpf.c @@ -0,0 +1,124 @@ +/* + * vsp1_rwpf.c -- R-Car VSP1 Read and Write Pixel Formatters + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * 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. + */ + +#include + +#include "vsp1.h" +#include "vsp1_rwpf.h" +#include "vsp1_video.h" + +#define RWPF_MIN_WIDTH 1 +#define RWPF_MIN_HEIGHT 1 + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Pad Operations + */ + +int vsp1_rwpf_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + static const unsigned int codes[] = { + V4L2_MBUS_FMT_ARGB8888_1X32, + V4L2_MBUS_FMT_AYUV8_1X32, + }; + + if (code->index >= ARRAY_SIZE(codes)) + return -EINVAL; + + code->code = codes[code->index]; + + return 0; +} + +int vsp1_rwpf_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct vsp1_rwpf *rwpf = to_rwpf(subdev); + struct v4l2_mbus_framefmt *format; + + format = v4l2_subdev_get_try_format(fh, fse->pad); + + if (fse->index || fse->code != format->code) + return -EINVAL; + + if (fse->pad == RWPF_PAD_SINK) { + fse->min_width = RWPF_MIN_WIDTH; + fse->max_width = rwpf->max_width; + fse->min_height = RWPF_MIN_HEIGHT; + fse->max_height = rwpf->max_height; + } else { + /* The size on the source pad are fixed and always identical to + * the size on the sink pad. + */ + fse->min_width = format->width; + fse->max_width = format->width; + fse->min_height = format->height; + fse->max_height = format->height; + } + + return 0; +} + +int vsp1_rwpf_get_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_rwpf *rwpf = to_rwpf(subdev); + + fmt->format = *vsp1_entity_get_pad_format(&rwpf->entity, fh, fmt->pad, + fmt->which); + + return 0; +} + +int vsp1_rwpf_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_rwpf *rwpf = to_rwpf(subdev); + struct v4l2_mbus_framefmt *format; + + /* Default to YUV if the requested format is not supported. */ + if (fmt->format.code != V4L2_MBUS_FMT_ARGB8888_1X32 && + fmt->format.code != V4L2_MBUS_FMT_AYUV8_1X32) + fmt->format.code = V4L2_MBUS_FMT_AYUV8_1X32; + + format = vsp1_entity_get_pad_format(&rwpf->entity, fh, fmt->pad, + fmt->which); + + if (fmt->pad == RWPF_PAD_SOURCE) { + /* The RWPF performs format conversion but can't scale, only the + * format code can be changed on the source pad. + */ + format->code = fmt->format.code; + fmt->format = *format; + return 0; + } + + format->code = fmt->format.code; + format->width = clamp_t(unsigned int, fmt->format.width, + RWPF_MIN_WIDTH, rwpf->max_width); + format->height = clamp_t(unsigned int, fmt->format.height, + RWPF_MIN_HEIGHT, rwpf->max_height); + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + + fmt->format = *format; + + /* Propagate the format to the source pad. */ + format = vsp1_entity_get_pad_format(&rwpf->entity, fh, RWPF_PAD_SOURCE, + fmt->which); + *format = fmt->format; + + return 0; +} diff --git a/drivers/media/platform/vsp1/vsp1_rwpf.h b/drivers/media/platform/vsp1/vsp1_rwpf.h new file mode 100644 index 000000000000..c182d85f36b3 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_rwpf.h @@ -0,0 +1,53 @@ +/* + * vsp1_rwpf.h -- R-Car VSP1 Read and Write Pixel Formatters + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * 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. + */ +#ifndef __VSP1_RWPF_H__ +#define __VSP1_RWPF_H__ + +#include +#include + +#include "vsp1.h" +#include "vsp1_entity.h" +#include "vsp1_video.h" + +#define RWPF_PAD_SINK 0 +#define RWPF_PAD_SOURCE 1 + +struct vsp1_rwpf { + struct vsp1_entity entity; + struct vsp1_video video; + + unsigned int max_width; + unsigned int max_height; +}; + +static inline struct vsp1_rwpf *to_rwpf(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_rwpf, entity.subdev); +} + +struct vsp1_rwpf *vsp1_rpf_create(struct vsp1_device *vsp1, unsigned int index); +struct vsp1_rwpf *vsp1_wpf_create(struct vsp1_device *vsp1, unsigned int index); + +int vsp1_rwpf_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code); +int vsp1_rwpf_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse); +int vsp1_rwpf_get_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt); +int vsp1_rwpf_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt); + +#endif /* __VSP1_RWPF_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_uds.c b/drivers/media/platform/vsp1/vsp1_uds.c new file mode 100644 index 000000000000..0e50b37f060d --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_uds.c @@ -0,0 +1,346 @@ +/* + * vsp1_uds.c -- R-Car VSP1 Up and Down Scaler + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * 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. + */ + +#include +#include + +#include + +#include "vsp1.h" +#include "vsp1_uds.h" + +#define UDS_MIN_SIZE 4U +#define UDS_MAX_SIZE 8190U + +#define UDS_MIN_FACTOR 0x0100 +#define UDS_MAX_FACTOR 0xffff + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline u32 vsp1_uds_read(struct vsp1_uds *uds, u32 reg) +{ + return vsp1_read(uds->entity.vsp1, + reg + uds->entity.index * VI6_UDS_OFFSET); +} + +static inline void vsp1_uds_write(struct vsp1_uds *uds, u32 reg, u32 data) +{ + vsp1_write(uds->entity.vsp1, + reg + uds->entity.index * VI6_UDS_OFFSET, data); +} + +/* ----------------------------------------------------------------------------- + * Scaling Computation + */ + +/* + * uds_output_size - Return the output size for an input size and scaling ratio + * @input: input size in pixels + * @ratio: scaling ratio in U4.12 fixed-point format + */ +static unsigned int uds_output_size(unsigned int input, unsigned int ratio) +{ + if (ratio > 4096) { + /* Down-scaling */ + unsigned int mp; + + mp = ratio / 4096; + mp = mp < 4 ? 1 : (mp < 8 ? 2 : 4); + + return (input - 1) / mp * mp * 4096 / ratio + 1; + } else { + /* Up-scaling */ + return (input - 1) * 4096 / ratio + 1; + } +} + +/* + * uds_output_limits - Return the min and max output sizes for an input size + * @input: input size in pixels + * @minimum: minimum output size (returned) + * @maximum: maximum output size (returned) + */ +static void uds_output_limits(unsigned int input, + unsigned int *minimum, unsigned int *maximum) +{ + *minimum = max(uds_output_size(input, UDS_MAX_FACTOR), UDS_MIN_SIZE); + *maximum = min(uds_output_size(input, UDS_MIN_FACTOR), UDS_MAX_SIZE); +} + +/* + * uds_passband_width - Return the passband filter width for a scaling ratio + * @ratio: scaling ratio in U4.12 fixed-point format + */ +static unsigned int uds_passband_width(unsigned int ratio) +{ + if (ratio >= 4096) { + /* Down-scaling */ + unsigned int mp; + + mp = ratio / 4096; + mp = mp < 4 ? 1 : (mp < 8 ? 2 : 4); + + return 64 * 4096 * mp / ratio; + } else { + /* Up-scaling */ + return 64; + } +} + +static unsigned int uds_compute_ratio(unsigned int input, unsigned int output) +{ + /* TODO: This is an approximation that will need to be refined. */ + return (input - 1) * 4096 / (output - 1); +} + +static void uds_compute_ratios(struct vsp1_uds *uds) +{ + struct v4l2_mbus_framefmt *input = &uds->entity.formats[UDS_PAD_SINK]; + struct v4l2_mbus_framefmt *output = + &uds->entity.formats[UDS_PAD_SOURCE]; + + uds->hscale = uds_compute_ratio(input->width, output->width); + uds->vscale = uds_compute_ratio(input->height, output->height); + + dev_dbg(uds->entity.vsp1->dev, "hscale %u vscale %u\n", + uds->hscale, uds->vscale); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Core Operations + */ + +static int uds_s_stream(struct v4l2_subdev *subdev, int enable) +{ + const struct v4l2_mbus_framefmt *format; + struct vsp1_uds *uds = to_uds(subdev); + + if (!enable) + return 0; + + /* Enable multi-tap scaling. */ + vsp1_uds_write(uds, VI6_UDS_CTRL, VI6_UDS_CTRL_BC); + + vsp1_uds_write(uds, VI6_UDS_PASS_BWIDTH, + (uds_passband_width(uds->hscale) + << VI6_UDS_PASS_BWIDTH_H_SHIFT) | + (uds_passband_width(uds->vscale) + << VI6_UDS_PASS_BWIDTH_V_SHIFT)); + + + /* Set the scaling ratios and the output size. */ + format = &uds->entity.formats[UDS_PAD_SOURCE]; + + vsp1_uds_write(uds, VI6_UDS_SCALE, + (uds->hscale << VI6_UDS_SCALE_HFRAC_SHIFT) | + (uds->vscale << VI6_UDS_SCALE_VFRAC_SHIFT)); + vsp1_uds_write(uds, VI6_UDS_CLIP_SIZE, + (format->width << VI6_UDS_CLIP_SIZE_HSIZE_SHIFT) | + (format->height << VI6_UDS_CLIP_SIZE_VSIZE_SHIFT)); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Pad Operations + */ + +static int uds_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + static const unsigned int codes[] = { + V4L2_MBUS_FMT_ARGB8888_1X32, + V4L2_MBUS_FMT_AYUV8_1X32, + }; + + if (code->pad == UDS_PAD_SINK) { + if (code->index >= ARRAY_SIZE(codes)) + return -EINVAL; + + code->code = codes[code->index]; + } else { + struct v4l2_mbus_framefmt *format; + + /* The UDS can't perform format conversion, the sink format is + * always identical to the source format. + */ + if (code->index) + return -EINVAL; + + format = v4l2_subdev_get_try_format(fh, UDS_PAD_SINK); + code->code = format->code; + } + + return 0; +} + +static int uds_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct v4l2_mbus_framefmt *format; + + format = v4l2_subdev_get_try_format(fh, UDS_PAD_SINK); + + if (fse->index || fse->code != format->code) + return -EINVAL; + + if (fse->pad == UDS_PAD_SINK) { + fse->min_width = UDS_MIN_SIZE; + fse->max_width = UDS_MAX_SIZE; + fse->min_height = UDS_MIN_SIZE; + fse->max_height = UDS_MAX_SIZE; + } else { + uds_output_limits(format->width, &fse->min_width, + &fse->max_width); + uds_output_limits(format->height, &fse->min_height, + &fse->max_height); + } + + return 0; +} + +static int uds_get_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_uds *uds = to_uds(subdev); + + fmt->format = *vsp1_entity_get_pad_format(&uds->entity, fh, fmt->pad, + fmt->which); + + return 0; +} + +static void uds_try_format(struct vsp1_uds *uds, struct v4l2_subdev_fh *fh, + unsigned int pad, struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + struct v4l2_mbus_framefmt *format; + unsigned int minimum; + unsigned int maximum; + + switch (pad) { + case UDS_PAD_SINK: + /* Default to YUV if the requested format is not supported. */ + if (fmt->code != V4L2_MBUS_FMT_ARGB8888_1X32 && + fmt->code != V4L2_MBUS_FMT_AYUV8_1X32) + fmt->code = V4L2_MBUS_FMT_AYUV8_1X32; + + fmt->width = clamp(fmt->width, UDS_MIN_SIZE, UDS_MAX_SIZE); + fmt->height = clamp(fmt->height, UDS_MIN_SIZE, UDS_MAX_SIZE); + break; + + case UDS_PAD_SOURCE: + /* The UDS scales but can't perform format conversion. */ + format = vsp1_entity_get_pad_format(&uds->entity, fh, + UDS_PAD_SINK, which); + fmt->code = format->code; + + uds_output_limits(format->width, &minimum, &maximum); + fmt->width = clamp(fmt->width, minimum, maximum); + uds_output_limits(format->height, &minimum, &maximum); + fmt->height = clamp(fmt->height, minimum, maximum); + break; + } + + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; +} + +static int uds_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_uds *uds = to_uds(subdev); + struct v4l2_mbus_framefmt *format; + + uds_try_format(uds, fh, fmt->pad, &fmt->format, fmt->which); + + format = vsp1_entity_get_pad_format(&uds->entity, fh, fmt->pad, + fmt->which); + *format = fmt->format; + + if (fmt->pad == UDS_PAD_SINK) { + /* Propagate the format to the source pad. */ + format = vsp1_entity_get_pad_format(&uds->entity, fh, + UDS_PAD_SOURCE, fmt->which); + *format = fmt->format; + + uds_try_format(uds, fh, UDS_PAD_SOURCE, format, fmt->which); + } + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) + uds_compute_ratios(uds); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static struct v4l2_subdev_video_ops uds_video_ops = { + .s_stream = uds_s_stream, +}; + +static struct v4l2_subdev_pad_ops uds_pad_ops = { + .enum_mbus_code = uds_enum_mbus_code, + .enum_frame_size = uds_enum_frame_size, + .get_fmt = uds_get_format, + .set_fmt = uds_set_format, +}; + +static struct v4l2_subdev_ops uds_ops = { + .video = &uds_video_ops, + .pad = &uds_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_uds *vsp1_uds_create(struct vsp1_device *vsp1, unsigned int index) +{ + struct v4l2_subdev *subdev; + struct vsp1_uds *uds; + int ret; + + uds = devm_kzalloc(vsp1->dev, sizeof(*uds), GFP_KERNEL); + if (uds == NULL) + return ERR_PTR(-ENOMEM); + + uds->entity.type = VSP1_ENTITY_UDS; + uds->entity.index = index; + uds->entity.id = VI6_DPR_NODE_UDS(index); + + ret = vsp1_entity_init(vsp1, &uds->entity, 2); + if (ret < 0) + return ERR_PTR(ret); + + /* Initialize the V4L2 subdev. */ + subdev = &uds->entity.subdev; + v4l2_subdev_init(subdev, &uds_ops); + + subdev->entity.ops = &vsp1_media_ops; + subdev->internal_ops = &vsp1_subdev_internal_ops; + snprintf(subdev->name, sizeof(subdev->name), "%s uds.%u", + dev_name(vsp1->dev), index); + v4l2_set_subdevdata(subdev, uds); + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + vsp1_entity_init_formats(subdev, NULL); + + return uds; +} diff --git a/drivers/media/platform/vsp1/vsp1_uds.h b/drivers/media/platform/vsp1/vsp1_uds.h new file mode 100644 index 000000000000..972a285abdb9 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_uds.h @@ -0,0 +1,40 @@ +/* + * vsp1_uds.h -- R-Car VSP1 Up and Down Scaler + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * 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. + */ +#ifndef __VSP1_UDS_H__ +#define __VSP1_UDS_H__ + +#include +#include + +#include "vsp1_entity.h" + +struct vsp1_device; + +#define UDS_PAD_SINK 0 +#define UDS_PAD_SOURCE 1 + +struct vsp1_uds { + struct vsp1_entity entity; + + unsigned int hscale; + unsigned int vscale; +}; + +static inline struct vsp1_uds *to_uds(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_uds, entity.subdev); +} + +struct vsp1_uds *vsp1_uds_create(struct vsp1_device *vsp1, unsigned int index); + +#endif /* __VSP1_UDS_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_video.c b/drivers/media/platform/vsp1/vsp1_video.c new file mode 100644 index 000000000000..f51f84232e2f --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_video.c @@ -0,0 +1,1071 @@ +/* + * vsp1_video.c -- R-Car VSP1 Video Node + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "vsp1.h" +#include "vsp1_entity.h" +#include "vsp1_rwpf.h" +#include "vsp1_video.h" + +#define VSP1_VIDEO_DEF_FORMAT V4L2_PIX_FMT_YUYV +#define VSP1_VIDEO_DEF_WIDTH 1024 +#define VSP1_VIDEO_DEF_HEIGHT 768 + +#define VSP1_VIDEO_MIN_WIDTH 2U +#define VSP1_VIDEO_MAX_WIDTH 8190U +#define VSP1_VIDEO_MIN_HEIGHT 2U +#define VSP1_VIDEO_MAX_HEIGHT 8190U + +/* ----------------------------------------------------------------------------- + * Helper functions + */ + +static const struct vsp1_format_info vsp1_video_formats[] = { + { V4L2_PIX_FMT_RGB332, V4L2_MBUS_FMT_ARGB8888_1X32, + VI6_FMT_RGB_332, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 8, 0, 0 }, false, false, 1, 1 }, + { V4L2_PIX_FMT_RGB444, V4L2_MBUS_FMT_ARGB8888_1X32, + VI6_FMT_XRGB_4444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1 }, + { V4L2_PIX_FMT_RGB555, V4L2_MBUS_FMT_ARGB8888_1X32, + VI6_FMT_XRGB_1555, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1 }, + { V4L2_PIX_FMT_RGB565, V4L2_MBUS_FMT_ARGB8888_1X32, + VI6_FMT_RGB_565, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1 }, + { V4L2_PIX_FMT_BGR24, V4L2_MBUS_FMT_ARGB8888_1X32, + VI6_FMT_BGR_888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 24, 0, 0 }, false, false, 1, 1 }, + { V4L2_PIX_FMT_RGB24, V4L2_MBUS_FMT_ARGB8888_1X32, + VI6_FMT_RGB_888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 24, 0, 0 }, false, false, 1, 1 }, + { V4L2_PIX_FMT_BGR32, V4L2_MBUS_FMT_ARGB8888_1X32, + VI6_FMT_ARGB_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS, + 1, { 32, 0, 0 }, false, false, 1, 1 }, + { V4L2_PIX_FMT_RGB32, V4L2_MBUS_FMT_ARGB8888_1X32, + VI6_FMT_ARGB_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 32, 0, 0 }, false, false, 1, 1 }, + { V4L2_PIX_FMT_UYVY, V4L2_MBUS_FMT_AYUV8_1X32, + VI6_FMT_YUYV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 16, 0, 0 }, false, false, 2, 1 }, + { V4L2_PIX_FMT_VYUY, V4L2_MBUS_FMT_AYUV8_1X32, + VI6_FMT_YUYV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 16, 0, 0 }, false, true, 2, 1 }, + { V4L2_PIX_FMT_YUYV, V4L2_MBUS_FMT_AYUV8_1X32, + VI6_FMT_YUYV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 16, 0, 0 }, true, false, 2, 1 }, + { V4L2_PIX_FMT_YVYU, V4L2_MBUS_FMT_AYUV8_1X32, + VI6_FMT_YUYV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 16, 0, 0 }, true, true, 2, 1 }, + { V4L2_PIX_FMT_NV12M, V4L2_MBUS_FMT_AYUV8_1X32, + VI6_FMT_Y_UV_420, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 2, { 8, 16, 0 }, false, false, 2, 2 }, + { V4L2_PIX_FMT_NV21M, V4L2_MBUS_FMT_AYUV8_1X32, + VI6_FMT_Y_UV_420, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 2, { 8, 16, 0 }, false, true, 2, 2 }, + { V4L2_PIX_FMT_NV16M, V4L2_MBUS_FMT_AYUV8_1X32, + VI6_FMT_Y_UV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 2, { 8, 16, 0 }, false, false, 2, 1 }, + { V4L2_PIX_FMT_NV61M, V4L2_MBUS_FMT_AYUV8_1X32, + VI6_FMT_Y_UV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 2, { 8, 16, 0 }, false, true, 2, 1 }, + { V4L2_PIX_FMT_YUV420M, V4L2_MBUS_FMT_AYUV8_1X32, + VI6_FMT_Y_U_V_420, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 3, { 8, 8, 8 }, false, false, 2, 2 }, +}; + +/* + * vsp1_get_format_info - Retrieve format information for a 4CC + * @fourcc: the format 4CC + * + * Return a pointer to the format information structure corresponding to the + * given V4L2 format 4CC, or NULL if no corresponding format can be found. + */ +static const struct vsp1_format_info *vsp1_get_format_info(u32 fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vsp1_video_formats); ++i) { + const struct vsp1_format_info *info = &vsp1_video_formats[i]; + + if (info->fourcc == fourcc) + return info; + } + + return NULL; +} + + +static struct v4l2_subdev * +vsp1_video_remote_subdev(struct media_pad *local, u32 *pad) +{ + struct media_pad *remote; + + remote = media_entity_remote_pad(local); + if (remote == NULL || + media_entity_type(remote->entity) != MEDIA_ENT_T_V4L2_SUBDEV) + return NULL; + + if (pad) + *pad = remote->index; + + return media_entity_to_v4l2_subdev(remote->entity); +} + +static int vsp1_video_verify_format(struct vsp1_video *video) +{ + struct v4l2_subdev_format fmt; + struct v4l2_subdev *subdev; + int ret; + + subdev = vsp1_video_remote_subdev(&video->pad, &fmt.pad); + if (subdev == NULL) + return -EINVAL; + + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); + if (ret < 0) + return ret == -ENOIOCTLCMD ? -EINVAL : ret; + + if (video->fmtinfo->mbus != fmt.format.code || + video->format.height != fmt.format.height || + video->format.width != fmt.format.width) + return -EINVAL; + + return 0; +} + +static int __vsp1_video_try_format(struct vsp1_video *video, + struct v4l2_pix_format_mplane *pix, + const struct vsp1_format_info **fmtinfo) +{ + const struct vsp1_format_info *info; + unsigned int width = pix->width; + unsigned int height = pix->height; + unsigned int i; + + /* Retrieve format information and select the default format if the + * requested format isn't supported. + */ + info = vsp1_get_format_info(pix->pixelformat); + if (info == NULL) + info = vsp1_get_format_info(VSP1_VIDEO_DEF_FORMAT); + + pix->pixelformat = info->fourcc; + pix->colorspace = V4L2_COLORSPACE_SRGB; + pix->field = V4L2_FIELD_NONE; + memset(pix->reserved, 0, sizeof(pix->reserved)); + + /* Align the width and height for YUV 4:2:2 and 4:2:0 formats. */ + width = round_down(width, info->hsub); + height = round_down(height, info->vsub); + + /* Clamp the width and height. */ + pix->width = clamp(width, VSP1_VIDEO_MIN_WIDTH, VSP1_VIDEO_MAX_WIDTH); + pix->height = clamp(height, VSP1_VIDEO_MIN_HEIGHT, + VSP1_VIDEO_MAX_HEIGHT); + + /* Compute and clamp the stride and image size. While not documented in + * the datasheet, strides not aligned to a multiple of 128 bytes result + * in image corruption. + */ + for (i = 0; i < max(info->planes, 2U); ++i) { + unsigned int hsub = i > 0 ? info->hsub : 1; + unsigned int vsub = i > 0 ? info->vsub : 1; + unsigned int align = 128; + unsigned int bpl; + + bpl = clamp_t(unsigned int, pix->plane_fmt[i].bytesperline, + pix->width / hsub * info->bpp[i] / 8, + round_down(65535U, align)); + + pix->plane_fmt[i].bytesperline = round_up(bpl, align); + pix->plane_fmt[i].sizeimage = pix->plane_fmt[i].bytesperline + * pix->height / vsub; + } + + if (info->planes == 3) { + /* The second and third planes must have the same stride. */ + pix->plane_fmt[2].bytesperline = pix->plane_fmt[1].bytesperline; + pix->plane_fmt[2].sizeimage = pix->plane_fmt[1].sizeimage; + } + + pix->num_planes = info->planes; + + if (fmtinfo) + *fmtinfo = info; + + return 0; +} + +static bool +vsp1_video_format_adjust(struct vsp1_video *video, + const struct v4l2_pix_format_mplane *format, + struct v4l2_pix_format_mplane *adjust) +{ + unsigned int i; + + *adjust = *format; + __vsp1_video_try_format(video, adjust, NULL); + + if (format->width != adjust->width || + format->height != adjust->height || + format->pixelformat != adjust->pixelformat || + format->num_planes != adjust->num_planes) + return false; + + for (i = 0; i < format->num_planes; ++i) { + if (format->plane_fmt[i].bytesperline != + adjust->plane_fmt[i].bytesperline) + return false; + + adjust->plane_fmt[i].sizeimage = + max(adjust->plane_fmt[i].sizeimage, + format->plane_fmt[i].sizeimage); + } + + return true; +} + +/* ----------------------------------------------------------------------------- + * Pipeline Management + */ + +static int vsp1_pipeline_validate_branch(struct vsp1_rwpf *input, + struct vsp1_rwpf *output) +{ + struct vsp1_entity *entity; + unsigned int entities = 0; + struct media_pad *pad; + bool uds_found = false; + + pad = media_entity_remote_pad(&input->entity.pads[RWPF_PAD_SOURCE]); + + while (1) { + if (pad == NULL) + return -EPIPE; + + /* We've reached a video node, that shouldn't have happened. */ + if (media_entity_type(pad->entity) != MEDIA_ENT_T_V4L2_SUBDEV) + return -EPIPE; + + entity = to_vsp1_entity(media_entity_to_v4l2_subdev(pad->entity)); + + /* We've reached the WPF, we're done. */ + if (entity->type == VSP1_ENTITY_WPF) + break; + + /* Ensure the branch has no loop. */ + if (entities & (1 << entity->subdev.entity.id)) + return -EPIPE; + + entities |= 1 << entity->subdev.entity.id; + + /* UDS can't be chained. */ + if (entity->type == VSP1_ENTITY_UDS) { + if (uds_found) + return -EPIPE; + uds_found = true; + } + + /* Follow the source link. The link setup operations ensure + * that the output fan-out can't be more than one, there is thus + * no need to verify here that only a single source link is + * activated. + */ + pad = &entity->pads[entity->source_pad]; + pad = media_entity_remote_pad(pad); + } + + /* The last entity must be the output WPF. */ + if (entity != &output->entity) + return -EPIPE; + + return 0; +} + +static int vsp1_pipeline_validate(struct vsp1_pipeline *pipe, + struct vsp1_video *video) +{ + struct media_entity_graph graph; + struct media_entity *entity = &video->video.entity; + struct media_device *mdev = entity->parent; + unsigned int i; + int ret; + + mutex_lock(&mdev->graph_mutex); + + /* Walk the graph to locate the entities and video nodes. */ + media_entity_graph_walk_start(&graph, entity); + + while ((entity = media_entity_graph_walk_next(&graph))) { + struct v4l2_subdev *subdev; + struct vsp1_rwpf *rwpf; + struct vsp1_entity *e; + + if (media_entity_type(entity) != MEDIA_ENT_T_V4L2_SUBDEV) { + pipe->num_video++; + continue; + } + + subdev = media_entity_to_v4l2_subdev(entity); + e = to_vsp1_entity(subdev); + list_add_tail(&e->list_pipe, &pipe->entities); + + if (e->type == VSP1_ENTITY_RPF) { + rwpf = to_rwpf(subdev); + pipe->inputs[pipe->num_inputs++] = rwpf; + rwpf->video.pipe_index = pipe->num_inputs; + } else if (e->type == VSP1_ENTITY_WPF) { + rwpf = to_rwpf(subdev); + pipe->output = to_rwpf(subdev); + rwpf->video.pipe_index = 0; + } else if (e->type == VSP1_ENTITY_LIF) { + pipe->lif = e; + } + } + + mutex_unlock(&mdev->graph_mutex); + + /* We need one output and at least one input. */ + if (pipe->num_inputs == 0 || !pipe->output) { + ret = -EPIPE; + goto error; + } + + /* Follow links downstream for each input and make sure the graph + * contains no loop and that all branches end at the output WPF. + */ + for (i = 0; i < pipe->num_inputs; ++i) { + ret = vsp1_pipeline_validate_branch(pipe->inputs[i], + pipe->output); + if (ret < 0) + goto error; + } + + return 0; + +error: + INIT_LIST_HEAD(&pipe->entities); + pipe->buffers_ready = 0; + pipe->num_video = 0; + pipe->num_inputs = 0; + pipe->output = NULL; + pipe->lif = NULL; + return ret; +} + +static int vsp1_pipeline_init(struct vsp1_pipeline *pipe, + struct vsp1_video *video) +{ + int ret; + + mutex_lock(&pipe->lock); + + /* If we're the first user validate and initialize the pipeline. */ + if (pipe->use_count == 0) { + ret = vsp1_pipeline_validate(pipe, video); + if (ret < 0) + goto done; + } + + pipe->use_count++; + ret = 0; + +done: + mutex_unlock(&pipe->lock); + return ret; +} + +static void vsp1_pipeline_cleanup(struct vsp1_pipeline *pipe) +{ + mutex_lock(&pipe->lock); + + /* If we're the last user clean up the pipeline. */ + if (--pipe->use_count == 0) { + INIT_LIST_HEAD(&pipe->entities); + pipe->state = VSP1_PIPELINE_STOPPED; + pipe->buffers_ready = 0; + pipe->num_video = 0; + pipe->num_inputs = 0; + pipe->output = NULL; + pipe->lif = NULL; + } + + mutex_unlock(&pipe->lock); +} + +static void vsp1_pipeline_run(struct vsp1_pipeline *pipe) +{ + struct vsp1_device *vsp1 = pipe->output->entity.vsp1; + + vsp1_write(vsp1, VI6_CMD(pipe->output->entity.index), VI6_CMD_STRCMD); + pipe->state = VSP1_PIPELINE_RUNNING; + pipe->buffers_ready = 0; +} + +static int vsp1_pipeline_stop(struct vsp1_pipeline *pipe) +{ + struct vsp1_entity *entity; + unsigned long flags; + int ret; + + spin_lock_irqsave(&pipe->irqlock, flags); + pipe->state = VSP1_PIPELINE_STOPPING; + spin_unlock_irqrestore(&pipe->irqlock, flags); + + ret = wait_event_timeout(pipe->wq, pipe->state == VSP1_PIPELINE_STOPPED, + msecs_to_jiffies(500)); + ret = ret == 0 ? -ETIMEDOUT : 0; + + list_for_each_entry(entity, &pipe->entities, list_pipe) { + if (entity->route) + vsp1_write(entity->vsp1, entity->route, + VI6_DPR_NODE_UNUSED); + + v4l2_subdev_call(&entity->subdev, video, s_stream, 0); + } + + return ret; +} + +static bool vsp1_pipeline_ready(struct vsp1_pipeline *pipe) +{ + unsigned int mask; + + mask = ((1 << pipe->num_inputs) - 1) << 1; + if (!pipe->lif) + mask |= 1 << 0; + + return pipe->buffers_ready == mask; +} + +/* + * vsp1_video_complete_buffer - Complete the current buffer + * @video: the video node + * + * This function completes the current buffer by filling its sequence number, + * time stamp and payload size, and hands it back to the videobuf core. + * + * Return the next queued buffer or NULL if the queue is empty. + */ +static struct vsp1_video_buffer * +vsp1_video_complete_buffer(struct vsp1_video *video) +{ + struct vsp1_video_buffer *next = NULL; + struct vsp1_video_buffer *done; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&video->irqlock, flags); + + if (list_empty(&video->irqqueue)) { + spin_unlock_irqrestore(&video->irqlock, flags); + return NULL; + } + + done = list_first_entry(&video->irqqueue, + struct vsp1_video_buffer, queue); + list_del(&done->queue); + + if (!list_empty(&video->irqqueue)) + next = list_first_entry(&video->irqqueue, + struct vsp1_video_buffer, queue); + + spin_unlock_irqrestore(&video->irqlock, flags); + + done->buf.v4l2_buf.sequence = video->sequence++; + v4l2_get_timestamp(&done->buf.v4l2_buf.timestamp); + for (i = 0; i < done->buf.num_planes; ++i) + vb2_set_plane_payload(&done->buf, i, done->length[i]); + vb2_buffer_done(&done->buf, VB2_BUF_STATE_DONE); + + return next; +} + +static void vsp1_video_frame_end(struct vsp1_pipeline *pipe, + struct vsp1_video *video) +{ + struct vsp1_video_buffer *buf; + unsigned long flags; + + buf = vsp1_video_complete_buffer(video); + if (buf == NULL) + return; + + spin_lock_irqsave(&pipe->irqlock, flags); + + video->ops->queue(video, buf); + pipe->buffers_ready |= 1 << video->pipe_index; + + spin_unlock_irqrestore(&pipe->irqlock, flags); +} + +void vsp1_pipeline_frame_end(struct vsp1_pipeline *pipe) +{ + unsigned long flags; + unsigned int i; + + if (pipe == NULL) + return; + + /* Complete buffers on all video nodes. */ + for (i = 0; i < pipe->num_inputs; ++i) + vsp1_video_frame_end(pipe, &pipe->inputs[i]->video); + + if (!pipe->lif) + vsp1_video_frame_end(pipe, &pipe->output->video); + + spin_lock_irqsave(&pipe->irqlock, flags); + + /* If a stop has been requested, mark the pipeline as stopped and + * return. + */ + if (pipe->state == VSP1_PIPELINE_STOPPING) { + pipe->state = VSP1_PIPELINE_STOPPED; + wake_up(&pipe->wq); + goto done; + } + + /* Restart the pipeline if ready. */ + if (vsp1_pipeline_ready(pipe)) + vsp1_pipeline_run(pipe); + +done: + spin_unlock_irqrestore(&pipe->irqlock, flags); +} + +/* ----------------------------------------------------------------------------- + * videobuf2 Queue Operations + */ + +static int +vsp1_video_queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct vsp1_video *video = vb2_get_drv_priv(vq); + const struct v4l2_pix_format_mplane *format; + struct v4l2_pix_format_mplane pix_mp; + unsigned int i; + + if (fmt) { + /* Make sure the format is valid and adjust the sizeimage field + * if needed. + */ + if (!vsp1_video_format_adjust(video, &fmt->fmt.pix_mp, &pix_mp)) + return -EINVAL; + + format = &pix_mp; + } else { + format = &video->format; + } + + *nplanes = format->num_planes; + + for (i = 0; i < format->num_planes; ++i) { + sizes[i] = format->plane_fmt[i].sizeimage; + alloc_ctxs[i] = video->alloc_ctx; + } + + return 0; +} + +static int vsp1_video_buffer_prepare(struct vb2_buffer *vb) +{ + struct vsp1_video *video = vb2_get_drv_priv(vb->vb2_queue); + struct vsp1_video_buffer *buf = to_vsp1_video_buffer(vb); + const struct v4l2_pix_format_mplane *format = &video->format; + unsigned int i; + + if (vb->num_planes < format->num_planes) + return -EINVAL; + + buf->video = video; + + for (i = 0; i < vb->num_planes; ++i) { + buf->addr[i] = vb2_dma_contig_plane_dma_addr(vb, i); + buf->length[i] = vb2_plane_size(vb, i); + + if (buf->length[i] < format->plane_fmt[i].sizeimage) + return -EINVAL; + } + + return 0; +} + +static void vsp1_video_buffer_queue(struct vb2_buffer *vb) +{ + struct vsp1_video *video = vb2_get_drv_priv(vb->vb2_queue); + struct vsp1_pipeline *pipe = to_vsp1_pipeline(&video->video.entity); + struct vsp1_video_buffer *buf = to_vsp1_video_buffer(vb); + unsigned long flags; + bool empty; + + spin_lock_irqsave(&video->irqlock, flags); + empty = list_empty(&video->irqqueue); + list_add_tail(&buf->queue, &video->irqqueue); + spin_unlock_irqrestore(&video->irqlock, flags); + + if (!empty) + return; + + spin_lock_irqsave(&pipe->irqlock, flags); + + video->ops->queue(video, buf); + pipe->buffers_ready |= 1 << video->pipe_index; + + if (vb2_is_streaming(&video->queue) && + vsp1_pipeline_ready(pipe)) + vsp1_pipeline_run(pipe); + + spin_unlock_irqrestore(&pipe->irqlock, flags); +} + +static void vsp1_entity_route_setup(struct vsp1_entity *source) +{ + struct vsp1_entity *sink; + + if (source->route == 0) + return; + + sink = container_of(source->sink, struct vsp1_entity, subdev.entity); + vsp1_write(source->vsp1, source->route, sink->id); +} + +static int vsp1_video_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct vsp1_video *video = vb2_get_drv_priv(vq); + struct vsp1_pipeline *pipe = to_vsp1_pipeline(&video->video.entity); + struct vsp1_entity *entity; + unsigned long flags; + int ret; + + mutex_lock(&pipe->lock); + if (pipe->stream_count == pipe->num_video - 1) { + list_for_each_entry(entity, &pipe->entities, list_pipe) { + vsp1_entity_route_setup(entity); + + ret = v4l2_subdev_call(&entity->subdev, video, + s_stream, 1); + if (ret < 0) { + mutex_unlock(&pipe->lock); + return ret; + } + } + } + + pipe->stream_count++; + mutex_unlock(&pipe->lock); + + spin_lock_irqsave(&pipe->irqlock, flags); + if (vsp1_pipeline_ready(pipe)) + vsp1_pipeline_run(pipe); + spin_unlock_irqrestore(&pipe->irqlock, flags); + + return 0; +} + +static int vsp1_video_stop_streaming(struct vb2_queue *vq) +{ + struct vsp1_video *video = vb2_get_drv_priv(vq); + struct vsp1_pipeline *pipe = to_vsp1_pipeline(&video->video.entity); + unsigned long flags; + int ret; + + mutex_lock(&pipe->lock); + if (--pipe->stream_count == 0) { + /* Stop the pipeline. */ + ret = vsp1_pipeline_stop(pipe); + if (ret == -ETIMEDOUT) + dev_err(video->vsp1->dev, "pipeline stop timeout\n"); + } + mutex_unlock(&pipe->lock); + + vsp1_pipeline_cleanup(pipe); + media_entity_pipeline_stop(&video->video.entity); + + /* Remove all buffers from the IRQ queue. */ + spin_lock_irqsave(&video->irqlock, flags); + INIT_LIST_HEAD(&video->irqqueue); + spin_unlock_irqrestore(&video->irqlock, flags); + + return 0; +} + +static struct vb2_ops vsp1_video_queue_qops = { + .queue_setup = vsp1_video_queue_setup, + .buf_prepare = vsp1_video_buffer_prepare, + .buf_queue = vsp1_video_buffer_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = vsp1_video_start_streaming, + .stop_streaming = vsp1_video_stop_streaming, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 ioctls + */ + +static int +vsp1_video_querycap(struct file *file, void *fh, struct v4l2_capability *cap) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_video *video = to_vsp1_video(vfh->vdev); + + cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING + | V4L2_CAP_VIDEO_CAPTURE_MPLANE + | V4L2_CAP_VIDEO_OUTPUT_MPLANE; + + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE + | V4L2_CAP_STREAMING; + else + cap->device_caps = V4L2_CAP_VIDEO_OUTPUT_MPLANE + | V4L2_CAP_STREAMING; + + strlcpy(cap->driver, "vsp1", sizeof(cap->driver)); + strlcpy(cap->card, video->video.name, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", + dev_name(video->vsp1->dev)); + + return 0; +} + +static int +vsp1_video_get_format(struct file *file, void *fh, struct v4l2_format *format) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_video *video = to_vsp1_video(vfh->vdev); + + if (format->type != video->queue.type) + return -EINVAL; + + mutex_lock(&video->lock); + format->fmt.pix_mp = video->format; + mutex_unlock(&video->lock); + + return 0; +} + +static int +vsp1_video_try_format(struct file *file, void *fh, struct v4l2_format *format) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_video *video = to_vsp1_video(vfh->vdev); + + if (format->type != video->queue.type) + return -EINVAL; + + return __vsp1_video_try_format(video, &format->fmt.pix_mp, NULL); +} + +static int +vsp1_video_set_format(struct file *file, void *fh, struct v4l2_format *format) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_video *video = to_vsp1_video(vfh->vdev); + const struct vsp1_format_info *info; + int ret; + + if (format->type != video->queue.type) + return -EINVAL; + + ret = __vsp1_video_try_format(video, &format->fmt.pix_mp, &info); + if (ret < 0) + return ret; + + mutex_lock(&video->lock); + + if (vb2_is_busy(&video->queue)) { + ret = -EBUSY; + goto done; + } + + video->format = format->fmt.pix_mp; + video->fmtinfo = info; + +done: + mutex_unlock(&video->lock); + return ret; +} + +static int +vsp1_video_streamon(struct file *file, void *fh, enum v4l2_buf_type type) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_video *video = to_vsp1_video(vfh->vdev); + struct vsp1_pipeline *pipe; + int ret; + + mutex_lock(&video->lock); + + if (video->queue.owner && video->queue.owner != file->private_data) + return -EBUSY; + + video->sequence = 0; + + /* Start streaming on the pipeline. No link touching an entity in the + * pipeline can be activated or deactivated once streaming is started. + * + * Use the VSP1 pipeline object embedded in the first video object that + * starts streaming. + */ + pipe = video->video.entity.pipe + ? to_vsp1_pipeline(&video->video.entity) : &video->pipe; + + ret = media_entity_pipeline_start(&video->video.entity, &pipe->pipe); + if (ret < 0) + return ret; + + /* Verify that the configured format matches the output of the connected + * subdev. + */ + ret = vsp1_video_verify_format(video); + if (ret < 0) + goto err_stop; + + ret = vsp1_pipeline_init(pipe, video); + if (ret < 0) + goto err_stop; + + /* Start the queue. */ + ret = vb2_streamon(&video->queue, type); + if (ret < 0) + goto err_cleanup; + + return 0; + +err_cleanup: + vsp1_pipeline_cleanup(pipe); +err_stop: + media_entity_pipeline_stop(&video->video.entity); + return ret; +} + +static const struct v4l2_ioctl_ops vsp1_video_ioctl_ops = { + .vidioc_querycap = vsp1_video_querycap, + .vidioc_g_fmt_vid_cap_mplane = vsp1_video_get_format, + .vidioc_s_fmt_vid_cap_mplane = vsp1_video_set_format, + .vidioc_try_fmt_vid_cap_mplane = vsp1_video_try_format, + .vidioc_g_fmt_vid_out_mplane = vsp1_video_get_format, + .vidioc_s_fmt_vid_out_mplane = vsp1_video_set_format, + .vidioc_try_fmt_vid_out_mplane = vsp1_video_try_format, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vsp1_video_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 File Operations + */ + +static int vsp1_video_open(struct file *file) +{ + struct vsp1_video *video = video_drvdata(file); + struct v4l2_fh *vfh; + int ret = 0; + + vfh = kzalloc(sizeof(*vfh), GFP_KERNEL); + if (vfh == NULL) + return -ENOMEM; + + v4l2_fh_init(vfh, &video->video); + v4l2_fh_add(vfh); + + file->private_data = vfh; + + if (!vsp1_device_get(video->vsp1)) { + ret = -EBUSY; + v4l2_fh_del(vfh); + kfree(vfh); + } + + return ret; +} + +static int vsp1_video_release(struct file *file) +{ + struct vsp1_video *video = video_drvdata(file); + struct v4l2_fh *vfh = file->private_data; + + mutex_lock(&video->lock); + if (video->queue.owner == vfh) { + vb2_queue_release(&video->queue); + video->queue.owner = NULL; + } + mutex_unlock(&video->lock); + + vsp1_device_put(video->vsp1); + + v4l2_fh_release(file); + + file->private_data = NULL; + + return 0; +} + +static struct v4l2_file_operations vsp1_video_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = vsp1_video_open, + .release = vsp1_video_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +int vsp1_video_init(struct vsp1_video *video, struct vsp1_entity *rwpf) +{ + const char *direction; + int ret; + + switch (video->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + direction = "output"; + video->pad.flags = MEDIA_PAD_FL_SINK; + break; + + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + direction = "input"; + video->pad.flags = MEDIA_PAD_FL_SOURCE; + video->video.vfl_dir = VFL_DIR_TX; + break; + + default: + return -EINVAL; + } + + video->rwpf = rwpf; + + mutex_init(&video->lock); + spin_lock_init(&video->irqlock); + INIT_LIST_HEAD(&video->irqqueue); + + mutex_init(&video->pipe.lock); + spin_lock_init(&video->pipe.irqlock); + INIT_LIST_HEAD(&video->pipe.entities); + init_waitqueue_head(&video->pipe.wq); + video->pipe.state = VSP1_PIPELINE_STOPPED; + + /* Initialize the media entity... */ + ret = media_entity_init(&video->video.entity, 1, &video->pad, 0); + if (ret < 0) + return ret; + + /* ... and the format ... */ + video->fmtinfo = vsp1_get_format_info(VSP1_VIDEO_DEF_FORMAT); + video->format.pixelformat = video->fmtinfo->fourcc; + video->format.colorspace = V4L2_COLORSPACE_SRGB; + video->format.field = V4L2_FIELD_NONE; + video->format.width = VSP1_VIDEO_DEF_WIDTH; + video->format.height = VSP1_VIDEO_DEF_HEIGHT; + video->format.num_planes = 1; + video->format.plane_fmt[0].bytesperline = + video->format.width * video->fmtinfo->bpp[0] / 8; + video->format.plane_fmt[0].sizeimage = + video->format.plane_fmt[0].bytesperline * video->format.height; + + /* ... and the video node... */ + video->video.v4l2_dev = &video->vsp1->v4l2_dev; + video->video.fops = &vsp1_video_fops; + snprintf(video->video.name, sizeof(video->video.name), "%s %s", + rwpf->subdev.name, direction); + video->video.vfl_type = VFL_TYPE_GRABBER; + video->video.release = video_device_release_empty; + video->video.ioctl_ops = &vsp1_video_ioctl_ops; + + video_set_drvdata(&video->video, video); + + /* ... and the buffers queue... */ + video->alloc_ctx = vb2_dma_contig_init_ctx(video->vsp1->dev); + if (IS_ERR(video->alloc_ctx)) + goto error; + + video->queue.type = video->type; + video->queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + video->queue.lock = &video->lock; + video->queue.drv_priv = video; + video->queue.buf_struct_size = sizeof(struct vsp1_video_buffer); + video->queue.ops = &vsp1_video_queue_qops; + video->queue.mem_ops = &vb2_dma_contig_memops; + video->queue.timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_COPY; + ret = vb2_queue_init(&video->queue); + if (ret < 0) { + dev_err(video->vsp1->dev, "failed to initialize vb2 queue\n"); + goto error; + } + + /* ... and register the video device. */ + video->video.queue = &video->queue; + ret = video_register_device(&video->video, VFL_TYPE_GRABBER, -1); + if (ret < 0) { + dev_err(video->vsp1->dev, "failed to register video device\n"); + goto error; + } + + return 0; + +error: + vb2_dma_contig_cleanup_ctx(video->alloc_ctx); + vsp1_video_cleanup(video); + return ret; +} + +void vsp1_video_cleanup(struct vsp1_video *video) +{ + if (video_is_registered(&video->video)) + video_unregister_device(&video->video); + + vb2_dma_contig_cleanup_ctx(video->alloc_ctx); + media_entity_cleanup(&video->video.entity); +} diff --git a/drivers/media/platform/vsp1/vsp1_video.h b/drivers/media/platform/vsp1/vsp1_video.h new file mode 100644 index 000000000000..d8612a378345 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_video.h @@ -0,0 +1,144 @@ +/* + * vsp1_video.h -- R-Car VSP1 Video Node + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * 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. + */ +#ifndef __VSP1_VIDEO_H__ +#define __VSP1_VIDEO_H__ + +#include +#include +#include + +#include +#include + +struct vsp1_video; + +/* + * struct vsp1_format_info - VSP1 video format description + * @mbus: media bus format code + * @fourcc: V4L2 pixel format FCC identifier + * @planes: number of planes + * @bpp: bits per pixel + * @hwfmt: VSP1 hardware format + * @swap_yc: the Y and C components are swapped (Y comes before C) + * @swap_uv: the U and V components are swapped (V comes before U) + * @hsub: horizontal subsampling factor + * @vsub: vertical subsampling factor + */ +struct vsp1_format_info { + u32 fourcc; + unsigned int mbus; + unsigned int hwfmt; + unsigned int swap; + unsigned int planes; + unsigned int bpp[3]; + bool swap_yc; + bool swap_uv; + unsigned int hsub; + unsigned int vsub; +}; + +enum vsp1_pipeline_state { + VSP1_PIPELINE_STOPPED, + VSP1_PIPELINE_RUNNING, + VSP1_PIPELINE_STOPPING, +}; + +/* + * struct vsp1_pipeline - A VSP1 hardware pipeline + * @media: the media pipeline + * @irqlock: protects the pipeline state + * @lock: protects the pipeline use count and stream count + */ +struct vsp1_pipeline { + struct media_pipeline pipe; + + spinlock_t irqlock; + enum vsp1_pipeline_state state; + wait_queue_head_t wq; + + struct mutex lock; + unsigned int use_count; + unsigned int stream_count; + unsigned int buffers_ready; + + unsigned int num_video; + unsigned int num_inputs; + struct vsp1_rwpf *inputs[VPS1_MAX_RPF]; + struct vsp1_rwpf *output; + struct vsp1_entity *lif; + + struct list_head entities; +}; + +static inline struct vsp1_pipeline *to_vsp1_pipeline(struct media_entity *e) +{ + if (likely(e->pipe)) + return container_of(e->pipe, struct vsp1_pipeline, pipe); + else + return NULL; +} + +struct vsp1_video_buffer { + struct vsp1_video *video; + struct vb2_buffer buf; + struct list_head queue; + + dma_addr_t addr[3]; + unsigned int length[3]; +}; + +static inline struct vsp1_video_buffer * +to_vsp1_video_buffer(struct vb2_buffer *vb) +{ + return container_of(vb, struct vsp1_video_buffer, buf); +} + +struct vsp1_video_operations { + void (*queue)(struct vsp1_video *video, struct vsp1_video_buffer *buf); +}; + +struct vsp1_video { + struct vsp1_device *vsp1; + struct vsp1_entity *rwpf; + + const struct vsp1_video_operations *ops; + + struct video_device video; + enum v4l2_buf_type type; + struct media_pad pad; + + struct mutex lock; + struct v4l2_pix_format_mplane format; + const struct vsp1_format_info *fmtinfo; + + struct vsp1_pipeline pipe; + unsigned int pipe_index; + + struct vb2_queue queue; + void *alloc_ctx; + spinlock_t irqlock; + struct list_head irqqueue; + unsigned int sequence; +}; + +static inline struct vsp1_video *to_vsp1_video(struct video_device *vdev) +{ + return container_of(vdev, struct vsp1_video, video); +} + +int vsp1_video_init(struct vsp1_video *video, struct vsp1_entity *rwpf); +void vsp1_video_cleanup(struct vsp1_video *video); + +void vsp1_pipeline_frame_end(struct vsp1_pipeline *pipe); + +#endif /* __VSP1_VIDEO_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_wpf.c b/drivers/media/platform/vsp1/vsp1_wpf.c new file mode 100644 index 000000000000..db4b85ee05fc --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_wpf.c @@ -0,0 +1,233 @@ +/* + * vsp1_wpf.c -- R-Car VSP1 Write Pixel Formatter + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * 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. + */ + +#include + +#include + +#include "vsp1.h" +#include "vsp1_rwpf.h" +#include "vsp1_video.h" + +#define WPF_MAX_WIDTH 2048 +#define WPF_MAX_HEIGHT 2048 + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline u32 vsp1_wpf_read(struct vsp1_rwpf *wpf, u32 reg) +{ + return vsp1_read(wpf->entity.vsp1, + reg + wpf->entity.index * VI6_WPF_OFFSET); +} + +static inline void vsp1_wpf_write(struct vsp1_rwpf *wpf, u32 reg, u32 data) +{ + vsp1_write(wpf->entity.vsp1, + reg + wpf->entity.index * VI6_WPF_OFFSET, data); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Core Operations + */ + +static int wpf_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct vsp1_rwpf *wpf = to_rwpf(subdev); + struct vsp1_pipeline *pipe = + to_vsp1_pipeline(&wpf->entity.subdev.entity); + struct vsp1_device *vsp1 = wpf->entity.vsp1; + const struct v4l2_mbus_framefmt *format = + &wpf->entity.formats[RWPF_PAD_SOURCE]; + unsigned int i; + u32 srcrpf = 0; + u32 outfmt = 0; + + if (!enable) { + vsp1_write(vsp1, VI6_WPF_IRQ_ENB(wpf->entity.index), 0); + return 0; + } + + /* Sources */ + for (i = 0; i < pipe->num_inputs; ++i) { + struct vsp1_rwpf *input = pipe->inputs[i]; + + srcrpf |= VI6_WPF_SRCRPF_RPF_ACT_MST(input->entity.index); + } + + vsp1_wpf_write(wpf, VI6_WPF_SRCRPF, srcrpf); + + /* Destination stride. Cropping isn't supported yet. */ + if (!pipe->lif) { + struct v4l2_pix_format_mplane *format = &wpf->video.format; + + vsp1_wpf_write(wpf, VI6_WPF_DSTM_STRIDE_Y, + format->plane_fmt[0].bytesperline); + if (format->num_planes > 1) + vsp1_wpf_write(wpf, VI6_WPF_DSTM_STRIDE_C, + format->plane_fmt[1].bytesperline); + } + + vsp1_wpf_write(wpf, VI6_WPF_HSZCLIP, + format->width << VI6_WPF_SZCLIP_SIZE_SHIFT); + vsp1_wpf_write(wpf, VI6_WPF_VSZCLIP, + format->height << VI6_WPF_SZCLIP_SIZE_SHIFT); + + /* Format */ + if (!pipe->lif) { + const struct vsp1_format_info *fmtinfo = wpf->video.fmtinfo; + + outfmt = fmtinfo->hwfmt << VI6_WPF_OUTFMT_WRFMT_SHIFT; + + if (fmtinfo->swap_yc) + outfmt |= VI6_WPF_OUTFMT_SPYCS; + if (fmtinfo->swap_uv) + outfmt |= VI6_WPF_OUTFMT_SPUVS; + + vsp1_wpf_write(wpf, VI6_WPF_DSWAP, fmtinfo->swap); + } + + if (wpf->entity.formats[RWPF_PAD_SINK].code != + wpf->entity.formats[RWPF_PAD_SOURCE].code) + outfmt |= VI6_WPF_OUTFMT_CSC; + + vsp1_wpf_write(wpf, VI6_WPF_OUTFMT, outfmt); + + vsp1_write(vsp1, VI6_DPR_WPF_FPORCH(wpf->entity.index), + VI6_DPR_WPF_FPORCH_FP_WPFN); + + vsp1_write(vsp1, VI6_WPF_WRBCK_CTRL, 0); + + /* Enable interrupts */ + vsp1_write(vsp1, VI6_WPF_IRQ_STA(wpf->entity.index), 0); + vsp1_write(vsp1, VI6_WPF_IRQ_ENB(wpf->entity.index), + VI6_WFP_IRQ_ENB_FREE); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static struct v4l2_subdev_video_ops wpf_video_ops = { + .s_stream = wpf_s_stream, +}; + +static struct v4l2_subdev_pad_ops wpf_pad_ops = { + .enum_mbus_code = vsp1_rwpf_enum_mbus_code, + .enum_frame_size = vsp1_rwpf_enum_frame_size, + .get_fmt = vsp1_rwpf_get_format, + .set_fmt = vsp1_rwpf_set_format, +}; + +static struct v4l2_subdev_ops wpf_ops = { + .video = &wpf_video_ops, + .pad = &wpf_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Video Device Operations + */ + +static void wpf_vdev_queue(struct vsp1_video *video, + struct vsp1_video_buffer *buf) +{ + struct vsp1_rwpf *wpf = container_of(video, struct vsp1_rwpf, video); + + vsp1_wpf_write(wpf, VI6_WPF_DSTM_ADDR_Y, buf->addr[0]); + if (buf->buf.num_planes > 1) + vsp1_wpf_write(wpf, VI6_WPF_DSTM_ADDR_C0, buf->addr[1]); + if (buf->buf.num_planes > 2) + vsp1_wpf_write(wpf, VI6_WPF_DSTM_ADDR_C1, buf->addr[2]); +} + +static const struct vsp1_video_operations wpf_vdev_ops = { + .queue = wpf_vdev_queue, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_rwpf *vsp1_wpf_create(struct vsp1_device *vsp1, unsigned int index) +{ + struct v4l2_subdev *subdev; + struct vsp1_video *video; + struct vsp1_rwpf *wpf; + unsigned int flags; + int ret; + + wpf = devm_kzalloc(vsp1->dev, sizeof(*wpf), GFP_KERNEL); + if (wpf == NULL) + return ERR_PTR(-ENOMEM); + + wpf->max_width = WPF_MAX_WIDTH; + wpf->max_height = WPF_MAX_HEIGHT; + + wpf->entity.type = VSP1_ENTITY_WPF; + wpf->entity.index = index; + wpf->entity.id = VI6_DPR_NODE_WPF(index); + + ret = vsp1_entity_init(vsp1, &wpf->entity, 2); + if (ret < 0) + return ERR_PTR(ret); + + /* Initialize the V4L2 subdev. */ + subdev = &wpf->entity.subdev; + v4l2_subdev_init(subdev, &wpf_ops); + + subdev->entity.ops = &vsp1_media_ops; + subdev->internal_ops = &vsp1_subdev_internal_ops; + snprintf(subdev->name, sizeof(subdev->name), "%s wpf.%u", + dev_name(vsp1->dev), index); + v4l2_set_subdevdata(subdev, wpf); + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + vsp1_entity_init_formats(subdev, NULL); + + /* Initialize the video device. */ + video = &wpf->video; + + video->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + video->vsp1 = vsp1; + video->ops = &wpf_vdev_ops; + + ret = vsp1_video_init(video, &wpf->entity); + if (ret < 0) + goto error_video; + + /* Connect the video device to the WPF. All connections are immutable + * except for the WPF0 source link if a LIF is present. + */ + flags = MEDIA_LNK_FL_ENABLED; + if (!(vsp1->pdata->features & VSP1_HAS_LIF) || index != 0) + flags |= MEDIA_LNK_FL_IMMUTABLE; + + ret = media_entity_create_link(&wpf->entity.subdev.entity, + RWPF_PAD_SOURCE, + &wpf->video.video.entity, 0, flags); + if (ret < 0) + goto error_link; + + wpf->entity.sink = &wpf->video.video.entity; + + return wpf; + +error_link: + vsp1_video_cleanup(video); +error_video: + media_entity_cleanup(&wpf->entity.subdev.entity); + return ERR_PTR(ret); +} diff --git a/include/linux/platform_data/vsp1.h b/include/linux/platform_data/vsp1.h new file mode 100644 index 000000000000..a73a456d7f11 --- /dev/null +++ b/include/linux/platform_data/vsp1.h @@ -0,0 +1,25 @@ +/* + * vsp1.h -- R-Car VSP1 Platform Data + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * 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. + */ +#ifndef __PLATFORM_VSP1_H__ +#define __PLATFORM_VSP1_H__ + +#define VSP1_HAS_LIF (1 << 0) + +struct vsp1_platform_data { + unsigned int features; + unsigned int rpf_count; + unsigned int uds_count; + unsigned int wpf_count; +}; + +#endif /* __PLATFORM_VSP1_H__ */ -- cgit v1.2.3-71-gd317 From 73135e969970304a474c18c9f732fa3e36d88514 Mon Sep 17 00:00:00 2001 From: Vladimir Barinov Date: Thu, 25 Jul 2013 17:23:10 -0300 Subject: [media] V4L2: soc_camera: Renesas R-Car VIN driver Add Renesas R-Car VIN (Video In) V4L2 driver. Based on the patch by Phil Edworthy . [Sergei: removed deprecated IRQF_DISABLED flag, reordered/renamed 'enum chip_id' values, reordered rcar_vin_id_table[] entries, removed senseless parens from to_buf_list() macro, used ALIGN() macro in rcar_vin_setup(), added {} to the *if* statement and used 'bool' values instead of 0/1 where necessary, removed unused macros, done some reformatting and clarified some comments.] Signed-off-by: Vladimir Barinov Signed-off-by: Sergei Shtylyov Signed-off-by: Guennadi Liakhovetski Signed-off-by: Mauro Carvalho Chehab --- drivers/media/platform/soc_camera/Kconfig | 8 + drivers/media/platform/soc_camera/Makefile | 1 + drivers/media/platform/soc_camera/rcar_vin.c | 1486 ++++++++++++++++++++++++++ include/linux/platform_data/camera-rcar.h | 25 + 4 files changed, 1520 insertions(+) create mode 100644 drivers/media/platform/soc_camera/rcar_vin.c create mode 100644 include/linux/platform_data/camera-rcar.h (limited to 'include/linux/platform_data') diff --git a/drivers/media/platform/soc_camera/Kconfig b/drivers/media/platform/soc_camera/Kconfig index 626dcccc37da..af39c4665554 100644 --- a/drivers/media/platform/soc_camera/Kconfig +++ b/drivers/media/platform/soc_camera/Kconfig @@ -44,6 +44,14 @@ config VIDEO_PXA27x ---help--- This is a v4l2 driver for the PXA27x Quick Capture Interface +config VIDEO_RCAR_VIN + tristate "R-Car Video Input (VIN) support" + depends on VIDEO_DEV && SOC_CAMERA + select VIDEOBUF2_DMA_CONTIG + select SOC_CAMERA_SCALE_CROP + ---help--- + This is a v4l2 driver for the R-Car VIN Interface + config VIDEO_SH_MOBILE_CSI2 tristate "SuperH Mobile MIPI CSI-2 Interface driver" depends on VIDEO_DEV && SOC_CAMERA && HAVE_CLK diff --git a/drivers/media/platform/soc_camera/Makefile b/drivers/media/platform/soc_camera/Makefile index 39186224c16a..8aed26d7a64d 100644 --- a/drivers/media/platform/soc_camera/Makefile +++ b/drivers/media/platform/soc_camera/Makefile @@ -14,3 +14,4 @@ obj-$(CONFIG_VIDEO_OMAP1) += omap1_camera.o obj-$(CONFIG_VIDEO_PXA27x) += pxa_camera.o obj-$(CONFIG_VIDEO_SH_MOBILE_CEU) += sh_mobile_ceu_camera.o obj-$(CONFIG_VIDEO_SH_MOBILE_CSI2) += sh_mobile_csi2.o +obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar_vin.o diff --git a/drivers/media/platform/soc_camera/rcar_vin.c b/drivers/media/platform/soc_camera/rcar_vin.c new file mode 100644 index 000000000000..d02a7e0b773f --- /dev/null +++ b/drivers/media/platform/soc_camera/rcar_vin.c @@ -0,0 +1,1486 @@ +/* + * SoC-camera host driver for Renesas R-Car VIN unit + * + * Copyright (C) 2011-2013 Renesas Solutions Corp. + * Copyright (C) 2013 Cogent Embedded, Inc., + * + * Based on V4L2 Driver for SuperH Mobile CEU interface "sh_mobile_ceu_camera.c" + * + * Copyright (C) 2008 Magnus Damm + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "soc_scale_crop.h" + +#define DRV_NAME "rcar_vin" + +/* Register offsets for R-Car VIN */ +#define VNMC_REG 0x00 /* Video n Main Control Register */ +#define VNMS_REG 0x04 /* Video n Module Status Register */ +#define VNFC_REG 0x08 /* Video n Frame Capture Register */ +#define VNSLPRC_REG 0x0C /* Video n Start Line Pre-Clip Register */ +#define VNELPRC_REG 0x10 /* Video n End Line Pre-Clip Register */ +#define VNSPPRC_REG 0x14 /* Video n Start Pixel Pre-Clip Register */ +#define VNEPPRC_REG 0x18 /* Video n End Pixel Pre-Clip Register */ +#define VNSLPOC_REG 0x1C /* Video n Start Line Post-Clip Register */ +#define VNELPOC_REG 0x20 /* Video n End Line Post-Clip Register */ +#define VNSPPOC_REG 0x24 /* Video n Start Pixel Post-Clip Register */ +#define VNEPPOC_REG 0x28 /* Video n End Pixel Post-Clip Register */ +#define VNIS_REG 0x2C /* Video n Image Stride Register */ +#define VNMB_REG(m) (0x30 + ((m) << 2)) /* Video n Memory Base m Register */ +#define VNIE_REG 0x40 /* Video n Interrupt Enable Register */ +#define VNINTS_REG 0x44 /* Video n Interrupt Status Register */ +#define VNSI_REG 0x48 /* Video n Scanline Interrupt Register */ +#define VNMTC_REG 0x4C /* Video n Memory Transfer Control Register */ +#define VNYS_REG 0x50 /* Video n Y Scale Register */ +#define VNXS_REG 0x54 /* Video n X Scale Register */ +#define VNDMR_REG 0x58 /* Video n Data Mode Register */ +#define VNDMR2_REG 0x5C /* Video n Data Mode Register 2 */ +#define VNUVAOF_REG 0x60 /* Video n UV Address Offset Register */ + +/* Register bit fields for R-Car VIN */ +/* Video n Main Control Register bits */ +#define VNMC_FOC (1 << 21) +#define VNMC_YCAL (1 << 19) +#define VNMC_INF_YUV8_BT656 (0 << 16) +#define VNMC_INF_YUV8_BT601 (1 << 16) +#define VNMC_INF_YUV16 (5 << 16) +#define VNMC_VUP (1 << 10) +#define VNMC_IM_ODD (0 << 3) +#define VNMC_IM_ODD_EVEN (1 << 3) +#define VNMC_IM_EVEN (2 << 3) +#define VNMC_IM_FULL (3 << 3) +#define VNMC_BPS (1 << 1) +#define VNMC_ME (1 << 0) + +/* Video n Module Status Register bits */ +#define VNMS_FBS_MASK (3 << 3) +#define VNMS_FBS_SHIFT 3 +#define VNMS_AV (1 << 1) +#define VNMS_CA (1 << 0) + +/* Video n Frame Capture Register bits */ +#define VNFC_C_FRAME (1 << 1) +#define VNFC_S_FRAME (1 << 0) + +/* Video n Interrupt Enable Register bits */ +#define VNIE_FIE (1 << 4) +#define VNIE_EFE (1 << 1) + +/* Video n Data Mode Register bits */ +#define VNDMR_EXRGB (1 << 8) +#define VNDMR_BPSM (1 << 4) +#define VNDMR_DTMD_YCSEP (1 << 1) +#define VNDMR_DTMD_ARGB1555 (1 << 0) + +/* Video n Data Mode Register 2 bits */ +#define VNDMR2_VPS (1 << 30) +#define VNDMR2_HPS (1 << 29) +#define VNDMR2_FTEV (1 << 17) + +#define VIN_MAX_WIDTH 2048 +#define VIN_MAX_HEIGHT 2048 + +enum chip_id { + RCAR_H1, + RCAR_M1, + RCAR_E1, +}; + +enum rcar_vin_state { + STOPPED = 0, + RUNNING, + STOPPING, +}; + +struct rcar_vin_priv { + void __iomem *base; + spinlock_t lock; + int sequence; + /* State of the VIN module in capturing mode */ + enum rcar_vin_state state; + struct rcar_vin_platform_data *pdata; + struct soc_camera_host ici; + struct list_head capture; +#define MAX_BUFFER_NUM 3 + struct vb2_buffer *queue_buf[MAX_BUFFER_NUM]; + struct vb2_alloc_ctx *alloc_ctx; + enum v4l2_field field; + unsigned int vb_count; + unsigned int nr_hw_slots; + bool request_to_stop; + struct completion capture_stop; + enum chip_id chip; +}; + +#define is_continuous_transfer(priv) (priv->vb_count > MAX_BUFFER_NUM) + +struct rcar_vin_buffer { + struct vb2_buffer vb; + struct list_head list; +}; + +#define to_buf_list(vb2_buffer) (&container_of(vb2_buffer, \ + struct rcar_vin_buffer, \ + vb)->list) + +struct rcar_vin_cam { + /* VIN offsets within the camera output, before the VIN scaler */ + unsigned int vin_left; + unsigned int vin_top; + /* Client output, as seen by the VIN */ + unsigned int width; + unsigned int height; + /* + * User window from S_CROP / G_CROP, produced by client cropping and + * scaling, VIN scaling and VIN cropping, mapped back onto the client + * input window + */ + struct v4l2_rect subrect; + /* Camera cropping rectangle */ + struct v4l2_rect rect; + const struct soc_mbus_pixelfmt *extra_fmt; +}; + +/* + * .queue_setup() is called to check whether the driver can accept the requested + * number of buffers and to fill in plane sizes for the current frame format if + * required + */ +static int rcar_vin_videobuf_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *count, + unsigned int *num_planes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct soc_camera_device *icd = soc_camera_from_vb2q(vq); + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_priv *priv = ici->priv; + + if (fmt) { + const struct soc_camera_format_xlate *xlate; + unsigned int bytes_per_line; + int ret; + + xlate = soc_camera_xlate_by_fourcc(icd, + fmt->fmt.pix.pixelformat); + if (!xlate) + return -EINVAL; + ret = soc_mbus_bytes_per_line(fmt->fmt.pix.width, + xlate->host_fmt); + if (ret < 0) + return ret; + + bytes_per_line = max_t(u32, fmt->fmt.pix.bytesperline, ret); + + ret = soc_mbus_image_size(xlate->host_fmt, bytes_per_line, + fmt->fmt.pix.height); + if (ret < 0) + return ret; + + sizes[0] = max_t(u32, fmt->fmt.pix.sizeimage, ret); + } else { + /* Called from VIDIOC_REQBUFS or in compatibility mode */ + sizes[0] = icd->sizeimage; + } + + alloc_ctxs[0] = priv->alloc_ctx; + + if (!vq->num_buffers) + priv->sequence = 0; + + if (!*count) + *count = 2; + priv->vb_count = *count; + + *num_planes = 1; + + /* Number of hardware slots */ + if (is_continuous_transfer(priv)) + priv->nr_hw_slots = MAX_BUFFER_NUM; + else + priv->nr_hw_slots = 1; + + dev_dbg(icd->parent, "count=%d, size=%u\n", *count, sizes[0]); + + return 0; +} + +static int rcar_vin_setup(struct rcar_vin_priv *priv) +{ + struct soc_camera_device *icd = priv->ici.icd; + struct rcar_vin_cam *cam = icd->host_priv; + u32 vnmc, dmr, interrupts; + bool progressive = false, output_is_yuv = false; + + switch (priv->field) { + case V4L2_FIELD_TOP: + vnmc = VNMC_IM_ODD; + break; + case V4L2_FIELD_BOTTOM: + vnmc = VNMC_IM_EVEN; + break; + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_INTERLACED_TB: + vnmc = VNMC_IM_FULL; + break; + case V4L2_FIELD_INTERLACED_BT: + vnmc = VNMC_IM_FULL | VNMC_FOC; + break; + case V4L2_FIELD_NONE: + if (is_continuous_transfer(priv)) { + vnmc = VNMC_IM_ODD_EVEN; + progressive = true; + } else { + vnmc = VNMC_IM_ODD; + } + break; + default: + vnmc = VNMC_IM_ODD; + break; + } + + /* input interface */ + switch (icd->current_fmt->code) { + case V4L2_MBUS_FMT_YUYV8_1X16: + /* BT.601/BT.1358 16bit YCbCr422 */ + vnmc |= VNMC_INF_YUV16; + break; + case V4L2_MBUS_FMT_YUYV8_2X8: + /* BT.656 8bit YCbCr422 or BT.601 8bit YCbCr422 */ + vnmc |= priv->pdata->flags & RCAR_VIN_BT656 ? + VNMC_INF_YUV8_BT656 : VNMC_INF_YUV8_BT601; + default: + break; + } + + /* output format */ + switch (icd->current_fmt->host_fmt->fourcc) { + case V4L2_PIX_FMT_NV16: + iowrite32(ALIGN(cam->width * cam->height, 0x80), + priv->base + VNUVAOF_REG); + dmr = VNDMR_DTMD_YCSEP; + output_is_yuv = true; + break; + case V4L2_PIX_FMT_YUYV: + dmr = VNDMR_BPSM; + output_is_yuv = true; + break; + case V4L2_PIX_FMT_UYVY: + dmr = 0; + output_is_yuv = true; + break; + case V4L2_PIX_FMT_RGB555X: + dmr = VNDMR_DTMD_ARGB1555; + break; + case V4L2_PIX_FMT_RGB565: + dmr = 0; + break; + case V4L2_PIX_FMT_RGB32: + if (priv->chip == RCAR_H1 || priv->chip == RCAR_E1) { + dmr = VNDMR_EXRGB; + break; + } + default: + dev_warn(icd->parent, "Invalid fourcc format (0x%x)\n", + icd->current_fmt->host_fmt->fourcc); + return -EINVAL; + } + + /* Always update on field change */ + vnmc |= VNMC_VUP; + + /* If input and output use the same colorspace, use bypass mode */ + if (output_is_yuv) + vnmc |= VNMC_BPS; + + /* progressive or interlaced mode */ + interrupts = progressive ? VNIE_FIE | VNIE_EFE : VNIE_EFE; + + /* ack interrupts */ + iowrite32(interrupts, priv->base + VNINTS_REG); + /* enable interrupts */ + iowrite32(interrupts, priv->base + VNIE_REG); + /* start capturing */ + iowrite32(dmr, priv->base + VNDMR_REG); + iowrite32(vnmc | VNMC_ME, priv->base + VNMC_REG); + + return 0; +} + +static void rcar_vin_capture(struct rcar_vin_priv *priv) +{ + if (is_continuous_transfer(priv)) + /* Continuous Frame Capture Mode */ + iowrite32(VNFC_C_FRAME, priv->base + VNFC_REG); + else + /* Single Frame Capture Mode */ + iowrite32(VNFC_S_FRAME, priv->base + VNFC_REG); +} + +static void rcar_vin_request_capture_stop(struct rcar_vin_priv *priv) +{ + priv->state = STOPPING; + + /* set continuous & single transfer off */ + iowrite32(0, priv->base + VNFC_REG); + /* disable capture (release DMA buffer), reset */ + iowrite32(ioread32(priv->base + VNMC_REG) & ~VNMC_ME, + priv->base + VNMC_REG); + + /* update the status if stopped already */ + if (!(ioread32(priv->base + VNMS_REG) & VNMS_CA)) + priv->state = STOPPED; +} + +static int rcar_vin_get_free_hw_slot(struct rcar_vin_priv *priv) +{ + int slot; + + for (slot = 0; slot < priv->nr_hw_slots; slot++) + if (priv->queue_buf[slot] == NULL) + return slot; + + return -1; +} + +static int rcar_vin_hw_ready(struct rcar_vin_priv *priv) +{ + /* Ensure all HW slots are filled */ + return rcar_vin_get_free_hw_slot(priv) < 0 ? 1 : 0; +} + +/* Moves a buffer from the queue to the HW slots */ +static int rcar_vin_fill_hw_slot(struct rcar_vin_priv *priv) +{ + struct vb2_buffer *vb; + dma_addr_t phys_addr_top; + int slot; + + if (list_empty(&priv->capture)) + return 0; + + /* Find a free HW slot */ + slot = rcar_vin_get_free_hw_slot(priv); + if (slot < 0) + return 0; + + vb = &list_entry(priv->capture.next, struct rcar_vin_buffer, list)->vb; + list_del_init(to_buf_list(vb)); + priv->queue_buf[slot] = vb; + phys_addr_top = vb2_dma_contig_plane_dma_addr(vb, 0); + iowrite32(phys_addr_top, priv->base + VNMB_REG(slot)); + + return 1; +} + +static void rcar_vin_videobuf_queue(struct vb2_buffer *vb) +{ + struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue); + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_priv *priv = ici->priv; + unsigned long size; + + size = icd->sizeimage; + + if (vb2_plane_size(vb, 0) < size) { + dev_err(icd->parent, "Buffer #%d too small (%lu < %lu)\n", + vb->v4l2_buf.index, vb2_plane_size(vb, 0), size); + goto error; + } + + vb2_set_plane_payload(vb, 0, size); + + dev_dbg(icd->parent, "%s (vb=0x%p) 0x%p %lu\n", __func__, + vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0)); + + spin_lock_irq(&priv->lock); + + list_add_tail(to_buf_list(vb), &priv->capture); + rcar_vin_fill_hw_slot(priv); + + /* If we weren't running, and have enough buffers, start capturing! */ + if (priv->state != RUNNING && rcar_vin_hw_ready(priv)) { + if (rcar_vin_setup(priv)) { + /* Submit error */ + list_del_init(to_buf_list(vb)); + spin_unlock_irq(&priv->lock); + goto error; + } + priv->request_to_stop = false; + init_completion(&priv->capture_stop); + priv->state = RUNNING; + rcar_vin_capture(priv); + } + + spin_unlock_irq(&priv->lock); + + return; + +error: + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); +} + +static void rcar_vin_videobuf_release(struct vb2_buffer *vb) +{ + struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue); + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_priv *priv = ici->priv; + unsigned int i; + int buf_in_use = 0; + + spin_lock_irq(&priv->lock); + + /* Is the buffer in use by the VIN hardware? */ + for (i = 0; i < MAX_BUFFER_NUM; i++) { + if (priv->queue_buf[i] == vb) { + buf_in_use = 1; + break; + } + } + + if (buf_in_use) { + while (priv->state != STOPPED) { + + /* issue stop if running */ + if (priv->state == RUNNING) + rcar_vin_request_capture_stop(priv); + + /* wait until capturing has been stopped */ + if (priv->state == STOPPING) { + priv->request_to_stop = true; + spin_unlock_irq(&priv->lock); + wait_for_completion(&priv->capture_stop); + spin_lock_irq(&priv->lock); + } + } + /* + * Capturing has now stopped. The buffer we have been asked + * to release could be any of the current buffers in use, so + * release all buffers that are in use by HW + */ + for (i = 0; i < MAX_BUFFER_NUM; i++) { + if (priv->queue_buf[i]) { + vb2_buffer_done(priv->queue_buf[i], + VB2_BUF_STATE_ERROR); + priv->queue_buf[i] = NULL; + } + } + } else { + list_del_init(to_buf_list(vb)); + } + + spin_unlock_irq(&priv->lock); +} + +static int rcar_vin_videobuf_init(struct vb2_buffer *vb) +{ + INIT_LIST_HEAD(to_buf_list(vb)); + return 0; +} + +static int rcar_vin_stop_streaming(struct vb2_queue *vq) +{ + struct soc_camera_device *icd = soc_camera_from_vb2q(vq); + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_priv *priv = ici->priv; + struct list_head *buf_head, *tmp; + + spin_lock_irq(&priv->lock); + list_for_each_safe(buf_head, tmp, &priv->capture) + list_del_init(buf_head); + spin_unlock_irq(&priv->lock); + + return 0; +} + +static struct vb2_ops rcar_vin_vb2_ops = { + .queue_setup = rcar_vin_videobuf_setup, + .buf_init = rcar_vin_videobuf_init, + .buf_cleanup = rcar_vin_videobuf_release, + .buf_queue = rcar_vin_videobuf_queue, + .stop_streaming = rcar_vin_stop_streaming, + .wait_prepare = soc_camera_unlock, + .wait_finish = soc_camera_lock, +}; + +static irqreturn_t rcar_vin_irq(int irq, void *data) +{ + struct rcar_vin_priv *priv = data; + u32 int_status; + bool can_run = false, hw_stopped; + int slot; + unsigned int handled = 0; + + spin_lock(&priv->lock); + + int_status = ioread32(priv->base + VNINTS_REG); + if (!int_status) + goto done; + /* ack interrupts */ + iowrite32(int_status, priv->base + VNINTS_REG); + handled = 1; + + /* nothing to do if capture status is 'STOPPED' */ + if (priv->state == STOPPED) + goto done; + + hw_stopped = !(ioread32(priv->base + VNMS_REG) & VNMS_CA); + + if (!priv->request_to_stop) { + if (is_continuous_transfer(priv)) + slot = (ioread32(priv->base + VNMS_REG) & + VNMS_FBS_MASK) >> VNMS_FBS_SHIFT; + else + slot = 0; + + priv->queue_buf[slot]->v4l2_buf.field = priv->field; + priv->queue_buf[slot]->v4l2_buf.sequence = priv->sequence++; + do_gettimeofday(&priv->queue_buf[slot]->v4l2_buf.timestamp); + vb2_buffer_done(priv->queue_buf[slot], VB2_BUF_STATE_DONE); + priv->queue_buf[slot] = NULL; + + if (priv->state != STOPPING) + can_run = rcar_vin_fill_hw_slot(priv); + + if (hw_stopped || !can_run) { + priv->state = STOPPED; + } else if (is_continuous_transfer(priv) && + list_empty(&priv->capture) && + priv->state == RUNNING) { + /* + * The continuous capturing requires an explicit stop + * operation when there is no buffer to be set into + * the VnMBm registers. + */ + rcar_vin_request_capture_stop(priv); + } else { + rcar_vin_capture(priv); + } + + } else if (hw_stopped) { + priv->state = STOPPED; + priv->request_to_stop = false; + complete(&priv->capture_stop); + } + +done: + spin_unlock(&priv->lock); + + return IRQ_RETVAL(handled); +} + +static int rcar_vin_add_device(struct soc_camera_device *icd) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_priv *priv = ici->priv; + int i; + + for (i = 0; i < MAX_BUFFER_NUM; i++) + priv->queue_buf[i] = NULL; + + pm_runtime_get_sync(ici->v4l2_dev.dev); + + dev_dbg(icd->parent, "R-Car VIN driver attached to camera %d\n", + icd->devnum); + + return 0; +} + +static void rcar_vin_remove_device(struct soc_camera_device *icd) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_priv *priv = ici->priv; + struct vb2_buffer *vb; + int i; + + /* disable capture, disable interrupts */ + iowrite32(ioread32(priv->base + VNMC_REG) & ~VNMC_ME, + priv->base + VNMC_REG); + iowrite32(0, priv->base + VNIE_REG); + + priv->state = STOPPED; + priv->request_to_stop = false; + + /* make sure active buffer is cancelled */ + spin_lock_irq(&priv->lock); + for (i = 0; i < MAX_BUFFER_NUM; i++) { + vb = priv->queue_buf[i]; + if (vb) { + list_del_init(to_buf_list(vb)); + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + } + } + spin_unlock_irq(&priv->lock); + + pm_runtime_put(ici->v4l2_dev.dev); + + dev_dbg(icd->parent, "R-Car VIN driver detached from camera %d\n", + icd->devnum); +} + +/* Called with .host_lock held */ +static int rcar_vin_clock_start(struct soc_camera_host *ici) +{ + /* VIN does not have "mclk" */ + return 0; +} + +/* Called with .host_lock held */ +static void rcar_vin_clock_stop(struct soc_camera_host *ici) +{ + /* VIN does not have "mclk" */ +} + +/* rect is guaranteed to not exceed the scaled camera rectangle */ +static int rcar_vin_set_rect(struct soc_camera_device *icd) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_cam *cam = icd->host_priv; + struct rcar_vin_priv *priv = ici->priv; + unsigned int left_offset, top_offset; + unsigned char dsize = 0; + struct v4l2_rect *cam_subrect = &cam->subrect; + + dev_dbg(icd->parent, "Crop %ux%u@%u:%u\n", + icd->user_width, icd->user_height, cam->vin_left, cam->vin_top); + + left_offset = cam->vin_left; + top_offset = cam->vin_top; + + if (icd->current_fmt->host_fmt->fourcc == V4L2_PIX_FMT_RGB32 && + priv->chip == RCAR_E1) + dsize = 1; + + dev_dbg(icd->parent, "Cam %ux%u@%u:%u\n", + cam->width, cam->height, cam->vin_left, cam->vin_top); + dev_dbg(icd->parent, "Cam subrect %ux%u@%u:%u\n", + cam_subrect->width, cam_subrect->height, + cam_subrect->left, cam_subrect->top); + + /* Set Start/End Pixel/Line Pre-Clip */ + iowrite32(left_offset << dsize, priv->base + VNSPPRC_REG); + iowrite32((left_offset + cam->width - 1) << dsize, + priv->base + VNEPPRC_REG); + switch (priv->field) { + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + iowrite32(top_offset / 2, priv->base + VNSLPRC_REG); + iowrite32((top_offset + cam->height) / 2 - 1, + priv->base + VNELPRC_REG); + break; + default: + iowrite32(top_offset, priv->base + VNSLPRC_REG); + iowrite32(top_offset + cam->height - 1, + priv->base + VNELPRC_REG); + break; + } + + /* Set Start/End Pixel/Line Post-Clip */ + iowrite32(0, priv->base + VNSPPOC_REG); + iowrite32(0, priv->base + VNSLPOC_REG); + iowrite32((cam_subrect->width - 1) << dsize, priv->base + VNEPPOC_REG); + switch (priv->field) { + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + iowrite32(cam_subrect->height / 2 - 1, + priv->base + VNELPOC_REG); + break; + default: + iowrite32(cam_subrect->height - 1, priv->base + VNELPOC_REG); + break; + } + + iowrite32(ALIGN(cam->width, 0x10), priv->base + VNIS_REG); + + return 0; +} + +static void capture_stop_preserve(struct rcar_vin_priv *priv, u32 *vnmc) +{ + *vnmc = ioread32(priv->base + VNMC_REG); + /* module disable */ + iowrite32(*vnmc & ~VNMC_ME, priv->base + VNMC_REG); +} + +static void capture_restore(struct rcar_vin_priv *priv, u32 vnmc) +{ + unsigned long timeout = jiffies + 10 * HZ; + + /* + * Wait until the end of the current frame. It can take a long time, + * but if it has been aborted by a MRST1 reset, it should exit sooner. + */ + while ((ioread32(priv->base + VNMS_REG) & VNMS_AV) && + time_before(jiffies, timeout)) + msleep(1); + + if (time_after(jiffies, timeout)) { + dev_err(priv->ici.v4l2_dev.dev, + "Timeout waiting for frame end! Interface problem?\n"); + return; + } + + iowrite32(vnmc, priv->base + VNMC_REG); +} + +#define VIN_MBUS_FLAGS (V4L2_MBUS_MASTER | \ + V4L2_MBUS_PCLK_SAMPLE_RISING | \ + V4L2_MBUS_HSYNC_ACTIVE_HIGH | \ + V4L2_MBUS_HSYNC_ACTIVE_LOW | \ + V4L2_MBUS_VSYNC_ACTIVE_HIGH | \ + V4L2_MBUS_VSYNC_ACTIVE_LOW | \ + V4L2_MBUS_DATA_ACTIVE_HIGH) + +static int rcar_vin_set_bus_param(struct soc_camera_device *icd) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_priv *priv = ici->priv; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct v4l2_mbus_config cfg; + unsigned long common_flags; + u32 vnmc; + u32 val; + int ret; + + capture_stop_preserve(priv, &vnmc); + + ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg); + if (!ret) { + common_flags = soc_mbus_config_compatible(&cfg, VIN_MBUS_FLAGS); + if (!common_flags) { + dev_warn(icd->parent, + "MBUS flags incompatible: camera 0x%x, host 0x%x\n", + cfg.flags, VIN_MBUS_FLAGS); + return -EINVAL; + } + } else if (ret != -ENOIOCTLCMD) { + return ret; + } else { + common_flags = VIN_MBUS_FLAGS; + } + + /* Make choises, based on platform preferences */ + if ((common_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) && + (common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) { + if (priv->pdata->flags & RCAR_VIN_HSYNC_ACTIVE_LOW) + common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_HIGH; + else + common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_LOW; + } + + if ((common_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) && + (common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) { + if (priv->pdata->flags & RCAR_VIN_VSYNC_ACTIVE_LOW) + common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_HIGH; + else + common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_LOW; + } + + cfg.flags = common_flags; + ret = v4l2_subdev_call(sd, video, s_mbus_config, &cfg); + if (ret < 0 && ret != -ENOIOCTLCMD) + return ret; + + val = priv->field == V4L2_FIELD_NONE ? VNDMR2_FTEV : 0; + if (!(common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) + val |= VNDMR2_VPS; + if (!(common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) + val |= VNDMR2_HPS; + iowrite32(val, priv->base + VNDMR2_REG); + + ret = rcar_vin_set_rect(icd); + if (ret < 0) + return ret; + + capture_restore(priv, vnmc); + + return 0; +} + +static int rcar_vin_try_bus_param(struct soc_camera_device *icd, + unsigned char buswidth) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct v4l2_mbus_config cfg; + int ret; + + ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg); + if (ret == -ENOIOCTLCMD) + return 0; + else if (ret) + return ret; + + if (buswidth > 24) + return -EINVAL; + + /* check is there common mbus flags */ + ret = soc_mbus_config_compatible(&cfg, VIN_MBUS_FLAGS); + if (ret) + return 0; + + dev_warn(icd->parent, + "MBUS flags incompatible: camera 0x%x, host 0x%x\n", + cfg.flags, VIN_MBUS_FLAGS); + + return -EINVAL; +} + +static bool rcar_vin_packing_supported(const struct soc_mbus_pixelfmt *fmt) +{ + return fmt->packing == SOC_MBUS_PACKING_NONE || + (fmt->bits_per_sample > 8 && + fmt->packing == SOC_MBUS_PACKING_EXTEND16); +} + +static const struct soc_mbus_pixelfmt rcar_vin_formats[] = { + { + .fourcc = V4L2_PIX_FMT_NV16, + .name = "NV16", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_2X8_PADHI, + .order = SOC_MBUS_ORDER_LE, + .layout = SOC_MBUS_LAYOUT_PLANAR_Y_C, + }, + { + .fourcc = V4L2_PIX_FMT_UYVY, + .name = "UYVY", + .bits_per_sample = 16, + .packing = SOC_MBUS_PACKING_NONE, + .order = SOC_MBUS_ORDER_LE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, + { + .fourcc = V4L2_PIX_FMT_RGB565, + .name = "RGB565", + .bits_per_sample = 16, + .packing = SOC_MBUS_PACKING_NONE, + .order = SOC_MBUS_ORDER_LE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, + { + .fourcc = V4L2_PIX_FMT_RGB555X, + .name = "ARGB1555", + .bits_per_sample = 16, + .packing = SOC_MBUS_PACKING_NONE, + .order = SOC_MBUS_ORDER_LE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, + { + .fourcc = V4L2_PIX_FMT_RGB32, + .name = "RGB888", + .bits_per_sample = 32, + .packing = SOC_MBUS_PACKING_NONE, + .order = SOC_MBUS_ORDER_LE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, +}; + +static int rcar_vin_get_formats(struct soc_camera_device *icd, unsigned int idx, + struct soc_camera_format_xlate *xlate) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct device *dev = icd->parent; + int ret, k, n; + int formats = 0; + struct rcar_vin_cam *cam; + enum v4l2_mbus_pixelcode code; + const struct soc_mbus_pixelfmt *fmt; + + ret = v4l2_subdev_call(sd, video, enum_mbus_fmt, idx, &code); + if (ret < 0) + return 0; + + fmt = soc_mbus_get_fmtdesc(code); + if (!fmt) { + dev_warn(dev, "unsupported format code #%u: %d\n", idx, code); + return 0; + } + + ret = rcar_vin_try_bus_param(icd, fmt->bits_per_sample); + if (ret < 0) + return 0; + + if (!icd->host_priv) { + struct v4l2_mbus_framefmt mf; + struct v4l2_rect rect; + struct device *dev = icd->parent; + int shift; + + ret = v4l2_subdev_call(sd, video, g_mbus_fmt, &mf); + if (ret < 0) + return ret; + + /* Cache current client geometry */ + ret = soc_camera_client_g_rect(sd, &rect); + if (ret == -ENOIOCTLCMD) { + /* Sensor driver doesn't support cropping */ + rect.left = 0; + rect.top = 0; + rect.width = mf.width; + rect.height = mf.height; + } else if (ret < 0) { + return ret; + } + + /* + * If sensor proposes too large format then try smaller ones: + * 1280x960, 640x480, 320x240 + */ + for (shift = 0; shift < 3; shift++) { + if (mf.width <= VIN_MAX_WIDTH && + mf.height <= VIN_MAX_HEIGHT) + break; + + mf.width = 1280 >> shift; + mf.height = 960 >> shift; + ret = v4l2_device_call_until_err(sd->v4l2_dev, + soc_camera_grp_id(icd), + video, s_mbus_fmt, + &mf); + if (ret < 0) + return ret; + } + + if (shift == 3) { + dev_err(dev, + "Failed to configure the client below %ux%x\n", + mf.width, mf.height); + return -EIO; + } + + dev_dbg(dev, "camera fmt %ux%u\n", mf.width, mf.height); + + cam = kzalloc(sizeof(*cam), GFP_KERNEL); + if (!cam) + return -ENOMEM; + /* + * We are called with current camera crop, + * initialise subrect with it + */ + cam->rect = rect; + cam->subrect = rect; + cam->width = mf.width; + cam->height = mf.height; + + icd->host_priv = cam; + } else { + cam = icd->host_priv; + } + + /* Beginning of a pass */ + if (!idx) + cam->extra_fmt = NULL; + + switch (code) { + case V4L2_MBUS_FMT_YUYV8_1X16: + case V4L2_MBUS_FMT_YUYV8_2X8: + if (cam->extra_fmt) + break; + + /* Add all our formats that can be generated by VIN */ + cam->extra_fmt = rcar_vin_formats; + + n = ARRAY_SIZE(rcar_vin_formats); + formats += n; + for (k = 0; xlate && k < n; k++, xlate++) { + xlate->host_fmt = &rcar_vin_formats[k]; + xlate->code = code; + dev_dbg(dev, "Providing format %s using code %d\n", + rcar_vin_formats[k].name, code); + } + break; + default: + if (!rcar_vin_packing_supported(fmt)) + return 0; + + dev_dbg(dev, "Providing format %s in pass-through mode\n", + fmt->name); + break; + } + + /* Generic pass-through */ + formats++; + if (xlate) { + xlate->host_fmt = fmt; + xlate->code = code; + xlate++; + } + + return formats; +} + +static void rcar_vin_put_formats(struct soc_camera_device *icd) +{ + kfree(icd->host_priv); + icd->host_priv = NULL; +} + +static int rcar_vin_set_crop(struct soc_camera_device *icd, + const struct v4l2_crop *a) +{ + struct v4l2_crop a_writable = *a; + const struct v4l2_rect *rect = &a_writable.c; + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_priv *priv = ici->priv; + struct v4l2_crop cam_crop; + struct rcar_vin_cam *cam = icd->host_priv; + struct v4l2_rect *cam_rect = &cam_crop.c; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct device *dev = icd->parent; + struct v4l2_mbus_framefmt mf; + u32 vnmc; + int ret, i; + + dev_dbg(dev, "S_CROP(%ux%u@%u:%u)\n", rect->width, rect->height, + rect->left, rect->top); + + /* During camera cropping its output window can change too, stop VIN */ + capture_stop_preserve(priv, &vnmc); + dev_dbg(dev, "VNMC_REG 0x%x\n", vnmc); + + /* Apply iterative camera S_CROP for new input window. */ + ret = soc_camera_client_s_crop(sd, &a_writable, &cam_crop, + &cam->rect, &cam->subrect); + if (ret < 0) + return ret; + + dev_dbg(dev, "camera cropped to %ux%u@%u:%u\n", + cam_rect->width, cam_rect->height, + cam_rect->left, cam_rect->top); + + /* On success cam_crop contains current camera crop */ + + /* Retrieve camera output window */ + ret = v4l2_subdev_call(sd, video, g_mbus_fmt, &mf); + if (ret < 0) + return ret; + + if (mf.width > VIN_MAX_WIDTH || mf.height > VIN_MAX_HEIGHT) + return -EINVAL; + + /* Cache camera output window */ + cam->width = mf.width; + cam->height = mf.height; + + icd->user_width = cam->width; + icd->user_height = cam->height; + + cam->vin_left = rect->left & ~1; + cam->vin_top = rect->top & ~1; + + /* Use VIN cropping to crop to the new window. */ + ret = rcar_vin_set_rect(icd); + if (ret < 0) + return ret; + + cam->subrect = *rect; + + dev_dbg(dev, "VIN cropped to %ux%u@%u:%u\n", + icd->user_width, icd->user_height, + cam->vin_left, cam->vin_top); + + /* Restore capture */ + for (i = 0; i < MAX_BUFFER_NUM; i++) { + if (priv->queue_buf[i] && priv->state == STOPPED) { + vnmc |= VNMC_ME; + break; + } + } + capture_restore(priv, vnmc); + + /* Even if only camera cropping succeeded */ + return ret; +} + +static int rcar_vin_get_crop(struct soc_camera_device *icd, + struct v4l2_crop *a) +{ + struct rcar_vin_cam *cam = icd->host_priv; + + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + a->c = cam->subrect; + + return 0; +} + +/* Similar to set_crop multistage iterative algorithm */ +static int rcar_vin_set_fmt(struct soc_camera_device *icd, + struct v4l2_format *f) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct rcar_vin_priv *priv = ici->priv; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct rcar_vin_cam *cam = icd->host_priv; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct v4l2_mbus_framefmt mf; + struct device *dev = icd->parent; + __u32 pixfmt = pix->pixelformat; + const struct soc_camera_format_xlate *xlate; + unsigned int vin_sub_width = 0, vin_sub_height = 0; + int ret; + bool can_scale; + enum v4l2_field field; + v4l2_std_id std; + + dev_dbg(dev, "S_FMT(pix=0x%x, %ux%u)\n", + pixfmt, pix->width, pix->height); + + switch (pix->field) { + default: + pix->field = V4L2_FIELD_NONE; + /* fall-through */ + case V4L2_FIELD_NONE: + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + field = pix->field; + break; + case V4L2_FIELD_INTERLACED: + /* Query for standard if not explicitly mentioned _TB/_BT */ + ret = v4l2_subdev_call(sd, video, querystd, &std); + if (ret < 0) + std = V4L2_STD_625_50; + + field = std & V4L2_STD_625_50 ? V4L2_FIELD_INTERLACED_TB : + V4L2_FIELD_INTERLACED_BT; + break; + } + + xlate = soc_camera_xlate_by_fourcc(icd, pixfmt); + if (!xlate) { + dev_warn(dev, "Format %x not found\n", pixfmt); + return -EINVAL; + } + /* Calculate client output geometry */ + soc_camera_calc_client_output(icd, &cam->rect, &cam->subrect, pix, &mf, + 12); + mf.field = pix->field; + mf.colorspace = pix->colorspace; + mf.code = xlate->code; + + switch (pixfmt) { + case V4L2_PIX_FMT_RGB32: + can_scale = priv->chip != RCAR_E1; + break; + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_RGB555X: + can_scale = true; + break; + default: + can_scale = false; + break; + } + + dev_dbg(dev, "request camera output %ux%u\n", mf.width, mf.height); + + ret = soc_camera_client_scale(icd, &cam->rect, &cam->subrect, + &mf, &vin_sub_width, &vin_sub_height, + can_scale, 12); + + /* Done with the camera. Now see if we can improve the result */ + dev_dbg(dev, "Camera %d fmt %ux%u, requested %ux%u\n", + ret, mf.width, mf.height, pix->width, pix->height); + + if (ret == -ENOIOCTLCMD) + dev_dbg(dev, "Sensor doesn't support scaling\n"); + else if (ret < 0) + return ret; + + if (mf.code != xlate->code) + return -EINVAL; + + /* Prepare VIN crop */ + cam->width = mf.width; + cam->height = mf.height; + + /* Use VIN scaling to scale to the requested user window. */ + + /* We cannot scale up */ + if (pix->width > vin_sub_width) + vin_sub_width = pix->width; + + if (pix->height > vin_sub_height) + vin_sub_height = pix->height; + + pix->colorspace = mf.colorspace; + + if (!can_scale) { + pix->width = vin_sub_width; + pix->height = vin_sub_height; + } + + /* + * We have calculated CFLCR, the actual configuration will be performed + * in rcar_vin_set_bus_param() + */ + + dev_dbg(dev, "W: %u : %u, H: %u : %u\n", + vin_sub_width, pix->width, vin_sub_height, pix->height); + + icd->current_fmt = xlate; + + priv->field = field; + + return 0; +} + +static int rcar_vin_try_fmt(struct soc_camera_device *icd, + struct v4l2_format *f) +{ + const struct soc_camera_format_xlate *xlate; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct v4l2_mbus_framefmt mf; + __u32 pixfmt = pix->pixelformat; + int width, height; + int ret; + + xlate = soc_camera_xlate_by_fourcc(icd, pixfmt); + if (!xlate) { + xlate = icd->current_fmt; + dev_dbg(icd->parent, "Format %x not found, keeping %x\n", + pixfmt, xlate->host_fmt->fourcc); + pixfmt = xlate->host_fmt->fourcc; + pix->pixelformat = pixfmt; + pix->colorspace = icd->colorspace; + } + + /* FIXME: calculate using depth and bus width */ + v4l_bound_align_image(&pix->width, 2, VIN_MAX_WIDTH, 1, + &pix->height, 4, VIN_MAX_HEIGHT, 2, 0); + + width = pix->width; + height = pix->height; + + /* let soc-camera calculate these values */ + pix->bytesperline = 0; + pix->sizeimage = 0; + + /* limit to sensor capabilities */ + mf.width = pix->width; + mf.height = pix->height; + mf.field = pix->field; + mf.code = xlate->code; + mf.colorspace = pix->colorspace; + + ret = v4l2_device_call_until_err(sd->v4l2_dev, soc_camera_grp_id(icd), + video, try_mbus_fmt, &mf); + if (ret < 0) + return ret; + + pix->width = mf.width; + pix->height = mf.height; + pix->field = mf.field; + pix->colorspace = mf.colorspace; + + if (pixfmt == V4L2_PIX_FMT_NV16) { + /* FIXME: check against rect_max after converting soc-camera */ + /* We can scale precisely, need a bigger image from camera */ + if (pix->width < width || pix->height < height) { + /* + * We presume, the sensor behaves sanely, i.e. if + * requested a bigger rectangle, it will not return a + * smaller one. + */ + mf.width = VIN_MAX_WIDTH; + mf.height = VIN_MAX_HEIGHT; + ret = v4l2_device_call_until_err(sd->v4l2_dev, + soc_camera_grp_id(icd), + video, try_mbus_fmt, + &mf); + if (ret < 0) { + dev_err(icd->parent, + "client try_fmt() = %d\n", ret); + return ret; + } + } + /* We will scale exactly */ + if (mf.width > width) + pix->width = width; + if (mf.height > height) + pix->height = height; + } + + return ret; +} + +static unsigned int rcar_vin_poll(struct file *file, poll_table *pt) +{ + struct soc_camera_device *icd = file->private_data; + + return vb2_poll(&icd->vb2_vidq, file, pt); +} + +static int rcar_vin_querycap(struct soc_camera_host *ici, + struct v4l2_capability *cap) +{ + strlcpy(cap->card, "R_Car_VIN", sizeof(cap->card)); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + return 0; +} + +static int rcar_vin_init_videobuf2(struct vb2_queue *vq, + struct soc_camera_device *icd) +{ + vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vq->io_modes = VB2_MMAP | VB2_USERPTR; + vq->drv_priv = icd; + vq->ops = &rcar_vin_vb2_ops; + vq->mem_ops = &vb2_dma_contig_memops; + vq->buf_struct_size = sizeof(struct rcar_vin_buffer); + vq->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + + return vb2_queue_init(vq); +} + +static struct soc_camera_host_ops rcar_vin_host_ops = { + .owner = THIS_MODULE, + .add = rcar_vin_add_device, + .remove = rcar_vin_remove_device, + .clock_start = rcar_vin_clock_start, + .clock_stop = rcar_vin_clock_stop, + .get_formats = rcar_vin_get_formats, + .put_formats = rcar_vin_put_formats, + .get_crop = rcar_vin_get_crop, + .set_crop = rcar_vin_set_crop, + .try_fmt = rcar_vin_try_fmt, + .set_fmt = rcar_vin_set_fmt, + .poll = rcar_vin_poll, + .querycap = rcar_vin_querycap, + .set_bus_param = rcar_vin_set_bus_param, + .init_videobuf2 = rcar_vin_init_videobuf2, +}; + +static struct platform_device_id rcar_vin_id_table[] = { + { "r8a7779-vin", RCAR_H1 }, + { "r8a7778-vin", RCAR_M1 }, + { "uPD35004-vin", RCAR_E1 }, + {}, +}; +MODULE_DEVICE_TABLE(platform, rcar_vin_id_table); + +static int rcar_vin_probe(struct platform_device *pdev) +{ + struct rcar_vin_priv *priv; + struct resource *mem; + struct rcar_vin_platform_data *pdata; + int irq, ret; + + pdata = pdev->dev.platform_data; + if (!pdata || !pdata->flags) { + dev_err(&pdev->dev, "platform data not set\n"); + return -EINVAL; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (mem == NULL) + return -EINVAL; + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return -EINVAL; + + priv = devm_kzalloc(&pdev->dev, sizeof(struct rcar_vin_priv), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + ret = devm_request_irq(&pdev->dev, irq, rcar_vin_irq, IRQF_SHARED, + dev_name(&pdev->dev), priv); + if (ret) + return ret; + + priv->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); + if (IS_ERR(priv->alloc_ctx)) + return PTR_ERR(priv->alloc_ctx); + + priv->ici.priv = priv; + priv->ici.v4l2_dev.dev = &pdev->dev; + priv->ici.nr = pdev->id; + priv->ici.drv_name = dev_name(&pdev->dev); + priv->ici.ops = &rcar_vin_host_ops; + + priv->pdata = pdata; + priv->chip = pdev->id_entry->driver_data; + spin_lock_init(&priv->lock); + INIT_LIST_HEAD(&priv->capture); + + priv->state = STOPPED; + + pm_suspend_ignore_children(&pdev->dev, true); + pm_runtime_enable(&pdev->dev); + + ret = soc_camera_host_register(&priv->ici); + if (ret) + goto cleanup; + + return 0; + +cleanup: + pm_runtime_disable(&pdev->dev); + vb2_dma_contig_cleanup_ctx(priv->alloc_ctx); + + return ret; +} + +static int rcar_vin_remove(struct platform_device *pdev) +{ + struct soc_camera_host *soc_host = to_soc_camera_host(&pdev->dev); + struct rcar_vin_priv *priv = container_of(soc_host, + struct rcar_vin_priv, ici); + + soc_camera_host_unregister(soc_host); + pm_runtime_disable(&pdev->dev); + vb2_dma_contig_cleanup_ctx(priv->alloc_ctx); + + return 0; +} + +static struct platform_driver rcar_vin_driver = { + .probe = rcar_vin_probe, + .remove = rcar_vin_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .id_table = rcar_vin_id_table, +}; + +module_platform_driver(rcar_vin_driver); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rcar_vin"); +MODULE_DESCRIPTION("Renesas R-Car VIN camera host driver"); diff --git a/include/linux/platform_data/camera-rcar.h b/include/linux/platform_data/camera-rcar.h new file mode 100644 index 000000000000..dfc83c581593 --- /dev/null +++ b/include/linux/platform_data/camera-rcar.h @@ -0,0 +1,25 @@ +/* + * Platform data for Renesas R-Car VIN soc-camera driver + * + * Copyright (C) 2011-2013 Renesas Solutions Corp. + * Copyright (C) 2013 Cogent Embedded, Inc., + * + * 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. + */ + +#ifndef __CAMERA_RCAR_H_ +#define __CAMERA_RCAR_H_ + +#define RCAR_VIN_HSYNC_ACTIVE_LOW (1 << 0) +#define RCAR_VIN_VSYNC_ACTIVE_LOW (1 << 1) +#define RCAR_VIN_BT601 (1 << 2) +#define RCAR_VIN_BT656 (1 << 3) + +struct rcar_vin_platform_data { + unsigned int flags; +}; + +#endif /* __CAMERA_RCAR_H_ */ -- cgit v1.2.3-71-gd317 From 4dbfd040757b8bf22f4ac17e80b39c068061a16c Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Tue, 30 Jul 2013 02:59:49 -0300 Subject: [media] V4L2: mx3_camera: add support for asynchronous subdevice registration The soc-camera core does all the work on supporting asynchronous subdevice probing, host drivers only have to pass a subdevice list to soc-camera. Typically this list is provided by the platform. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Mauro Carvalho Chehab --- drivers/media/platform/soc_camera/mx3_camera.c | 16 +++++++++++++--- include/linux/platform_data/camera-mx3.h | 4 ++++ 2 files changed, 17 insertions(+), 3 deletions(-) (limited to 'include/linux/platform_data') diff --git a/drivers/media/platform/soc_camera/mx3_camera.c b/drivers/media/platform/soc_camera/mx3_camera.c index 83592e4ae532..8f9f6211c52e 100644 --- a/drivers/media/platform/soc_camera/mx3_camera.c +++ b/drivers/media/platform/soc_camera/mx3_camera.c @@ -1144,6 +1144,7 @@ static struct soc_camera_host_ops mx3_soc_camera_host_ops = { static int mx3_camera_probe(struct platform_device *pdev) { + struct mx3_camera_pdata *pdata = pdev->dev.platform_data; struct mx3_camera_dev *mx3_cam; struct resource *res; void __iomem *base; @@ -1155,6 +1156,9 @@ static int mx3_camera_probe(struct platform_device *pdev) if (IS_ERR(base)) return PTR_ERR(base); + if (!pdata) + return -EINVAL; + mx3_cam = devm_kzalloc(&pdev->dev, sizeof(*mx3_cam), GFP_KERNEL); if (!mx3_cam) { dev_err(&pdev->dev, "Could not allocate mx3 camera object\n"); @@ -1165,8 +1169,8 @@ static int mx3_camera_probe(struct platform_device *pdev) if (IS_ERR(mx3_cam->clk)) return PTR_ERR(mx3_cam->clk); - mx3_cam->pdata = pdev->dev.platform_data; - mx3_cam->platform_flags = mx3_cam->pdata->flags; + mx3_cam->pdata = pdata; + mx3_cam->platform_flags = pdata->flags; if (!(mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_MASK)) { /* * Platform hasn't set available data widths. This is bad. @@ -1185,7 +1189,7 @@ static int mx3_camera_probe(struct platform_device *pdev) if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_15) mx3_cam->width_flags |= 1 << 14; - mx3_cam->mclk = mx3_cam->pdata->mclk_10khz * 10000; + mx3_cam->mclk = pdata->mclk_10khz * 10000; if (!mx3_cam->mclk) { dev_warn(&pdev->dev, "mclk_10khz == 0! Please, fix your platform data. " @@ -1210,6 +1214,11 @@ static int mx3_camera_probe(struct platform_device *pdev) if (IS_ERR(mx3_cam->alloc_ctx)) return PTR_ERR(mx3_cam->alloc_ctx); + if (pdata->asd_sizes) { + soc_host->asd = pdata->asd; + soc_host->asd_sizes = pdata->asd_sizes; + } + err = soc_camera_host_register(soc_host); if (err) goto ecamhostreg; @@ -1249,6 +1258,7 @@ static int mx3_camera_remove(struct platform_device *pdev) static struct platform_driver mx3_camera_driver = { .driver = { .name = MX3_CAM_DRV_NAME, + .owner = THIS_MODULE, }, .probe = mx3_camera_probe, .remove = mx3_camera_remove, diff --git a/include/linux/platform_data/camera-mx3.h b/include/linux/platform_data/camera-mx3.h index f226ee3777e1..a910dadc8258 100644 --- a/include/linux/platform_data/camera-mx3.h +++ b/include/linux/platform_data/camera-mx3.h @@ -33,6 +33,8 @@ #define MX3_CAMERA_DATAWIDTH_MASK (MX3_CAMERA_DATAWIDTH_4 | MX3_CAMERA_DATAWIDTH_8 | \ MX3_CAMERA_DATAWIDTH_10 | MX3_CAMERA_DATAWIDTH_15) +struct v4l2_async_subdev; + /** * struct mx3_camera_pdata - i.MX3x camera platform data * @flags: MX3_CAMERA_* flags @@ -43,6 +45,8 @@ struct mx3_camera_pdata { unsigned long flags; unsigned long mclk_10khz; struct device *dma_dev; + struct v4l2_async_subdev **asd; /* Flat array, arranged in groups */ + int *asd_sizes; /* 0-terminated array of asd group sizes */ }; #endif -- cgit v1.2.3-71-gd317 From dbe34724c08ea25d39d31735120077013fbbb6fb Mon Sep 17 00:00:00 2001 From: Mugunthan V N Date: Mon, 19 Aug 2013 17:47:40 +0530 Subject: drivers: net: cpsw: remove platform data header file of cpsw CPSW driver no longer supports platform register as all the SoCs which has CPSW are supporting DT only booting, so moving cpsw.h header file from platform include to drivers/net/ethernet/ti Signed-off-by: Mugunthan V N Signed-off-by: David S. Miller --- drivers/net/ethernet/ti/cpsw.c | 2 +- drivers/net/ethernet/ti/cpsw.h | 42 ++++++++++++++++++++++++++++++++++++ include/linux/platform_data/cpsw.h | 44 -------------------------------------- 3 files changed, 43 insertions(+), 45 deletions(-) create mode 100644 drivers/net/ethernet/ti/cpsw.h delete mode 100644 include/linux/platform_data/cpsw.h (limited to 'include/linux/platform_data') diff --git a/drivers/net/ethernet/ti/cpsw.c b/drivers/net/ethernet/ti/cpsw.c index 0fcf21254ad3..b4a85f5531ed 100644 --- a/drivers/net/ethernet/ti/cpsw.c +++ b/drivers/net/ethernet/ti/cpsw.c @@ -34,9 +34,9 @@ #include #include -#include #include +#include "cpsw.h" #include "cpsw_ale.h" #include "cpts.h" #include "davinci_cpdma.h" diff --git a/drivers/net/ethernet/ti/cpsw.h b/drivers/net/ethernet/ti/cpsw.h new file mode 100644 index 000000000000..eb3e101ec048 --- /dev/null +++ b/drivers/net/ethernet/ti/cpsw.h @@ -0,0 +1,42 @@ +/* Texas Instruments Ethernet Switch Driver + * + * Copyright (C) 2013 Texas Instruments + * + * 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __CPSW_H__ +#define __CPSW_H__ + +#include + +struct cpsw_slave_data { + char phy_id[MII_BUS_ID_SIZE]; + int phy_if; + u8 mac_addr[ETH_ALEN]; + u16 dual_emac_res_vlan; /* Reserved VLAN for DualEMAC */ +}; + +struct cpsw_platform_data { + struct cpsw_slave_data *slave_data; + u32 ss_reg_ofs; /* Subsystem control register offset */ + u32 channels; /* number of cpdma channels (symmetric) */ + u32 slaves; /* number of slave cpgmac ports */ + u32 active_slave; /* time stamping, ethtool and SIOCGMIIPHY slave */ + u32 cpts_clock_mult; /* convert input clock ticks to nanoseconds */ + u32 cpts_clock_shift; /* convert input clock ticks to nanoseconds */ + u32 ale_entries; /* ale table size */ + u32 bd_ram_size; /*buffer descriptor ram size */ + u32 rx_descs; /* Number of Rx Descriptios */ + u32 mac_control; /* Mac control register */ + u16 default_vlan; /* Def VLAN for ALE lookup in VLAN aware mode*/ + bool dual_emac; /* Enable Dual EMAC mode */ +}; + +#endif /* __CPSW_H__ */ diff --git a/include/linux/platform_data/cpsw.h b/include/linux/platform_data/cpsw.h deleted file mode 100644 index bb3cd58d71e3..000000000000 --- a/include/linux/platform_data/cpsw.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Texas Instruments Ethernet Switch Driver - * - * Copyright (C) 2012 Texas Instruments - * - * 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 version 2. - * - * This program is distributed "as is" WITHOUT ANY WARRANTY of any - * kind, whether express or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ -#ifndef __CPSW_H__ -#define __CPSW_H__ - -#include - -struct cpsw_slave_data { - char phy_id[MII_BUS_ID_SIZE]; - int phy_if; - u8 mac_addr[ETH_ALEN]; - u16 dual_emac_res_vlan; /* Reserved VLAN for DualEMAC */ - -}; - -struct cpsw_platform_data { - u32 ss_reg_ofs; /* Subsystem control register offset */ - u32 channels; /* number of cpdma channels (symmetric) */ - u32 slaves; /* number of slave cpgmac ports */ - struct cpsw_slave_data *slave_data; - u32 active_slave; /* time stamping, ethtool and SIOCGMIIPHY slave */ - u32 cpts_clock_mult; /* convert input clock ticks to nanoseconds */ - u32 cpts_clock_shift; /* convert input clock ticks to nanoseconds */ - u32 ale_entries; /* ale table size */ - u32 bd_ram_size; /*buffer descriptor ram size */ - u32 rx_descs; /* Number of Rx Descriptios */ - u32 mac_control; /* Mac control register */ - u16 default_vlan; /* Def VLAN for ALE lookup in VLAN aware mode*/ - bool dual_emac; /* Enable Dual EMAC mode */ -}; - -#endif /* __CPSW_H__ */ -- cgit v1.2.3-71-gd317 From 33b3a561f417ec3e1013999ce8bdb6c055abb1ce Mon Sep 17 00:00:00 2001 From: "Kim, Milo" Date: Tue, 9 Jul 2013 02:11:37 -0700 Subject: leds: support new LP8501 device - another LP55xx common LP8501 can drive up to 9 channels like LP5523. LEDs can be controlled directly via the I2C and programmable engines are supported. LP55xx common driver LP8501 is one of LP55xx family device, so LP55xx common code are used. Chip specific data is defined in the structure, 'lp55xx_device_config'. Differences between LP8501 and LP5523 Different register layout for LED output control and others. LP8501 specific feature for separate output power selection. LP8501 doesn't support external clock detection. Different programming engine data. LP8501 specific feature - output power selection Output channels are selected by power selection - Vout or Vdd. Separate power for VDD1-6 and VDD7-9 are available. It is configurable in the platform data. To support this feature, LP55xx DT structure and header are changed. Device tree binding is updated as well. LED pattern data Example pattern data is updated in the driver documentation. Signed-off-by: Milo Kim Signed-off-by: Bryan Wu --- .../devicetree/bindings/leds/leds-lp55xx.txt | 72 +++- Documentation/leds/leds-lp55xx.txt | 30 +- drivers/leds/Kconfig | 18 +- drivers/leds/Makefile | 1 + drivers/leds/leds-lp55xx-common.c | 3 + drivers/leds/leds-lp8501.c | 410 +++++++++++++++++++++ include/linux/platform_data/leds-lp55xx.h | 10 + 7 files changed, 537 insertions(+), 7 deletions(-) create mode 100644 drivers/leds/leds-lp8501.c (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/leds/leds-lp55xx.txt b/Documentation/devicetree/bindings/leds/leds-lp55xx.txt index d5176882d8b9..a61727f9a6d1 100644 --- a/Documentation/devicetree/bindings/leds/leds-lp55xx.txt +++ b/Documentation/devicetree/bindings/leds/leds-lp55xx.txt @@ -1,7 +1,7 @@ Binding for TI/National Semiconductor LP55xx Led Drivers Required properties: -- compatible: "national,lp5521" or "national,lp5523" or "ti,lp5562" +- compatible: "national,lp5521" or "national,lp5523" or "ti,lp5562" or "ti,lp8501" - reg: I2C slave address - clock-mode: Input clock mode, (0: automode, 1: internal, 2: external) @@ -11,6 +11,11 @@ Each child has own specific current settings Optional properties: - label: Used for naming LEDs +- pwr-sel: LP8501 specific property. Power selection for output channels. + 0: D1~9 are connected to VDD + 1: D1~6 with VDD, D7~9 with VOUT + 2: D1~6 with VOUT, D7~9 with VDD + 3: D1~9 are connected to VOUT Alternatively, each child can have specific channel name - chan-name: Name of each channel name @@ -145,3 +150,68 @@ lp5562@30 { max-cur = /bits/ 8 <0x60>; }; }; + +example 4) LP8501 +9 channels are defined. The 'pwr-sel' is LP8501 specific property. +Others are same as LP5523. + +lp8501@32 { + compatible = "ti,lp8501"; + reg = <0x32>; + clock-mode = /bits/ 8 <2>; + pwr-sel = /bits/ 8 <3>; /* D1~9 connected to VOUT */ + + chan0 { + chan-name = "d1"; + led-cur = /bits/ 8 <0x14>; + max-cur = /bits/ 8 <0x20>; + }; + + chan1 { + chan-name = "d2"; + led-cur = /bits/ 8 <0x14>; + max-cur = /bits/ 8 <0x20>; + }; + + chan2 { + chan-name = "d3"; + led-cur = /bits/ 8 <0x14>; + max-cur = /bits/ 8 <0x20>; + }; + + chan3 { + chan-name = "d4"; + led-cur = /bits/ 8 <0x14>; + max-cur = /bits/ 8 <0x20>; + }; + + chan4 { + chan-name = "d5"; + led-cur = /bits/ 8 <0x14>; + max-cur = /bits/ 8 <0x20>; + }; + + chan5 { + chan-name = "d6"; + led-cur = /bits/ 8 <0x14>; + max-cur = /bits/ 8 <0x20>; + }; + + chan6 { + chan-name = "d7"; + led-cur = /bits/ 8 <0x14>; + max-cur = /bits/ 8 <0x20>; + }; + + chan7 { + chan-name = "d8"; + led-cur = /bits/ 8 <0x14>; + max-cur = /bits/ 8 <0x20>; + }; + + chan8 { + chan-name = "d9"; + led-cur = /bits/ 8 <0x14>; + max-cur = /bits/ 8 <0x20>; + }; +}; diff --git a/Documentation/leds/leds-lp55xx.txt b/Documentation/leds/leds-lp55xx.txt index eec8fa2ffe4e..82713ff92eb3 100644 --- a/Documentation/leds/leds-lp55xx.txt +++ b/Documentation/leds/leds-lp55xx.txt @@ -1,11 +1,11 @@ -LP5521/LP5523/LP55231 Common Driver -=================================== +LP5521/LP5523/LP55231/LP5562/LP8501 Common Driver +================================================= Authors: Milo(Woogyom) Kim Description ----------- -LP5521, LP5523/55231 and LP5562 have common features as below. +LP5521, LP5523/55231, LP5562 and LP8501 have common features as below. Register access via the I2C Device initialization/deinitialization @@ -109,6 +109,30 @@ As soon as 'loading' is set to 0, registered callback is called. Inside the callback, the selected engine is loaded and memory is updated. To run programmed pattern, 'run_engine' attribute should be enabled. +The pattern sqeuence of LP8501 is same as LP5523. +However pattern data is specific. +Ex 1) Engine 1 is used +echo 1 > /sys/bus/i2c/devices/xxxx/select_engine +echo 1 > /sys/class/firmware/lp8501/loading +echo "9d0140ff7e0040007e00a001c000" > /sys/class/firmware/lp8501/data +echo 0 > /sys/class/firmware/lp8501/loading +echo 1 > /sys/bus/i2c/devices/xxxx/run_engine + +Ex 2) Engine 2 and 3 are used at the same time +echo 2 > /sys/bus/i2c/devices/xxxx/select_engine +sleep 1 +echo 1 > /sys/class/firmware/lp8501/loading +echo "9d0140ff7e0040007e00a001c000" > /sys/class/firmware/lp8501/data +echo 0 > /sys/class/firmware/lp8501/loading +sleep 1 +echo 3 > /sys/bus/i2c/devices/xxxx/select_engine +sleep 1 +echo 1 > /sys/class/firmware/lp8501/loading +echo "9d0340ff7e0040007e00a001c000" > /sys/class/firmware/lp8501/data +echo 0 > /sys/class/firmware/lp8501/loading +sleep 1 +echo 1 > /sys/class/leds/d1/device/run_engine + ( 'run_engine' and 'firmware_cb' ) The sequence of running the program data is common. But each device has own specific register addresses for commands. diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index e43402dd1dea..77329ce672cb 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -194,11 +194,11 @@ config LEDS_LP3944 module will be called leds-lp3944. config LEDS_LP55XX_COMMON - tristate "Common Driver for TI/National LP5521, LP5523/55231 and LP5562" - depends on LEDS_LP5521 || LEDS_LP5523 || LEDS_LP5562 + tristate "Common Driver for TI/National LP5521/5523/55231/5562/8501" + depends on LEDS_LP5521 || LEDS_LP5523 || LEDS_LP5562 || LEDS_LP8501 select FW_LOADER help - This option supports common operations for LP5521 and LP5523/55231 + This option supports common operations for LP5521/5523/55231/5562/8501 devices. config LEDS_LP5521 @@ -232,6 +232,18 @@ config LEDS_LP5562 Driver provides direct control via LED class and interface for programming the engines. +config LEDS_LP8501 + tristate "LED Support for TI LP8501 LED driver chip" + depends on LEDS_CLASS && I2C + select LEDS_LP55XX_COMMON + help + If you say yes here you get support for TI LP8501 LED driver. + It is 9 channel chip with programmable engines. + Driver provides direct control via LED class and interface for + programming the engines. + It is similar as LP5523, but output power selection is available. + And register layout and engine program schemes are different. + config LEDS_LP8788 tristate "LED support for the TI LP8788 PMIC" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index ac2897732b02..3013113e74d2 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_LEDS_LP55XX_COMMON) += leds-lp55xx-common.o obj-$(CONFIG_LEDS_LP5521) += leds-lp5521.o obj-$(CONFIG_LEDS_LP5523) += leds-lp5523.o obj-$(CONFIG_LEDS_LP5562) += leds-lp5562.o +obj-$(CONFIG_LEDS_LP8501) += leds-lp8501.o obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o diff --git a/drivers/leds/leds-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c index c2fecd4d391c..351825b96f16 100644 --- a/drivers/leds/leds-lp55xx-common.c +++ b/drivers/leds/leds-lp55xx-common.c @@ -593,6 +593,9 @@ int lp55xx_of_populate_pdata(struct device *dev, struct device_node *np) of_property_read_string(np, "label", &pdata->label); of_property_read_u8(np, "clock-mode", &pdata->clock_mode); + /* LP8501 specific */ + of_property_read_u8(np, "pwr-sel", (u8 *)&pdata->pwr_sel); + dev->platform_data = pdata; return 0; diff --git a/drivers/leds/leds-lp8501.c b/drivers/leds/leds-lp8501.c new file mode 100644 index 000000000000..4573b9471aaa --- /dev/null +++ b/drivers/leds/leds-lp8501.c @@ -0,0 +1,410 @@ +/* + * TI LP8501 9 channel LED Driver + * + * Copyright (C) 2013 Texas Instruments + * + * Author: Milo(Woogyom) Kim + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "leds-lp55xx-common.h" + +#define LP8501_PROGRAM_LENGTH 32 +#define LP8501_MAX_LEDS 9 + +/* Registers */ +#define LP8501_REG_ENABLE 0x00 +#define LP8501_ENABLE BIT(6) +#define LP8501_EXEC_M 0x3F +#define LP8501_EXEC_ENG1_M 0x30 +#define LP8501_EXEC_ENG2_M 0x0C +#define LP8501_EXEC_ENG3_M 0x03 +#define LP8501_RUN_ENG1 0x20 +#define LP8501_RUN_ENG2 0x08 +#define LP8501_RUN_ENG3 0x02 + +#define LP8501_REG_OP_MODE 0x01 +#define LP8501_MODE_ENG1_M 0x30 +#define LP8501_MODE_ENG2_M 0x0C +#define LP8501_MODE_ENG3_M 0x03 +#define LP8501_LOAD_ENG1 0x10 +#define LP8501_LOAD_ENG2 0x04 +#define LP8501_LOAD_ENG3 0x01 + +#define LP8501_REG_PWR_CONFIG 0x05 +#define LP8501_PWR_CONFIG_M 0x03 + +#define LP8501_REG_LED_PWM_BASE 0x16 + +#define LP8501_REG_LED_CURRENT_BASE 0x26 + +#define LP8501_REG_CONFIG 0x36 +#define LP8501_PWM_PSAVE BIT(7) +#define LP8501_AUTO_INC BIT(6) +#define LP8501_PWR_SAVE BIT(5) +#define LP8501_CP_AUTO 0x18 +#define LP8501_INT_CLK BIT(0) +#define LP8501_DEFAULT_CFG \ + (LP8501_PWM_PSAVE | LP8501_AUTO_INC | LP8501_PWR_SAVE | LP8501_CP_AUTO) + +#define LP8501_REG_RESET 0x3D +#define LP8501_RESET 0xFF + +#define LP8501_REG_PROG_PAGE_SEL 0x4F +#define LP8501_PAGE_ENG1 0 +#define LP8501_PAGE_ENG2 1 +#define LP8501_PAGE_ENG3 2 + +#define LP8501_REG_PROG_MEM 0x50 + +#define LP8501_ENG1_IS_LOADING(mode) \ + ((mode & LP8501_MODE_ENG1_M) == LP8501_LOAD_ENG1) +#define LP8501_ENG2_IS_LOADING(mode) \ + ((mode & LP8501_MODE_ENG2_M) == LP8501_LOAD_ENG2) +#define LP8501_ENG3_IS_LOADING(mode) \ + ((mode & LP8501_MODE_ENG3_M) == LP8501_LOAD_ENG3) + +static inline void lp8501_wait_opmode_done(void) +{ + usleep_range(1000, 2000); +} + +static void lp8501_set_led_current(struct lp55xx_led *led, u8 led_current) +{ + led->led_current = led_current; + lp55xx_write(led->chip, LP8501_REG_LED_CURRENT_BASE + led->chan_nr, + led_current); +} + +static int lp8501_post_init_device(struct lp55xx_chip *chip) +{ + int ret; + u8 val = LP8501_DEFAULT_CFG; + + ret = lp55xx_write(chip, LP8501_REG_ENABLE, LP8501_ENABLE); + if (ret) + return ret; + + /* Chip startup time is 500 us, 1 - 2 ms gives some margin */ + usleep_range(1000, 2000); + + if (chip->pdata->clock_mode != LP55XX_CLOCK_EXT) + val |= LP8501_INT_CLK; + + ret = lp55xx_write(chip, LP8501_REG_CONFIG, val); + if (ret) + return ret; + + /* Power selection for each output */ + return lp55xx_update_bits(chip, LP8501_REG_PWR_CONFIG, + LP8501_PWR_CONFIG_M, chip->pdata->pwr_sel); +} + +static void lp8501_load_engine(struct lp55xx_chip *chip) +{ + enum lp55xx_engine_index idx = chip->engine_idx; + u8 mask[] = { + [LP55XX_ENGINE_1] = LP8501_MODE_ENG1_M, + [LP55XX_ENGINE_2] = LP8501_MODE_ENG2_M, + [LP55XX_ENGINE_3] = LP8501_MODE_ENG3_M, + }; + + u8 val[] = { + [LP55XX_ENGINE_1] = LP8501_LOAD_ENG1, + [LP55XX_ENGINE_2] = LP8501_LOAD_ENG2, + [LP55XX_ENGINE_3] = LP8501_LOAD_ENG3, + }; + + u8 page_sel[] = { + [LP55XX_ENGINE_1] = LP8501_PAGE_ENG1, + [LP55XX_ENGINE_2] = LP8501_PAGE_ENG2, + [LP55XX_ENGINE_3] = LP8501_PAGE_ENG3, + }; + + lp55xx_update_bits(chip, LP8501_REG_OP_MODE, mask[idx], val[idx]); + + lp8501_wait_opmode_done(); + + lp55xx_write(chip, LP8501_REG_PROG_PAGE_SEL, page_sel[idx]); +} + +static void lp8501_stop_engine(struct lp55xx_chip *chip) +{ + lp55xx_write(chip, LP8501_REG_OP_MODE, 0); + lp8501_wait_opmode_done(); +} + +static void lp8501_turn_off_channels(struct lp55xx_chip *chip) +{ + int i; + + for (i = 0; i < LP8501_MAX_LEDS; i++) + lp55xx_write(chip, LP8501_REG_LED_PWM_BASE + i, 0); +} + +static void lp8501_run_engine(struct lp55xx_chip *chip, bool start) +{ + int ret; + u8 mode; + u8 exec; + + /* stop engine */ + if (!start) { + lp8501_stop_engine(chip); + lp8501_turn_off_channels(chip); + return; + } + + /* + * To run the engine, + * operation mode and enable register should updated at the same time + */ + + ret = lp55xx_read(chip, LP8501_REG_OP_MODE, &mode); + if (ret) + return; + + ret = lp55xx_read(chip, LP8501_REG_ENABLE, &exec); + if (ret) + return; + + /* change operation mode to RUN only when each engine is loading */ + if (LP8501_ENG1_IS_LOADING(mode)) { + mode = (mode & ~LP8501_MODE_ENG1_M) | LP8501_RUN_ENG1; + exec = (exec & ~LP8501_EXEC_ENG1_M) | LP8501_RUN_ENG1; + } + + if (LP8501_ENG2_IS_LOADING(mode)) { + mode = (mode & ~LP8501_MODE_ENG2_M) | LP8501_RUN_ENG2; + exec = (exec & ~LP8501_EXEC_ENG2_M) | LP8501_RUN_ENG2; + } + + if (LP8501_ENG3_IS_LOADING(mode)) { + mode = (mode & ~LP8501_MODE_ENG3_M) | LP8501_RUN_ENG3; + exec = (exec & ~LP8501_EXEC_ENG3_M) | LP8501_RUN_ENG3; + } + + lp55xx_write(chip, LP8501_REG_OP_MODE, mode); + lp8501_wait_opmode_done(); + + lp55xx_update_bits(chip, LP8501_REG_ENABLE, LP8501_EXEC_M, exec); +} + +static int lp8501_update_program_memory(struct lp55xx_chip *chip, + const u8 *data, size_t size) +{ + u8 pattern[LP8501_PROGRAM_LENGTH] = {0}; + unsigned cmd; + char c[3]; + int update_size; + int nrchars; + int offset = 0; + int ret; + int i; + + /* clear program memory before updating */ + for (i = 0; i < LP8501_PROGRAM_LENGTH; i++) + lp55xx_write(chip, LP8501_REG_PROG_MEM + i, 0); + + i = 0; + while ((offset < size - 1) && (i < LP8501_PROGRAM_LENGTH)) { + /* separate sscanfs because length is working only for %s */ + ret = sscanf(data + offset, "%2s%n ", c, &nrchars); + if (ret != 1) + goto err; + + ret = sscanf(c, "%2x", &cmd); + if (ret != 1) + goto err; + + pattern[i] = (u8)cmd; + offset += nrchars; + i++; + } + + /* Each instruction is 16bit long. Check that length is even */ + if (i % 2) + goto err; + + update_size = i; + for (i = 0; i < update_size; i++) + lp55xx_write(chip, LP8501_REG_PROG_MEM + i, pattern[i]); + + return 0; + +err: + dev_err(&chip->cl->dev, "wrong pattern format\n"); + return -EINVAL; +} + +static void lp8501_firmware_loaded(struct lp55xx_chip *chip) +{ + const struct firmware *fw = chip->fw; + + if (fw->size > LP8501_PROGRAM_LENGTH) { + dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n", + fw->size); + return; + } + + /* + * Program momery sequence + * 1) set engine mode to "LOAD" + * 2) write firmware data into program memory + */ + + lp8501_load_engine(chip); + lp8501_update_program_memory(chip, fw->data, fw->size); +} + +static void lp8501_led_brightness_work(struct work_struct *work) +{ + struct lp55xx_led *led = container_of(work, struct lp55xx_led, + brightness_work); + struct lp55xx_chip *chip = led->chip; + + mutex_lock(&chip->lock); + lp55xx_write(chip, LP8501_REG_LED_PWM_BASE + led->chan_nr, + led->brightness); + mutex_unlock(&chip->lock); +} + +/* Chip specific configurations */ +static struct lp55xx_device_config lp8501_cfg = { + .reset = { + .addr = LP8501_REG_RESET, + .val = LP8501_RESET, + }, + .enable = { + .addr = LP8501_REG_ENABLE, + .val = LP8501_ENABLE, + }, + .max_channel = LP8501_MAX_LEDS, + .post_init_device = lp8501_post_init_device, + .brightness_work_fn = lp8501_led_brightness_work, + .set_led_current = lp8501_set_led_current, + .firmware_cb = lp8501_firmware_loaded, + .run_engine = lp8501_run_engine, +}; + +static int lp8501_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct lp55xx_chip *chip; + struct lp55xx_led *led; + struct lp55xx_platform_data *pdata; + struct device_node *np = client->dev.of_node; + + if (!client->dev.platform_data) { + if (np) { + ret = lp55xx_of_populate_pdata(&client->dev, np); + if (ret < 0) + return ret; + } else { + dev_err(&client->dev, "no platform data\n"); + return -EINVAL; + } + } + pdata = client->dev.platform_data; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + led = devm_kzalloc(&client->dev, + sizeof(*led) * pdata->num_channels, GFP_KERNEL); + if (!led) + return -ENOMEM; + + chip->cl = client; + chip->pdata = pdata; + chip->cfg = &lp8501_cfg; + + mutex_init(&chip->lock); + + i2c_set_clientdata(client, led); + + ret = lp55xx_init_device(chip); + if (ret) + goto err_init; + + dev_info(&client->dev, "%s Programmable led chip found\n", id->name); + + ret = lp55xx_register_leds(led, chip); + if (ret) + goto err_register_leds; + + ret = lp55xx_register_sysfs(chip); + if (ret) { + dev_err(&client->dev, "registering sysfs failed\n"); + goto err_register_sysfs; + } + + return 0; + +err_register_sysfs: + lp55xx_unregister_leds(led, chip); +err_register_leds: + lp55xx_deinit_device(chip); +err_init: + return ret; +} + +static int lp8501_remove(struct i2c_client *client) +{ + struct lp55xx_led *led = i2c_get_clientdata(client); + struct lp55xx_chip *chip = led->chip; + + lp8501_stop_engine(chip); + lp55xx_unregister_sysfs(chip); + lp55xx_unregister_leds(led, chip); + lp55xx_deinit_device(chip); + + return 0; +} + +static const struct i2c_device_id lp8501_id[] = { + { "lp8501", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lp8501_id); + +#ifdef CONFIG_OF +static const struct of_device_id of_lp8501_leds_match[] = { + { .compatible = "ti,lp8501", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, of_lp8501_leds_match); +#endif + +static struct i2c_driver lp8501_driver = { + .driver = { + .name = "lp8501", + .of_match_table = of_match_ptr(of_lp8501_leds_match), + }, + .probe = lp8501_probe, + .remove = lp8501_remove, + .id_table = lp8501_id, +}; + +module_i2c_driver(lp8501_driver); + +MODULE_DESCRIPTION("Texas Instruments LP8501 LED drvier"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/platform_data/leds-lp55xx.h b/include/linux/platform_data/leds-lp55xx.h index 202e290faea8..51a2ff579d60 100644 --- a/include/linux/platform_data/leds-lp55xx.h +++ b/include/linux/platform_data/leds-lp55xx.h @@ -36,6 +36,13 @@ struct lp55xx_predef_pattern { u8 size_b; }; +enum lp8501_pwr_sel { + LP8501_ALL_VDD, /* D1~9 are connected to VDD */ + LP8501_6VDD_3VOUT, /* D1~6 with VDD, D7~9 with VOUT */ + LP8501_3VDD_6VOUT, /* D1~6 with VOUT, D7~9 with VDD */ + LP8501_ALL_VOUT, /* D1~9 are connected to VOUT */ +}; + /* * struct lp55xx_platform_data * @led_config : Configurable led class device @@ -67,6 +74,9 @@ struct lp55xx_platform_data { /* Predefined pattern data */ struct lp55xx_predef_pattern *patterns; unsigned int num_patterns; + + /* LP8501 specific */ + enum lp8501_pwr_sel pwr_sel; }; #endif /* _LEDS_LP55XX_H */ -- cgit v1.2.3-71-gd317 From 8465b01827b7a1e0e2464b0a300618bf7add25d8 Mon Sep 17 00:00:00 2001 From: "Mark A. Greer" Date: Thu, 25 Jul 2013 10:16:41 -0700 Subject: leds: pca9633: Add hardware blink support Add hardware blinking support to the pca9633 driver. NOTE: Hardware blinking violates the leds infrastructure driver interface since the hardware only supports blinking all LEDs with the same delay_on/delay_off rates. That is, only the LEDs that are set to blink will actually blink but all LEDs that are set to blink will blink in identical fashion. The delay_on/delay_off values of the last LED that is set to blink will be used for all of the blinking LEDs. If the hardware doesn't support the requested blinking pattern, a default of 500ms on and off will be used. Hardware blinking is disabled by default but can be enabled by setting the 'blink_type' member in the platform_data struct to 'PCA9633_HW_BLINK' or by adding the 'nxp,hw-blink' property to the DTS. (fengguang.wu@intel.com: Removes unneeded semicolon.) Signed-off-by: Mark A. Greer Reported-by: Fengguang Wu Signed-off-by: Bryan Wu --- Documentation/devicetree/bindings/leds/pca9633.txt | 1 + drivers/leds/leds-pca9633.c | 136 ++++++++++++++++++++- include/linux/platform_data/leds-pca9633.h | 6 + 3 files changed, 139 insertions(+), 4 deletions(-) (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/leds/pca9633.txt b/Documentation/devicetree/bindings/leds/pca9633.txt index 8140512c0ac8..6d9e1a9e7e9f 100644 --- a/Documentation/devicetree/bindings/leds/pca9633.txt +++ b/Documentation/devicetree/bindings/leds/pca9633.txt @@ -5,6 +5,7 @@ Required properties: Optional properties: - nxp,totem-pole : use totem pole (push-pull) instead of default open-drain +- nxp,hw-blink : use hardware blinking instead of software blinking Each led is represented as a sub-node of the nxp,pca9633 device. diff --git a/drivers/leds/leds-pca9633.c b/drivers/leds/leds-pca9633.c index 90935e465c4d..77b3f24b7207 100644 --- a/drivers/leds/leds-pca9633.c +++ b/drivers/leds/leds-pca9633.c @@ -11,6 +11,15 @@ * * LED driver for the PCA9633 I2C LED driver (7-bit slave address 0x62) * + * Note that hardware blinking violates the leds infrastructure driver + * interface since the hardware only supports blinking all LEDs with the + * same delay_on/delay_off rates. That is, only the LEDs that are set to + * blink will actually blink but all LEDs that are set to blink will blink + * in identical fashion. The delay_on/delay_off values of the last LED + * that is set to blink will be used for all of the blinking LEDs. + * Hardware blinking is disabled by default but can be enabled by setting + * the 'blink_type' member in the platform_data struct to 'PCA9633_HW_BLINK' + * or by adding the 'nxp,hw-blink' property to the DTS. */ #include @@ -31,30 +40,44 @@ #define PCA9633_LED_PWM 0x2 /* Controlled through PWM */ #define PCA9633_LED_GRP_PWM 0x3 /* Controlled through PWM/GRPPWM */ +#define PCA9633_MODE2_DMBLNK 0x20 /* Enable blinking */ + #define PCA9633_MODE1 0x00 #define PCA9633_MODE2 0x01 #define PCA9633_PWM_BASE 0x02 +#define PCA9633_GRPPWM 0x06 +#define PCA9633_GRPFREQ 0x07 #define PCA9633_LEDOUT 0x08 +/* Total blink period in milliseconds */ +#define PCA9632_BLINK_PERIOD_MIN 42 +#define PCA9632_BLINK_PERIOD_MAX 10667 + static const struct i2c_device_id pca9633_id[] = { { "pca9633", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, pca9633_id); +enum pca9633_cmd { + BRIGHTNESS_SET, + BLINK_SET, +}; + struct pca9633_led { struct i2c_client *client; struct work_struct work; enum led_brightness brightness; struct led_classdev led_cdev; int led_num; /* 0 .. 3 potentially */ + enum pca9633_cmd cmd; char name[32]; + u8 gdc; + u8 gfrq; }; -static void pca9633_led_work(struct work_struct *work) +static void pca9633_brightness_work(struct pca9633_led *pca9633) { - struct pca9633_led *pca9633 = container_of(work, - struct pca9633_led, work); u8 ledout = i2c_smbus_read_byte_data(pca9633->client, PCA9633_LEDOUT); int shift = 2 * pca9633->led_num; u8 mask = 0x3 << shift; @@ -78,6 +101,43 @@ static void pca9633_led_work(struct work_struct *work) } } +static void pca9633_blink_work(struct pca9633_led *pca9633) +{ + u8 ledout = i2c_smbus_read_byte_data(pca9633->client, PCA9633_LEDOUT); + u8 mode2 = i2c_smbus_read_byte_data(pca9633->client, PCA9633_MODE2); + int shift = 2 * pca9633->led_num; + u8 mask = 0x3 << shift; + + i2c_smbus_write_byte_data(pca9633->client, PCA9633_GRPPWM, + pca9633->gdc); + + i2c_smbus_write_byte_data(pca9633->client, PCA9633_GRPFREQ, + pca9633->gfrq); + + if (!(mode2 & PCA9633_MODE2_DMBLNK)) + i2c_smbus_write_byte_data(pca9633->client, PCA9633_MODE2, + mode2 | PCA9633_MODE2_DMBLNK); + + if ((ledout & mask) != (PCA9633_LED_GRP_PWM << shift)) + i2c_smbus_write_byte_data(pca9633->client, PCA9633_LEDOUT, + (ledout & ~mask) | (PCA9633_LED_GRP_PWM << shift)); +} + +static void pca9633_work(struct work_struct *work) +{ + struct pca9633_led *pca9633 = container_of(work, + struct pca9633_led, work); + + switch (pca9633->cmd) { + case BRIGHTNESS_SET: + pca9633_brightness_work(pca9633); + break; + case BLINK_SET: + pca9633_blink_work(pca9633); + break; + } +} + static void pca9633_led_set(struct led_classdev *led_cdev, enum led_brightness value) { @@ -85,6 +145,7 @@ static void pca9633_led_set(struct led_classdev *led_cdev, pca9633 = container_of(led_cdev, struct pca9633_led, led_cdev); + pca9633->cmd = BRIGHTNESS_SET; pca9633->brightness = value; /* @@ -94,6 +155,64 @@ static void pca9633_led_set(struct led_classdev *led_cdev, schedule_work(&pca9633->work); } +static int pca9633_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, unsigned long *delay_off) +{ + struct pca9633_led *pca9633; + unsigned long time_on, time_off, period; + u8 gdc, gfrq; + + pca9633 = container_of(led_cdev, struct pca9633_led, led_cdev); + + time_on = *delay_on; + time_off = *delay_off; + + /* If both zero, pick reasonable defaults of 500ms each */ + if (!time_on && !time_off) { + time_on = 500; + time_off = 500; + } + + period = time_on + time_off; + + /* If period not supported by hardware, default to someting sane. */ + if ((period < PCA9632_BLINK_PERIOD_MIN) || + (period > PCA9632_BLINK_PERIOD_MAX)) { + time_on = 500; + time_off = 500; + period = time_on + time_off; + } + + /* + * From manual: duty cycle = (GDC / 256) -> + * (time_on / period) = (GDC / 256) -> + * GDC = ((time_on * 256) / period) + */ + gdc = (time_on * 256) / period; + + /* + * From manual: period = ((GFRQ + 1) / 24) in seconds. + * So, period (in ms) = (((GFRQ + 1) / 24) * 1000) -> + * GFRQ = ((period * 24 / 1000) - 1) + */ + gfrq = (period * 24 / 1000) - 1; + + pca9633->cmd = BLINK_SET; + pca9633->gdc = gdc; + pca9633->gfrq = gfrq; + + /* + * Must use workqueue for the actual I/O since I2C operations + * can sleep. + */ + schedule_work(&pca9633->work); + + *delay_on = time_on; + *delay_off = time_off; + + return 0; +} + #if IS_ENABLED(CONFIG_OF) static struct pca9633_platform_data * pca9633_dt_init(struct i2c_client *client) @@ -140,6 +259,12 @@ pca9633_dt_init(struct i2c_client *client) else pdata->outdrv = PCA9633_OPEN_DRAIN; + /* default to software blinking unless hardware blinking is specified */ + if (of_property_read_bool(np, "nxp,hw-blink")) + pdata->blink_type = PCA9633_HW_BLINK; + else + pdata->blink_type = PCA9633_SW_BLINK; + return pdata; } @@ -206,7 +331,10 @@ static int pca9633_probe(struct i2c_client *client, pca9633[i].led_cdev.name = pca9633[i].name; pca9633[i].led_cdev.brightness_set = pca9633_led_set; - INIT_WORK(&pca9633[i].work, pca9633_led_work); + if (pdata && pdata->blink_type == PCA9633_HW_BLINK) + pca9633[i].led_cdev.blink_set = pca9633_blink_set; + + INIT_WORK(&pca9633[i].work, pca9633_work); err = led_classdev_register(&client->dev, &pca9633[i].led_cdev); if (err < 0) diff --git a/include/linux/platform_data/leds-pca9633.h b/include/linux/platform_data/leds-pca9633.h index c5bf29b6fa7f..3c1037a81d34 100644 --- a/include/linux/platform_data/leds-pca9633.h +++ b/include/linux/platform_data/leds-pca9633.h @@ -27,9 +27,15 @@ enum pca9633_outdrv { PCA9633_TOTEM_POLE, /* aka push-pull */ }; +enum pca9633_blink_type { + PCA9633_SW_BLINK, + PCA9633_HW_BLINK, +}; + struct pca9633_platform_data { struct led_platform_data leds; enum pca9633_outdrv outdrv; + enum pca9633_blink_type blink_type; }; #endif /* __LINUX_PCA9633_H*/ -- cgit v1.2.3-71-gd317 From 56a1740c21e4396164265c3ec80e29990ddcdc36 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Wed, 14 Aug 2013 14:23:50 -0700 Subject: leds-pca9633: Rename to leds-pca963x The driver now supports the chips pca9633 and pca9634, therefore we rename the files to more generic and meaningul names Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Bryan Wu --- Documentation/devicetree/bindings/leds/pca9633.txt | 47 --- Documentation/devicetree/bindings/leds/pca963x.txt | 47 +++ drivers/leds/Kconfig | 2 +- drivers/leds/Makefile | 2 +- drivers/leds/leds-pca9633.c | 461 --------------------- drivers/leds/leds-pca963x.c | 461 +++++++++++++++++++++ include/linux/platform_data/leds-pca9633.h | 41 -- include/linux/platform_data/leds-pca963x.h | 42 ++ 8 files changed, 552 insertions(+), 551 deletions(-) delete mode 100644 Documentation/devicetree/bindings/leds/pca9633.txt create mode 100644 Documentation/devicetree/bindings/leds/pca963x.txt delete mode 100644 drivers/leds/leds-pca9633.c create mode 100644 drivers/leds/leds-pca963x.c delete mode 100644 include/linux/platform_data/leds-pca9633.h create mode 100644 include/linux/platform_data/leds-pca963x.h (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/leds/pca9633.txt b/Documentation/devicetree/bindings/leds/pca9633.txt deleted file mode 100644 index aece3eac1b63..000000000000 --- a/Documentation/devicetree/bindings/leds/pca9633.txt +++ /dev/null @@ -1,47 +0,0 @@ -LEDs connected to pca9632, pca9633 or pca9634 - -Required properties: -- compatible : should be : "nxp,pca9632", "nxp,pca9633" or "nxp,pca9634" - -Optional properties: -- nxp,totem-pole : use totem pole (push-pull) instead of default open-drain -- nxp,hw-blink : use hardware blinking instead of software blinking - -Each led is represented as a sub-node of the nxp,pca963x device. - -LED sub-node properties: -- label : (optional) see Documentation/devicetree/bindings/leds/common.txt -- reg : number of LED line (could be from 0 to 3 in pca9632 or pca9633 - or 0 to 7 in pca9634) -- linux,default-trigger : (optional) - see Documentation/devicetree/bindings/leds/common.txt - -Examples: - -pca9632: pca9632 { - compatible = "nxp,pca9632"; - #address-cells = <1>; - #size-cells = <0>; - reg = <0x62>; - - red@0 { - label = "red"; - reg = <0>; - linux,default-trigger = "none"; - }; - green@1 { - label = "green"; - reg = <1>; - linux,default-trigger = "none"; - }; - blue@2 { - label = "blue"; - reg = <2>; - linux,default-trigger = "none"; - }; - unused@3 { - label = "unused"; - reg = <3>; - linux,default-trigger = "none"; - }; -}; diff --git a/Documentation/devicetree/bindings/leds/pca963x.txt b/Documentation/devicetree/bindings/leds/pca963x.txt new file mode 100644 index 000000000000..aece3eac1b63 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/pca963x.txt @@ -0,0 +1,47 @@ +LEDs connected to pca9632, pca9633 or pca9634 + +Required properties: +- compatible : should be : "nxp,pca9632", "nxp,pca9633" or "nxp,pca9634" + +Optional properties: +- nxp,totem-pole : use totem pole (push-pull) instead of default open-drain +- nxp,hw-blink : use hardware blinking instead of software blinking + +Each led is represented as a sub-node of the nxp,pca963x device. + +LED sub-node properties: +- label : (optional) see Documentation/devicetree/bindings/leds/common.txt +- reg : number of LED line (could be from 0 to 3 in pca9632 or pca9633 + or 0 to 7 in pca9634) +- linux,default-trigger : (optional) + see Documentation/devicetree/bindings/leds/common.txt + +Examples: + +pca9632: pca9632 { + compatible = "nxp,pca9632"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x62>; + + red@0 { + label = "red"; + reg = <0>; + linux,default-trigger = "none"; + }; + green@1 { + label = "green"; + reg = <1>; + linux,default-trigger = "none"; + }; + blue@2 { + label = "blue"; + reg = <2>; + linux,default-trigger = "none"; + }; + unused@3 { + label = "unused"; + reg = <3>; + linux,default-trigger = "none"; + }; +}; diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index e7977aa3ee59..a1a52bec03d4 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -291,7 +291,7 @@ config LEDS_PCA955X LED driver chips accessed via the I2C bus. Supported devices include PCA9550, PCA9551, PCA9552, and PCA9553. -config LEDS_PCA9633 +config LEDS_PCA963X tristate "LED support for PCA963x I2C chip" depends on LEDS_CLASS depends on I2C diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 3013113e74d2..c7e35423fed9 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -35,7 +35,7 @@ obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o obj-$(CONFIG_LEDS_OT200) += leds-ot200.o obj-$(CONFIG_LEDS_FSG) += leds-fsg.o obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o -obj-$(CONFIG_LEDS_PCA9633) += leds-pca9633.o +obj-$(CONFIG_LEDS_PCA963X) += leds-pca963x.o obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o obj-$(CONFIG_LEDS_DA9052) += leds-da9052.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o diff --git a/drivers/leds/leds-pca9633.c b/drivers/leds/leds-pca9633.c deleted file mode 100644 index aaa1c4a0dbc5..000000000000 --- a/drivers/leds/leds-pca9633.c +++ /dev/null @@ -1,461 +0,0 @@ -/* - * Copyright 2011 bct electronic GmbH - * Copyright 2013 Qtechnology/AS - * - * Author: Peter Meerwald - * Author: Ricardo Ribalda - * - * Based on leds-pca955x.c - * - * This file is subject to the terms and conditions of version 2 of - * the GNU General Public License. See the file COPYING in the main - * directory of this archive for more details. - * - * LED driver for the PCA9633 I2C LED driver (7-bit slave address 0x62) - * LED driver for the PCA9634 I2C LED driver (7-bit slave address set by hw.) - * - * Note that hardware blinking violates the leds infrastructure driver - * interface since the hardware only supports blinking all LEDs with the - * same delay_on/delay_off rates. That is, only the LEDs that are set to - * blink will actually blink but all LEDs that are set to blink will blink - * in identical fashion. The delay_on/delay_off values of the last LED - * that is set to blink will be used for all of the blinking LEDs. - * Hardware blinking is disabled by default but can be enabled by setting - * the 'blink_type' member in the platform_data struct to 'PCA9633_HW_BLINK' - * or by adding the 'nxp,hw-blink' property to the DTS. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* LED select registers determine the source that drives LED outputs */ -#define PCA9633_LED_OFF 0x0 /* LED driver off */ -#define PCA9633_LED_ON 0x1 /* LED driver on */ -#define PCA9633_LED_PWM 0x2 /* Controlled through PWM */ -#define PCA9633_LED_GRP_PWM 0x3 /* Controlled through PWM/GRPPWM */ - -#define PCA9633_MODE2_DMBLNK 0x20 /* Enable blinking */ - -#define PCA9633_MODE1 0x00 -#define PCA9633_MODE2 0x01 -#define PCA9633_PWM_BASE 0x02 - -enum pca9633_type { - pca9633, - pca9634, -}; - -struct pca9633_chipdef { - u8 grppwm; - u8 grpfreq; - u8 ledout_base; - int n_leds; -}; - -static struct pca9633_chipdef pca9633_chipdefs[] = { - [pca9633] = { - .grppwm = 0x6, - .grpfreq = 0x7, - .ledout_base = 0x8, - .n_leds = 4, - }, - [pca9634] = { - .grppwm = 0xa, - .grpfreq = 0xb, - .ledout_base = 0xc, - .n_leds = 8, - }, -}; - -/* Total blink period in milliseconds */ -#define PCA9632_BLINK_PERIOD_MIN 42 -#define PCA9632_BLINK_PERIOD_MAX 10667 - -static const struct i2c_device_id pca9633_id[] = { - { "pca9632", pca9633 }, - { "pca9633", pca9633 }, - { "pca9634", pca9634 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, pca9633_id); - -enum pca9633_cmd { - BRIGHTNESS_SET, - BLINK_SET, -}; - -struct pca9633_led; - -struct pca9633 { - struct pca9633_chipdef *chipdef; - struct mutex mutex; - struct i2c_client *client; - struct pca9633_led *leds; -}; - -struct pca9633_led { - struct pca9633 *chip; - struct work_struct work; - enum led_brightness brightness; - struct led_classdev led_cdev; - int led_num; /* 0 .. 7 potentially */ - enum pca9633_cmd cmd; - char name[32]; - u8 gdc; - u8 gfrq; -}; - -static void pca9633_brightness_work(struct pca9633_led *pca9633) -{ - u8 ledout_addr = pca9633->chip->chipdef->ledout_base - + (pca9633->led_num / 4); - u8 ledout; - int shift = 2 * (pca9633->led_num % 4); - u8 mask = 0x3 << shift; - - mutex_lock(&pca9633->chip->mutex); - ledout = i2c_smbus_read_byte_data(pca9633->chip->client, ledout_addr); - switch (pca9633->brightness) { - case LED_FULL: - i2c_smbus_write_byte_data(pca9633->chip->client, ledout_addr, - (ledout & ~mask) | (PCA9633_LED_ON << shift)); - break; - case LED_OFF: - i2c_smbus_write_byte_data(pca9633->chip->client, ledout_addr, - ledout & ~mask); - break; - default: - i2c_smbus_write_byte_data(pca9633->chip->client, - PCA9633_PWM_BASE + pca9633->led_num, - pca9633->brightness); - i2c_smbus_write_byte_data(pca9633->chip->client, ledout_addr, - (ledout & ~mask) | (PCA9633_LED_PWM << shift)); - break; - } - mutex_unlock(&pca9633->chip->mutex); -} - -static void pca9633_blink_work(struct pca9633_led *pca9633) -{ - u8 ledout_addr = pca9633->chip->chipdef->ledout_base + - (pca9633->led_num / 4); - u8 ledout; - u8 mode2 = i2c_smbus_read_byte_data(pca9633->chip->client, - PCA9633_MODE2); - int shift = 2 * (pca9633->led_num % 4); - u8 mask = 0x3 << shift; - - i2c_smbus_write_byte_data(pca9633->chip->client, - pca9633->chip->chipdef->grppwm, pca9633->gdc); - - i2c_smbus_write_byte_data(pca9633->chip->client, - pca9633->chip->chipdef->grpfreq, pca9633->gfrq); - - if (!(mode2 & PCA9633_MODE2_DMBLNK)) - i2c_smbus_write_byte_data(pca9633->chip->client, PCA9633_MODE2, - mode2 | PCA9633_MODE2_DMBLNK); - - mutex_lock(&pca9633->chip->mutex); - ledout = i2c_smbus_read_byte_data(pca9633->chip->client, ledout_addr); - if ((ledout & mask) != (PCA9633_LED_GRP_PWM << shift)) - i2c_smbus_write_byte_data(pca9633->chip->client, ledout_addr, - (ledout & ~mask) | (PCA9633_LED_GRP_PWM << shift)); - mutex_unlock(&pca9633->chip->mutex); -} - -static void pca9633_work(struct work_struct *work) -{ - struct pca9633_led *pca9633 = container_of(work, - struct pca9633_led, work); - - switch (pca9633->cmd) { - case BRIGHTNESS_SET: - pca9633_brightness_work(pca9633); - break; - case BLINK_SET: - pca9633_blink_work(pca9633); - break; - } -} - -static void pca9633_led_set(struct led_classdev *led_cdev, - enum led_brightness value) -{ - struct pca9633_led *pca9633; - - pca9633 = container_of(led_cdev, struct pca9633_led, led_cdev); - - pca9633->cmd = BRIGHTNESS_SET; - pca9633->brightness = value; - - /* - * Must use workqueue for the actual I/O since I2C operations - * can sleep. - */ - schedule_work(&pca9633->work); -} - -static int pca9633_blink_set(struct led_classdev *led_cdev, - unsigned long *delay_on, unsigned long *delay_off) -{ - struct pca9633_led *pca9633; - unsigned long time_on, time_off, period; - u8 gdc, gfrq; - - pca9633 = container_of(led_cdev, struct pca9633_led, led_cdev); - - time_on = *delay_on; - time_off = *delay_off; - - /* If both zero, pick reasonable defaults of 500ms each */ - if (!time_on && !time_off) { - time_on = 500; - time_off = 500; - } - - period = time_on + time_off; - - /* If period not supported by hardware, default to someting sane. */ - if ((period < PCA9632_BLINK_PERIOD_MIN) || - (period > PCA9632_BLINK_PERIOD_MAX)) { - time_on = 500; - time_off = 500; - period = time_on + time_off; - } - - /* - * From manual: duty cycle = (GDC / 256) -> - * (time_on / period) = (GDC / 256) -> - * GDC = ((time_on * 256) / period) - */ - gdc = (time_on * 256) / period; - - /* - * From manual: period = ((GFRQ + 1) / 24) in seconds. - * So, period (in ms) = (((GFRQ + 1) / 24) * 1000) -> - * GFRQ = ((period * 24 / 1000) - 1) - */ - gfrq = (period * 24 / 1000) - 1; - - pca9633->cmd = BLINK_SET; - pca9633->gdc = gdc; - pca9633->gfrq = gfrq; - - /* - * Must use workqueue for the actual I/O since I2C operations - * can sleep. - */ - schedule_work(&pca9633->work); - - *delay_on = time_on; - *delay_off = time_off; - - return 0; -} - -#if IS_ENABLED(CONFIG_OF) -static struct pca9633_platform_data * -pca9633_dt_init(struct i2c_client *client, struct pca9633_chipdef *chip) -{ - struct device_node *np = client->dev.of_node, *child; - struct pca9633_platform_data *pdata; - struct led_info *pca9633_leds; - int count; - - count = of_get_child_count(np); - if (!count || count > chip->n_leds) - return ERR_PTR(-ENODEV); - - pca9633_leds = devm_kzalloc(&client->dev, - sizeof(struct led_info) * chip->n_leds, GFP_KERNEL); - if (!pca9633_leds) - return ERR_PTR(-ENOMEM); - - for_each_child_of_node(np, child) { - struct led_info led; - u32 reg; - int res; - - led.name = - of_get_property(child, "label", NULL) ? : child->name; - led.default_trigger = - of_get_property(child, "linux,default-trigger", NULL); - res = of_property_read_u32(child, "reg", ®); - if (res != 0) - continue; - pca9633_leds[reg] = led; - } - pdata = devm_kzalloc(&client->dev, - sizeof(struct pca9633_platform_data), GFP_KERNEL); - if (!pdata) - return ERR_PTR(-ENOMEM); - - pdata->leds.leds = pca9633_leds; - pdata->leds.num_leds = count; - - /* default to open-drain unless totem pole (push-pull) is specified */ - if (of_property_read_bool(np, "nxp,totem-pole")) - pdata->outdrv = PCA9633_TOTEM_POLE; - else - pdata->outdrv = PCA9633_OPEN_DRAIN; - - /* default to software blinking unless hardware blinking is specified */ - if (of_property_read_bool(np, "nxp,hw-blink")) - pdata->blink_type = PCA9633_HW_BLINK; - else - pdata->blink_type = PCA9633_SW_BLINK; - - return pdata; -} - -static const struct of_device_id of_pca9633_match[] = { - { .compatible = "nxp,pca9632", }, - { .compatible = "nxp,pca9633", }, - { .compatible = "nxp,pca9634", }, - {}, -}; -#else -static struct pca9633_platform_data * -pca9633_dt_init(struct i2c_client *client, struct pca9633_chipdef *chip) -{ - return ERR_PTR(-ENODEV); -} -#endif - -static int pca9633_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct pca9633 *pca9633_chip; - struct pca9633_led *pca9633; - struct pca9633_platform_data *pdata; - struct pca9633_chipdef *chip; - int i, err; - - chip = &pca9633_chipdefs[id->driver_data]; - pdata = dev_get_platdata(&client->dev); - - if (!pdata) { - pdata = pca9633_dt_init(client, chip); - if (IS_ERR(pdata)) { - dev_warn(&client->dev, "could not parse configuration\n"); - pdata = NULL; - } - } - - if (pdata && (pdata->leds.num_leds < 1 || - pdata->leds.num_leds > chip->n_leds)) { - dev_err(&client->dev, "board info must claim 1-%d LEDs", - chip->n_leds); - return -EINVAL; - } - - pca9633_chip = devm_kzalloc(&client->dev, sizeof(*pca9633_chip), - GFP_KERNEL); - if (!pca9633_chip) - return -ENOMEM; - pca9633 = devm_kzalloc(&client->dev, chip->n_leds * sizeof(*pca9633), - GFP_KERNEL); - if (!pca9633) - return -ENOMEM; - - i2c_set_clientdata(client, pca9633_chip); - - mutex_init(&pca9633_chip->mutex); - pca9633_chip->chipdef = chip; - pca9633_chip->client = client; - pca9633_chip->leds = pca9633; - - /* Turn off LEDs by default*/ - i2c_smbus_write_byte_data(client, chip->ledout_base, 0x00); - if (chip->n_leds > 4) - i2c_smbus_write_byte_data(client, chip->ledout_base + 1, 0x00); - - for (i = 0; i < chip->n_leds; i++) { - pca9633[i].led_num = i; - pca9633[i].chip = pca9633_chip; - - /* Platform data can specify LED names and default triggers */ - if (pdata && i < pdata->leds.num_leds) { - if (pdata->leds.leds[i].name) - snprintf(pca9633[i].name, - sizeof(pca9633[i].name), "pca9633:%s", - pdata->leds.leds[i].name); - if (pdata->leds.leds[i].default_trigger) - pca9633[i].led_cdev.default_trigger = - pdata->leds.leds[i].default_trigger; - } - if (!pdata || i >= pdata->leds.num_leds || - !pdata->leds.leds[i].name) - snprintf(pca9633[i].name, sizeof(pca9633[i].name), - "pca9633:%d:%.2x:%d", client->adapter->nr, - client->addr, i); - - pca9633[i].led_cdev.name = pca9633[i].name; - pca9633[i].led_cdev.brightness_set = pca9633_led_set; - - if (pdata && pdata->blink_type == PCA9633_HW_BLINK) - pca9633[i].led_cdev.blink_set = pca9633_blink_set; - - INIT_WORK(&pca9633[i].work, pca9633_work); - - err = led_classdev_register(&client->dev, &pca9633[i].led_cdev); - if (err < 0) - goto exit; - } - - /* Disable LED all-call address and set normal mode */ - i2c_smbus_write_byte_data(client, PCA9633_MODE1, 0x00); - - /* Configure output: open-drain or totem pole (push-pull) */ - if (pdata && pdata->outdrv == PCA9633_OPEN_DRAIN) - i2c_smbus_write_byte_data(client, PCA9633_MODE2, 0x01); - - return 0; - -exit: - while (i--) { - led_classdev_unregister(&pca9633[i].led_cdev); - cancel_work_sync(&pca9633[i].work); - } - - return err; -} - -static int pca9633_remove(struct i2c_client *client) -{ - struct pca9633 *pca9633 = i2c_get_clientdata(client); - int i; - - for (i = 0; i < pca9633->chipdef->n_leds; i++) { - led_classdev_unregister(&pca9633->leds[i].led_cdev); - cancel_work_sync(&pca9633->leds[i].work); - } - - return 0; -} - -static struct i2c_driver pca9633_driver = { - .driver = { - .name = "leds-pca9633", - .owner = THIS_MODULE, - .of_match_table = of_match_ptr(of_pca9633_match), - }, - .probe = pca9633_probe, - .remove = pca9633_remove, - .id_table = pca9633_id, -}; - -module_i2c_driver(pca9633_driver); - -MODULE_AUTHOR("Peter Meerwald "); -MODULE_DESCRIPTION("PCA9633 LED driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c new file mode 100644 index 000000000000..35d56a62f92f --- /dev/null +++ b/drivers/leds/leds-pca963x.c @@ -0,0 +1,461 @@ +/* + * Copyright 2011 bct electronic GmbH + * Copyright 2013 Qtechnology/AS + * + * Author: Peter Meerwald + * Author: Ricardo Ribalda + * + * Based on leds-pca955x.c + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * LED driver for the PCA9633 I2C LED driver (7-bit slave address 0x62) + * LED driver for the PCA9634 I2C LED driver (7-bit slave address set by hw.) + * + * Note that hardware blinking violates the leds infrastructure driver + * interface since the hardware only supports blinking all LEDs with the + * same delay_on/delay_off rates. That is, only the LEDs that are set to + * blink will actually blink but all LEDs that are set to blink will blink + * in identical fashion. The delay_on/delay_off values of the last LED + * that is set to blink will be used for all of the blinking LEDs. + * Hardware blinking is disabled by default but can be enabled by setting + * the 'blink_type' member in the platform_data struct to 'PCA963X_HW_BLINK' + * or by adding the 'nxp,hw-blink' property to the DTS. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* LED select registers determine the source that drives LED outputs */ +#define PCA963X_LED_OFF 0x0 /* LED driver off */ +#define PCA963X_LED_ON 0x1 /* LED driver on */ +#define PCA963X_LED_PWM 0x2 /* Controlled through PWM */ +#define PCA963X_LED_GRP_PWM 0x3 /* Controlled through PWM/GRPPWM */ + +#define PCA963X_MODE2_DMBLNK 0x20 /* Enable blinking */ + +#define PCA963X_MODE1 0x00 +#define PCA963X_MODE2 0x01 +#define PCA963X_PWM_BASE 0x02 + +enum pca963x_type { + pca9633, + pca9634, +}; + +struct pca963x_chipdef { + u8 grppwm; + u8 grpfreq; + u8 ledout_base; + int n_leds; +}; + +static struct pca963x_chipdef pca963x_chipdefs[] = { + [pca9633] = { + .grppwm = 0x6, + .grpfreq = 0x7, + .ledout_base = 0x8, + .n_leds = 4, + }, + [pca9634] = { + .grppwm = 0xa, + .grpfreq = 0xb, + .ledout_base = 0xc, + .n_leds = 8, + }, +}; + +/* Total blink period in milliseconds */ +#define PCA963X_BLINK_PERIOD_MIN 42 +#define PCA963X_BLINK_PERIOD_MAX 10667 + +static const struct i2c_device_id pca963x_id[] = { + { "pca9632", pca9633 }, + { "pca9633", pca9633 }, + { "pca9634", pca9634 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pca963x_id); + +enum pca963x_cmd { + BRIGHTNESS_SET, + BLINK_SET, +}; + +struct pca963x_led; + +struct pca963x { + struct pca963x_chipdef *chipdef; + struct mutex mutex; + struct i2c_client *client; + struct pca963x_led *leds; +}; + +struct pca963x_led { + struct pca963x *chip; + struct work_struct work; + enum led_brightness brightness; + struct led_classdev led_cdev; + int led_num; /* 0 .. 7 potentially */ + enum pca963x_cmd cmd; + char name[32]; + u8 gdc; + u8 gfrq; +}; + +static void pca963x_brightness_work(struct pca963x_led *pca963x) +{ + u8 ledout_addr = pca963x->chip->chipdef->ledout_base + + (pca963x->led_num / 4); + u8 ledout; + int shift = 2 * (pca963x->led_num % 4); + u8 mask = 0x3 << shift; + + mutex_lock(&pca963x->chip->mutex); + ledout = i2c_smbus_read_byte_data(pca963x->chip->client, ledout_addr); + switch (pca963x->brightness) { + case LED_FULL: + i2c_smbus_write_byte_data(pca963x->chip->client, ledout_addr, + (ledout & ~mask) | (PCA963X_LED_ON << shift)); + break; + case LED_OFF: + i2c_smbus_write_byte_data(pca963x->chip->client, ledout_addr, + ledout & ~mask); + break; + default: + i2c_smbus_write_byte_data(pca963x->chip->client, + PCA963X_PWM_BASE + pca963x->led_num, + pca963x->brightness); + i2c_smbus_write_byte_data(pca963x->chip->client, ledout_addr, + (ledout & ~mask) | (PCA963X_LED_PWM << shift)); + break; + } + mutex_unlock(&pca963x->chip->mutex); +} + +static void pca963x_blink_work(struct pca963x_led *pca963x) +{ + u8 ledout_addr = pca963x->chip->chipdef->ledout_base + + (pca963x->led_num / 4); + u8 ledout; + u8 mode2 = i2c_smbus_read_byte_data(pca963x->chip->client, + PCA963X_MODE2); + int shift = 2 * (pca963x->led_num % 4); + u8 mask = 0x3 << shift; + + i2c_smbus_write_byte_data(pca963x->chip->client, + pca963x->chip->chipdef->grppwm, pca963x->gdc); + + i2c_smbus_write_byte_data(pca963x->chip->client, + pca963x->chip->chipdef->grpfreq, pca963x->gfrq); + + if (!(mode2 & PCA963X_MODE2_DMBLNK)) + i2c_smbus_write_byte_data(pca963x->chip->client, PCA963X_MODE2, + mode2 | PCA963X_MODE2_DMBLNK); + + mutex_lock(&pca963x->chip->mutex); + ledout = i2c_smbus_read_byte_data(pca963x->chip->client, ledout_addr); + if ((ledout & mask) != (PCA963X_LED_GRP_PWM << shift)) + i2c_smbus_write_byte_data(pca963x->chip->client, ledout_addr, + (ledout & ~mask) | (PCA963X_LED_GRP_PWM << shift)); + mutex_unlock(&pca963x->chip->mutex); +} + +static void pca963x_work(struct work_struct *work) +{ + struct pca963x_led *pca963x = container_of(work, + struct pca963x_led, work); + + switch (pca963x->cmd) { + case BRIGHTNESS_SET: + pca963x_brightness_work(pca963x); + break; + case BLINK_SET: + pca963x_blink_work(pca963x); + break; + } +} + +static void pca963x_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct pca963x_led *pca963x; + + pca963x = container_of(led_cdev, struct pca963x_led, led_cdev); + + pca963x->cmd = BRIGHTNESS_SET; + pca963x->brightness = value; + + /* + * Must use workqueue for the actual I/O since I2C operations + * can sleep. + */ + schedule_work(&pca963x->work); +} + +static int pca963x_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, unsigned long *delay_off) +{ + struct pca963x_led *pca963x; + unsigned long time_on, time_off, period; + u8 gdc, gfrq; + + pca963x = container_of(led_cdev, struct pca963x_led, led_cdev); + + time_on = *delay_on; + time_off = *delay_off; + + /* If both zero, pick reasonable defaults of 500ms each */ + if (!time_on && !time_off) { + time_on = 500; + time_off = 500; + } + + period = time_on + time_off; + + /* If period not supported by hardware, default to someting sane. */ + if ((period < PCA963X_BLINK_PERIOD_MIN) || + (period > PCA963X_BLINK_PERIOD_MAX)) { + time_on = 500; + time_off = 500; + period = time_on + time_off; + } + + /* + * From manual: duty cycle = (GDC / 256) -> + * (time_on / period) = (GDC / 256) -> + * GDC = ((time_on * 256) / period) + */ + gdc = (time_on * 256) / period; + + /* + * From manual: period = ((GFRQ + 1) / 24) in seconds. + * So, period (in ms) = (((GFRQ + 1) / 24) * 1000) -> + * GFRQ = ((period * 24 / 1000) - 1) + */ + gfrq = (period * 24 / 1000) - 1; + + pca963x->cmd = BLINK_SET; + pca963x->gdc = gdc; + pca963x->gfrq = gfrq; + + /* + * Must use workqueue for the actual I/O since I2C operations + * can sleep. + */ + schedule_work(&pca963x->work); + + *delay_on = time_on; + *delay_off = time_off; + + return 0; +} + +#if IS_ENABLED(CONFIG_OF) +static struct pca963x_platform_data * +pca963x_dt_init(struct i2c_client *client, struct pca963x_chipdef *chip) +{ + struct device_node *np = client->dev.of_node, *child; + struct pca963x_platform_data *pdata; + struct led_info *pca963x_leds; + int count; + + count = of_get_child_count(np); + if (!count || count > chip->n_leds) + return ERR_PTR(-ENODEV); + + pca963x_leds = devm_kzalloc(&client->dev, + sizeof(struct led_info) * chip->n_leds, GFP_KERNEL); + if (!pca963x_leds) + return ERR_PTR(-ENOMEM); + + for_each_child_of_node(np, child) { + struct led_info led; + u32 reg; + int res; + + led.name = + of_get_property(child, "label", NULL) ? : child->name; + led.default_trigger = + of_get_property(child, "linux,default-trigger", NULL); + res = of_property_read_u32(child, "reg", ®); + if (res != 0) + continue; + pca963x_leds[reg] = led; + } + pdata = devm_kzalloc(&client->dev, + sizeof(struct pca963x_platform_data), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->leds.leds = pca963x_leds; + pdata->leds.num_leds = count; + + /* default to open-drain unless totem pole (push-pull) is specified */ + if (of_property_read_bool(np, "nxp,totem-pole")) + pdata->outdrv = PCA963X_TOTEM_POLE; + else + pdata->outdrv = PCA963X_OPEN_DRAIN; + + /* default to software blinking unless hardware blinking is specified */ + if (of_property_read_bool(np, "nxp,hw-blink")) + pdata->blink_type = PCA963X_HW_BLINK; + else + pdata->blink_type = PCA963X_SW_BLINK; + + return pdata; +} + +static const struct of_device_id of_pca963x_match[] = { + { .compatible = "nxp,pca9632", }, + { .compatible = "nxp,pca9633", }, + { .compatible = "nxp,pca9634", }, + {}, +}; +#else +static struct pca963x_platform_data * +pca963x_dt_init(struct i2c_client *client, struct pca963x_chipdef *chip) +{ + return ERR_PTR(-ENODEV); +} +#endif + +static int pca963x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pca963x *pca963x_chip; + struct pca963x_led *pca963x; + struct pca963x_platform_data *pdata; + struct pca963x_chipdef *chip; + int i, err; + + chip = &pca963x_chipdefs[id->driver_data]; + pdata = dev_get_platdata(&client->dev); + + if (!pdata) { + pdata = pca963x_dt_init(client, chip); + if (IS_ERR(pdata)) { + dev_warn(&client->dev, "could not parse configuration\n"); + pdata = NULL; + } + } + + if (pdata && (pdata->leds.num_leds < 1 || + pdata->leds.num_leds > chip->n_leds)) { + dev_err(&client->dev, "board info must claim 1-%d LEDs", + chip->n_leds); + return -EINVAL; + } + + pca963x_chip = devm_kzalloc(&client->dev, sizeof(*pca963x_chip), + GFP_KERNEL); + if (!pca963x_chip) + return -ENOMEM; + pca963x = devm_kzalloc(&client->dev, chip->n_leds * sizeof(*pca963x), + GFP_KERNEL); + if (!pca963x) + return -ENOMEM; + + i2c_set_clientdata(client, pca963x_chip); + + mutex_init(&pca963x_chip->mutex); + pca963x_chip->chipdef = chip; + pca963x_chip->client = client; + pca963x_chip->leds = pca963x; + + /* Turn off LEDs by default*/ + i2c_smbus_write_byte_data(client, chip->ledout_base, 0x00); + if (chip->n_leds > 4) + i2c_smbus_write_byte_data(client, chip->ledout_base + 1, 0x00); + + for (i = 0; i < chip->n_leds; i++) { + pca963x[i].led_num = i; + pca963x[i].chip = pca963x_chip; + + /* Platform data can specify LED names and default triggers */ + if (pdata && i < pdata->leds.num_leds) { + if (pdata->leds.leds[i].name) + snprintf(pca963x[i].name, + sizeof(pca963x[i].name), "pca963x:%s", + pdata->leds.leds[i].name); + if (pdata->leds.leds[i].default_trigger) + pca963x[i].led_cdev.default_trigger = + pdata->leds.leds[i].default_trigger; + } + if (!pdata || i >= pdata->leds.num_leds || + !pdata->leds.leds[i].name) + snprintf(pca963x[i].name, sizeof(pca963x[i].name), + "pca963x:%d:%.2x:%d", client->adapter->nr, + client->addr, i); + + pca963x[i].led_cdev.name = pca963x[i].name; + pca963x[i].led_cdev.brightness_set = pca963x_led_set; + + if (pdata && pdata->blink_type == PCA963X_HW_BLINK) + pca963x[i].led_cdev.blink_set = pca963x_blink_set; + + INIT_WORK(&pca963x[i].work, pca963x_work); + + err = led_classdev_register(&client->dev, &pca963x[i].led_cdev); + if (err < 0) + goto exit; + } + + /* Disable LED all-call address and set normal mode */ + i2c_smbus_write_byte_data(client, PCA963X_MODE1, 0x00); + + /* Configure output: open-drain or totem pole (push-pull) */ + if (pdata && pdata->outdrv == PCA963X_OPEN_DRAIN) + i2c_smbus_write_byte_data(client, PCA963X_MODE2, 0x01); + + return 0; + +exit: + while (i--) { + led_classdev_unregister(&pca963x[i].led_cdev); + cancel_work_sync(&pca963x[i].work); + } + + return err; +} + +static int pca963x_remove(struct i2c_client *client) +{ + struct pca963x *pca963x = i2c_get_clientdata(client); + int i; + + for (i = 0; i < pca963x->chipdef->n_leds; i++) { + led_classdev_unregister(&pca963x->leds[i].led_cdev); + cancel_work_sync(&pca963x->leds[i].work); + } + + return 0; +} + +static struct i2c_driver pca963x_driver = { + .driver = { + .name = "leds-pca963x", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(of_pca963x_match), + }, + .probe = pca963x_probe, + .remove = pca963x_remove, + .id_table = pca963x_id, +}; + +module_i2c_driver(pca963x_driver); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_DESCRIPTION("PCA963X LED driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/platform_data/leds-pca9633.h b/include/linux/platform_data/leds-pca9633.h deleted file mode 100644 index 3c1037a81d34..000000000000 --- a/include/linux/platform_data/leds-pca9633.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * PCA9633 LED chip driver. - * - * Copyright 2012 bct electronic GmbH - * - * 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. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA - */ - -#ifndef __LINUX_PCA9633_H -#define __LINUX_PCA9633_H -#include - -enum pca9633_outdrv { - PCA9633_OPEN_DRAIN, - PCA9633_TOTEM_POLE, /* aka push-pull */ -}; - -enum pca9633_blink_type { - PCA9633_SW_BLINK, - PCA9633_HW_BLINK, -}; - -struct pca9633_platform_data { - struct led_platform_data leds; - enum pca9633_outdrv outdrv; - enum pca9633_blink_type blink_type; -}; - -#endif /* __LINUX_PCA9633_H*/ diff --git a/include/linux/platform_data/leds-pca963x.h b/include/linux/platform_data/leds-pca963x.h new file mode 100644 index 000000000000..e731f0036329 --- /dev/null +++ b/include/linux/platform_data/leds-pca963x.h @@ -0,0 +1,42 @@ +/* + * PCA963X LED chip driver. + * + * Copyright 2012 bct electronic GmbH + * Copyright 2013 Qtechnology A/S + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __LINUX_PCA963X_H +#define __LINUX_PCA963X_H +#include + +enum pca963x_outdrv { + PCA963X_OPEN_DRAIN, + PCA963X_TOTEM_POLE, /* aka push-pull */ +}; + +enum pca963x_blink_type { + PCA963X_SW_BLINK, + PCA963X_HW_BLINK, +}; + +struct pca963x_platform_data { + struct led_platform_data leds; + enum pca963x_outdrv outdrv; + enum pca963x_blink_type blink_type; +}; + +#endif /* __LINUX_PCA963X_H*/ -- cgit v1.2.3-71-gd317 From 5877457a96e5f89567d2eea092ec28db5d55fc06 Mon Sep 17 00:00:00 2001 From: Vivien Didelot Date: Thu, 29 Aug 2013 15:24:14 -0400 Subject: gpio: (gpio-pca953x) move header to linux/platform_data/ This patch moves the pca953x.h header from include/linux/i2c to include/linux/platform_data and updates existing support accordingly. Acked-by: Linus Walleij Acked-by: H Hartley Sweeten Signed-off-by: Vivien Didelot Signed-off-by: Olof Johansson --- arch/arm/mach-at91/board-snapper9260.c | 2 +- arch/arm/mach-davinci/board-da850-evm.c | 2 +- arch/arm/mach-ep93xx/vision_ep9307.c | 2 +- arch/arm/mach-imx/mach-imx27_visstrim_m10.c | 2 +- arch/arm/mach-imx/mach-mxt_td60.c | 2 +- arch/arm/mach-ks8695/board-acs5k.c | 2 +- arch/arm/mach-mmp/ttc_dkb.c | 2 +- arch/arm/mach-omap2/board-am3517evm.c | 2 +- arch/arm/mach-pxa/cm-x300.c | 2 +- arch/arm/mach-pxa/em-x270.c | 2 +- arch/arm/mach-pxa/pcm990-baseboard.c | 2 +- arch/arm/mach-pxa/spitz.c | 2 +- arch/arm/mach-pxa/zeus.c | 2 +- arch/arm/mach-pxa/zylonite_pxa300.c | 2 +- arch/arm/mach-s3c64xx/mach-crag6410.c | 2 +- arch/x86/platform/mrst/mrst.c | 2 +- drivers/gpio/gpio-pca953x.c | 2 +- include/linux/i2c/pca953x.h | 30 ----------------------------- include/linux/platform_data/pca953x.h | 30 +++++++++++++++++++++++++++++ 19 files changed, 47 insertions(+), 47 deletions(-) delete mode 100644 include/linux/i2c/pca953x.h create mode 100644 include/linux/platform_data/pca953x.h (limited to 'include/linux/platform_data') diff --git a/arch/arm/mach-at91/board-snapper9260.c b/arch/arm/mach-at91/board-snapper9260.c index 3aaa9784cf0e..f1d49e929ccb 100644 --- a/arch/arm/mach-at91/board-snapper9260.c +++ b/arch/arm/mach-at91/board-snapper9260.c @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include diff --git a/arch/arm/mach-davinci/board-da850-evm.c b/arch/arm/mach-davinci/board-da850-evm.c index bea6793a7ede..af6dafbd02ef 100644 --- a/arch/arm/mach-davinci/board-da850-evm.c +++ b/arch/arm/mach-davinci/board-da850-evm.c @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-ep93xx/vision_ep9307.c b/arch/arm/mach-ep93xx/vision_ep9307.c index 605956fd07a2..64f2e50e19ca 100644 --- a/arch/arm/mach-ep93xx/vision_ep9307.c +++ b/arch/arm/mach-ep93xx/vision_ep9307.c @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-imx/mach-imx27_visstrim_m10.c b/arch/arm/mach-imx/mach-imx27_visstrim_m10.c index 29ac8ee651d2..97f9c6297fcf 100644 --- a/arch/arm/mach-imx/mach-imx27_visstrim_m10.c +++ b/arch/arm/mach-imx/mach-imx27_visstrim_m10.c @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-imx/mach-mxt_td60.c b/arch/arm/mach-imx/mach-mxt_td60.c index a27faaba98ec..c91894003da9 100644 --- a/arch/arm/mach-imx/mach-mxt_td60.c +++ b/arch/arm/mach-imx/mach-mxt_td60.c @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include "common.h" #include "devices-imx27.h" diff --git a/arch/arm/mach-ks8695/board-acs5k.c b/arch/arm/mach-ks8695/board-acs5k.c index 456d6386edf8..9f9c0441a917 100644 --- a/arch/arm/mach-ks8695/board-acs5k.c +++ b/arch/arm/mach-ks8695/board-acs5k.c @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include diff --git a/arch/arm/mach-mmp/ttc_dkb.c b/arch/arm/mach-mmp/ttc_dkb.c index 8483906d4308..702232996c8c 100644 --- a/arch/arm/mach-mmp/ttc_dkb.c +++ b/arch/arm/mach-mmp/ttc_dkb.c @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-omap2/board-am3517evm.c b/arch/arm/mach-omap2/board-am3517evm.c index d63f14b534b5..c7196a69ff37 100644 --- a/arch/arm/mach-omap2/board-am3517evm.c +++ b/arch/arm/mach-omap2/board-am3517evm.c @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-pxa/cm-x300.c b/arch/arm/mach-pxa/cm-x300.c index 8091aac89edf..f9423493ed36 100644 --- a/arch/arm/mach-pxa/cm-x300.c +++ b/arch/arm/mach-pxa/cm-x300.c @@ -29,7 +29,7 @@ #include #include -#include +#include #include #include diff --git a/arch/arm/mach-pxa/em-x270.c b/arch/arm/mach-pxa/em-x270.c index 3a3362fa793e..8eb4e23c561d 100644 --- a/arch/arm/mach-pxa/em-x270.c +++ b/arch/arm/mach-pxa/em-x270.c @@ -30,7 +30,7 @@ #include #include #include -#include +#include #include #include diff --git a/arch/arm/mach-pxa/pcm990-baseboard.c b/arch/arm/mach-pxa/pcm990-baseboard.c index 13e5b00eae90..3133ba82c508 100644 --- a/arch/arm/mach-pxa/pcm990-baseboard.c +++ b/arch/arm/mach-pxa/pcm990-baseboard.c @@ -408,7 +408,7 @@ struct pxacamera_platform_data pcm990_pxacamera_platform_data = { .mclk_10khz = 1000, }; -#include +#include static struct pca953x_platform_data pca9536_data = { .gpio_base = PXA_NR_BUILTIN_GPIO, diff --git a/arch/arm/mach-pxa/spitz.c b/arch/arm/mach-pxa/spitz.c index 4c29173026e8..0b11c1af51c4 100644 --- a/arch/arm/mach-pxa/spitz.c +++ b/arch/arm/mach-pxa/spitz.c @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-pxa/zeus.c b/arch/arm/mach-pxa/zeus.c index f5d436434566..126e4a806a07 100644 --- a/arch/arm/mach-pxa/zeus.c +++ b/arch/arm/mach-pxa/zeus.c @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include diff --git a/arch/arm/mach-pxa/zylonite_pxa300.c b/arch/arm/mach-pxa/zylonite_pxa300.c index 86e59c043de2..869bce7c3f24 100644 --- a/arch/arm/mach-pxa/zylonite_pxa300.c +++ b/arch/arm/mach-pxa/zylonite_pxa300.c @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include diff --git a/arch/arm/mach-s3c64xx/mach-crag6410.c b/arch/arm/mach-s3c64xx/mach-crag6410.c index 28889cc788b3..eb8e5a1aca42 100644 --- a/arch/arm/mach-s3c64xx/mach-crag6410.c +++ b/arch/arm/mach-s3c64xx/mach-crag6410.c @@ -30,7 +30,7 @@ #include #include -#include +#include #include #include