From b6b5e76bb8bb22ecff90a7840dc4845d63328289 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Thu, 23 May 2013 20:14:50 +0200 Subject: ASoC: Add ssm2518 support This patch adds a ASoC CODEC driver for the SSM2516. The SSM2516 is a stereo Class-D audio amplifier with an I2S interface for audio in and a built-in dynamic range control processor. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown --- include/linux/platform_data/ssm2518.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 include/linux/platform_data/ssm2518.h (limited to 'include/linux/platform_data') diff --git a/include/linux/platform_data/ssm2518.h b/include/linux/platform_data/ssm2518.h new file mode 100644 index 000000000000..9a8e3ea287e3 --- /dev/null +++ b/include/linux/platform_data/ssm2518.h @@ -0,0 +1,22 @@ +/* + * SSM2518 amplifier audio driver + * + * Copyright 2013 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2. + */ + +#ifndef __LINUX_PLATFORM_DATA_SSM2518_H__ +#define __LINUX_PLATFORM_DATA_SSM2518_H__ + +/** + * struct ssm2518_platform_data - Platform data for the ssm2518 driver + * @enable_gpio: GPIO connected to the nSD pin. Set to -1 if the nSD pin is + * hardwired. + */ +struct ssm2518_platform_data { + int enable_gpio; +}; + +#endif -- cgit v1.2.3 From 1a0483d2a4c2c5e218d415c90d1a62b3b917d34e Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Fri, 3 May 2013 07:33:27 +0200 Subject: clk: si5351: Allow user to define disabled state for every clock output This patch adds platform data and DT bindings to allow to overwrite the stored disabled state for each clock output. Signed-off-by: Marek Belisko Signed-off-by: Sebastian Hesselbarth Signed-off-by: Mike Turquette --- .../devicetree/bindings/clock/silabs,si5351.txt | 5 ++ drivers/clk/clk-si5351.c | 74 +++++++++++++++++++++- drivers/clk/clk-si5351.h | 1 + include/linux/platform_data/si5351.h | 18 ++++++ 4 files changed, 95 insertions(+), 3 deletions(-) (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt index cc374651662c..66c75b2d6158 100644 --- a/Documentation/devicetree/bindings/clock/silabs,si5351.txt +++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt @@ -44,6 +44,11 @@ Optional child node properties: - silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth divider. - silabs,pll-master: boolean, multisynth can change pll frequency. +- silabs,disable-state : clock output disable state, shall be + 0 = clock output is driven LOW when disabled + 1 = clock output is driven HIGH when disabled + 2 = clock output is FLOATING (HIGH-Z) when disabled + 3 = clock output is NEVER disabled ==Example== diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c index 892728412e9d..efc6d5e9268b 100644 --- a/drivers/clk/clk-si5351.c +++ b/drivers/clk/clk-si5351.c @@ -851,6 +851,41 @@ static int _si5351_clkout_set_drive_strength( return 0; } +static int _si5351_clkout_set_disable_state( + struct si5351_driver_data *drvdata, int num, + enum si5351_disable_state state) +{ + u8 reg = (num < 4) ? SI5351_CLK3_0_DISABLE_STATE : + SI5351_CLK7_4_DISABLE_STATE; + u8 shift = (num < 4) ? (2 * num) : (2 * (num-4)); + u8 mask = SI5351_CLK_DISABLE_STATE_MASK << shift; + u8 val; + + if (num > 8) + return -EINVAL; + + switch (state) { + case SI5351_DISABLE_LOW: + val = SI5351_CLK_DISABLE_STATE_LOW; + break; + case SI5351_DISABLE_HIGH: + val = SI5351_CLK_DISABLE_STATE_HIGH; + break; + case SI5351_DISABLE_FLOATING: + val = SI5351_CLK_DISABLE_STATE_FLOAT; + break; + case SI5351_DISABLE_NEVER: + val = SI5351_CLK_DISABLE_STATE_NEVER; + break; + default: + return 0; + } + + si5351_set_bits(drvdata, reg, mask, val << shift); + + return 0; +} + static int si5351_clkout_prepare(struct clk_hw *hw) { struct si5351_hw_data *hwdata = @@ -1225,6 +1260,33 @@ static int si5351_dt_parse(struct i2c_client *client) } } + if (!of_property_read_u32(child, "silabs,disable-state", + &val)) { + switch (val) { + case 0: + pdata->clkout[num].disable_state = + SI5351_DISABLE_LOW; + break; + case 1: + pdata->clkout[num].disable_state = + SI5351_DISABLE_HIGH; + break; + case 2: + pdata->clkout[num].disable_state = + SI5351_DISABLE_FLOATING; + break; + case 3: + pdata->clkout[num].disable_state = + SI5351_DISABLE_NEVER; + break; + default: + dev_err(&client->dev, + "invalid disable state %d for clkout %d\n", + val, num); + return -EINVAL; + } + } + if (!of_property_read_u32(child, "clock-frequency", &val)) pdata->clkout[num].rate = val; @@ -1281,9 +1343,6 @@ static int si5351_i2c_probe(struct i2c_client *client, /* Disable interrupts */ si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0); - /* Set disabled output drivers to drive low */ - si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00); - si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00); /* Ensure pll select is on XTAL for Si5351A/B */ if (drvdata->variant != SI5351_VARIANT_C) si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, @@ -1327,6 +1386,15 @@ static int si5351_i2c_probe(struct i2c_client *client, n, pdata->clkout[n].drive); return ret; } + + ret = _si5351_clkout_set_disable_state(drvdata, n, + pdata->clkout[n].disable_state); + if (ret) { + dev_err(&client->dev, + "failed set disable state of clkout%d to %d\n", + n, pdata->clkout[n].disable_state); + return ret; + } } /* register xtal input clock gate */ diff --git a/drivers/clk/clk-si5351.h b/drivers/clk/clk-si5351.h index af41b5080f43..c0dbf2676872 100644 --- a/drivers/clk/clk-si5351.h +++ b/drivers/clk/clk-si5351.h @@ -81,6 +81,7 @@ #define SI5351_CLK3_0_DISABLE_STATE 24 #define SI5351_CLK7_4_DISABLE_STATE 25 +#define SI5351_CLK_DISABLE_STATE_MASK 3 #define SI5351_CLK_DISABLE_STATE_LOW 0 #define SI5351_CLK_DISABLE_STATE_HIGH 1 #define SI5351_CLK_DISABLE_STATE_FLOAT 2 diff --git a/include/linux/platform_data/si5351.h b/include/linux/platform_data/si5351.h index 92dabcaf6499..54334393ab92 100644 --- a/include/linux/platform_data/si5351.h +++ b/include/linux/platform_data/si5351.h @@ -78,6 +78,23 @@ enum si5351_drive_strength { SI5351_DRIVE_8MA = 8, }; +/** + * enum si5351_disable_state - Si5351 clock output disable state + * @SI5351_DISABLE_DEFAULT: default, do not change eeprom config + * @SI5351_DISABLE_LOW: CLKx is set to a LOW state when disabled + * @SI5351_DISABLE_HIGH: CLKx is set to a HIGH state when disabled + * @SI5351_DISABLE_FLOATING: CLKx is set to a FLOATING state when + * disabled + * @SI5351_DISABLE_NEVER: CLKx is NEVER disabled + */ +enum si5351_disable_state { + SI5351_DISABLE_DEFAULT = 0, + SI5351_DISABLE_LOW, + SI5351_DISABLE_HIGH, + SI5351_DISABLE_FLOATING, + SI5351_DISABLE_NEVER, +}; + /** * struct si5351_clkout_config - Si5351 clock output configuration * @clkout: clkout number @@ -91,6 +108,7 @@ struct si5351_clkout_config { enum si5351_multisynth_src multisynth_src; enum si5351_clkout_src clkout_src; enum si5351_drive_strength drive; + enum si5351_disable_state disable_state; bool pll_master; unsigned long rate; }; -- cgit v1.2.3 From c992219825823cad5e11fab3854eb93df2e9ffa1 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sun, 2 Jun 2013 09:53:01 -0400 Subject: cw1200: move platform_data header to correct location. (As suggested by Arnd Bergmann) Signed-off-by: Solomon Peachy Signed-off-by: John W. Linville --- drivers/net/wireless/cw1200/cw1200_sagrad.c | 2 +- drivers/net/wireless/cw1200/cw1200_sdio.c | 2 +- drivers/net/wireless/cw1200/cw1200_spi.c | 2 +- include/linux/cw1200_platform.h | 46 --------------------------- include/linux/platform_data/cw1200_platform.h | 46 +++++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 49 deletions(-) delete mode 100644 include/linux/cw1200_platform.h create mode 100644 include/linux/platform_data/cw1200_platform.h (limited to 'include/linux/platform_data') diff --git a/drivers/net/wireless/cw1200/cw1200_sagrad.c b/drivers/net/wireless/cw1200/cw1200_sagrad.c index a5ada0eda1dd..14c2a186b493 100644 --- a/drivers/net/wireless/cw1200/cw1200_sagrad.c +++ b/drivers/net/wireless/cw1200/cw1200_sagrad.c @@ -10,7 +10,7 @@ */ #include -#include +#include MODULE_AUTHOR("Solomon Peachy "); MODULE_DESCRIPTION("ST-Ericsson CW1200 Platform glue driver"); diff --git a/drivers/net/wireless/cw1200/cw1200_sdio.c b/drivers/net/wireless/cw1200/cw1200_sdio.c index 78c3bc55cd0b..9f25ec5641fe 100644 --- a/drivers/net/wireless/cw1200/cw1200_sdio.c +++ b/drivers/net/wireless/cw1200/cw1200_sdio.c @@ -20,7 +20,7 @@ #include "cw1200.h" #include "hwbus.h" -#include +#include #include "hwio.h" MODULE_AUTHOR("Dmitry Tarnyagin "); diff --git a/drivers/net/wireless/cw1200/cw1200_spi.c b/drivers/net/wireless/cw1200/cw1200_spi.c index 75efe54e495f..940e0947a5dc 100644 --- a/drivers/net/wireless/cw1200/cw1200_spi.c +++ b/drivers/net/wireless/cw1200/cw1200_spi.c @@ -25,7 +25,7 @@ #include "cw1200.h" #include "hwbus.h" -#include +#include #include "hwio.h" MODULE_AUTHOR("Solomon Peachy "); diff --git a/include/linux/cw1200_platform.h b/include/linux/cw1200_platform.h deleted file mode 100644 index c168fa512347..000000000000 --- a/include/linux/cw1200_platform.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) ST-Ericsson SA 2011 - * - * Author: Dmitry Tarnyagin - * License terms: GNU General Public License (GPL) version 2 - */ - -#ifndef CW1200_PLAT_H_INCLUDED -#define CW1200_PLAT_H_INCLUDED - -struct cw1200_platform_data_spi { - u8 spi_bits_per_word; /* REQUIRED */ - u16 ref_clk; /* REQUIRED (in KHz) */ - - /* All others are optional */ - bool have_5ghz; - const struct resource *reset; /* GPIO to RSTn signal */ - const struct resource *powerup; /* GPIO to POWERUP signal */ - int (*power_ctrl)(const struct cw1200_platform_data_spi *pdata, - bool enable); /* Control 3v3 / 1v8 supply */ - int (*clk_ctrl)(const struct cw1200_platform_data_spi *pdata, - bool enable); /* Control CLK32K */ - const u8 *macaddr; /* if NULL, use cw1200_mac_template module parameter */ - const char *sdd_file; /* if NULL, will use default for detected hw type */ -}; - -struct cw1200_platform_data_sdio { - u16 ref_clk; /* REQUIRED (in KHz) */ - - /* All others are optional */ - const struct resource *irq; /* if using GPIO for IRQ */ - bool have_5ghz; - bool no_nptb; /* SDIO hardware does not support non-power-of-2-blocksizes */ - const struct resource *reset; /* GPIO to RSTn signal */ - const struct resource *powerup; /* GPIO to POWERUP signal */ - int (*power_ctrl)(const struct cw1200_platform_data_sdio *pdata, - bool enable); /* Control 3v3 / 1v8 supply */ - int (*clk_ctrl)(const struct cw1200_platform_data_sdio *pdata, - bool enable); /* Control CLK32K */ - const u8 *macaddr; /* if NULL, use cw1200_mac_template module parameter */ - const char *sdd_file; /* if NULL, will use default for detected hw type */ -}; - -const void *cw1200_get_platform_data(void); - -#endif /* CW1200_PLAT_H_INCLUDED */ diff --git a/include/linux/platform_data/cw1200_platform.h b/include/linux/platform_data/cw1200_platform.h new file mode 100644 index 000000000000..c168fa512347 --- /dev/null +++ b/include/linux/platform_data/cw1200_platform.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Dmitry Tarnyagin + * License terms: GNU General Public License (GPL) version 2 + */ + +#ifndef CW1200_PLAT_H_INCLUDED +#define CW1200_PLAT_H_INCLUDED + +struct cw1200_platform_data_spi { + u8 spi_bits_per_word; /* REQUIRED */ + u16 ref_clk; /* REQUIRED (in KHz) */ + + /* All others are optional */ + bool have_5ghz; + const struct resource *reset; /* GPIO to RSTn signal */ + const struct resource *powerup; /* GPIO to POWERUP signal */ + int (*power_ctrl)(const struct cw1200_platform_data_spi *pdata, + bool enable); /* Control 3v3 / 1v8 supply */ + int (*clk_ctrl)(const struct cw1200_platform_data_spi *pdata, + bool enable); /* Control CLK32K */ + const u8 *macaddr; /* if NULL, use cw1200_mac_template module parameter */ + const char *sdd_file; /* if NULL, will use default for detected hw type */ +}; + +struct cw1200_platform_data_sdio { + u16 ref_clk; /* REQUIRED (in KHz) */ + + /* All others are optional */ + const struct resource *irq; /* if using GPIO for IRQ */ + bool have_5ghz; + bool no_nptb; /* SDIO hardware does not support non-power-of-2-blocksizes */ + const struct resource *reset; /* GPIO to RSTn signal */ + const struct resource *powerup; /* GPIO to POWERUP signal */ + int (*power_ctrl)(const struct cw1200_platform_data_sdio *pdata, + bool enable); /* Control 3v3 / 1v8 supply */ + int (*clk_ctrl)(const struct cw1200_platform_data_sdio *pdata, + bool enable); /* Control CLK32K */ + const u8 *macaddr; /* if NULL, use cw1200_mac_template module parameter */ + const char *sdd_file; /* if NULL, will use default for detected hw type */ +}; + +const void *cw1200_get_platform_data(void); + +#endif /* CW1200_PLAT_H_INCLUDED */ -- cgit v1.2.3 From 6dd64a304eff76ca7dd41bf63df55efa965fa9ec Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sun, 2 Jun 2013 09:53:03 -0400 Subject: cw1200: Replace use of 'struct resource' with 'int' for GPIO fields. The only advantage of 'struct resource' is that it lets us assign names as part of the platform data. Unfortunately since we are using platform data, we are already limited to a single instance of each driver, rendering this moot. So, replace the struct resources with ints, resulting in cleaner code. This was based on a suggestion from Arnd Bergmann. Signed-off-by: Solomon Peachy Signed-off-by: John W. Linville --- drivers/net/wireless/cw1200/cw1200_sagrad.c | 49 +++----------------------- drivers/net/wireless/cw1200/cw1200_sdio.c | 50 ++++++++++++--------------- drivers/net/wireless/cw1200/cw1200_spi.c | 33 ++++++++---------- include/linux/platform_data/cw1200_platform.h | 12 +++---- 4 files changed, 47 insertions(+), 97 deletions(-) (limited to 'include/linux/platform_data') diff --git a/drivers/net/wireless/cw1200/cw1200_sagrad.c b/drivers/net/wireless/cw1200/cw1200_sagrad.c index 14c2a186b493..3f884ac96ccc 100644 --- a/drivers/net/wireless/cw1200/cw1200_sagrad.c +++ b/drivers/net/wireless/cw1200/cw1200_sagrad.c @@ -21,29 +21,6 @@ MODULE_LICENSE("GPL"); /* #define SAGRAD_1091_1098_EVK_SPI */ #ifdef SAGRAD_1091_1098_EVK_SDIO -#if 0 -static struct resource cw1200_href_resources[] = { - { - .start = 215, /* fix me as appropriate */ - .end = 215, /* ditto */ - .flags = IORESOURCE_IO, - .name = "cw1200_wlan_reset", - }, - { - .start = 216, /* fix me as appropriate */ - .end = 216, /* ditto */ - .flags = IORESOURCE_IO, - .name = "cw1200_wlan_powerup", - }, - { - .start = NOMADIK_GPIO_TO_IRQ(216), /* fix me as appropriate */ - .end = NOMADIK_GPIO_TO_IRQ(216), /* ditto */ - .flags = IORESOURCE_IRQ, - .name = "cw1200_wlan_irq", - }, -}; -#endif - static int cw1200_power_ctrl(const struct cw1200_platform_data_sdio *pdata, bool enable) { @@ -68,9 +45,9 @@ static struct cw1200_platform_data_sdio cw1200_platform_data = { .ref_clk = 38400, .have_5ghz = false, #if 0 - .reset = &cw1200_href_resources[0], - .powerup = &cw1200_href_resources[1], - .irq = &cw1200_href_resources[2], + .reset = GPIO_RF_RESET, /* Replace as appropriate */ + .powerup = GPIO_RF_POWERUP, /* Replace as appropriate */ + .irq = GPIO_TO_IRQ(GPIO_RF_IRQ), /* Replace as appropriate */ #endif .power_ctrl = cw1200_power_ctrl, .clk_ctrl = cw1200_clk_ctrl, @@ -80,22 +57,6 @@ static struct cw1200_platform_data_sdio cw1200_platform_data = { #endif #ifdef SAGRAD_1091_1098_EVK_SPI -/* Note that this is an example of integrating into your board support file */ -static struct resource cw1200_href_resources[] = { - { - .start = GPIO_RF_RESET, - .end = GPIO_RF_RESET, - .flags = IORESOURCE_IO, - .name = "cw1200_wlan_reset", - }, - { - .start = GPIO_RF_POWERUP, - .end = GPIO_RF_POWERUP, - .flags = IORESOURCE_IO, - .name = "cw1200_wlan_powerup", - }, -}; - static int cw1200_power_ctrl(const struct cw1200_platform_data_spi *pdata, bool enable) { @@ -118,8 +79,8 @@ static int cw1200_clk_ctrl(const struct cw1200_platform_data_spi *pdata, static struct cw1200_platform_data_spi cw1200_platform_data = { .ref_clk = 38400, .spi_bits_per_word = 16, - .reset = &cw1200_href_resources[0], - .powerup = &cw1200_href_resources[1], + .reset = GPIO_RF_RESET, /* Replace as appropriate */ + .powerup = GPIO_RF_POWERUP, /* Replace as appropriate */ .power_ctrl = cw1200_power_ctrl, .clk_ctrl = cw1200_clk_ctrl, /* .macaddr = ??? */ diff --git a/drivers/net/wireless/cw1200/cw1200_sdio.c b/drivers/net/wireless/cw1200/cw1200_sdio.c index 0a882ca89bbb..574cf727567c 100644 --- a/drivers/net/wireless/cw1200/cw1200_sdio.c +++ b/drivers/net/wireless/cw1200/cw1200_sdio.c @@ -105,7 +105,6 @@ static irqreturn_t cw1200_gpio_irq(int irq, void *dev_id) static int cw1200_request_irq(struct hwbus_priv *self) { int ret; - const struct resource *irq = self->pdata->irq; u8 cccr; cccr = sdio_f0_readb(self->func, SDIO_CCCR_IENx, &ret); @@ -122,15 +121,15 @@ static int cw1200_request_irq(struct hwbus_priv *self) if (WARN_ON(ret)) goto err; - ret = enable_irq_wake(irq->start); + ret = enable_irq_wake(self->pdata->irq); if (WARN_ON(ret)) goto err; /* Request the IRQ */ - ret = request_threaded_irq(irq->start, cw1200_gpio_hardirq, + ret = request_threaded_irq(self->pdata->irq, cw1200_gpio_hardirq, cw1200_gpio_irq, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, - irq->name, self); + "cw1200_wlan_irq", self); if (WARN_ON(ret)) goto err; @@ -162,8 +161,8 @@ static int cw1200_sdio_irq_unsubscribe(struct hwbus_priv *self) pr_debug("SW IRQ unsubscribe\n"); if (self->pdata->irq) { - disable_irq_wake(self->pdata->irq->start); - free_irq(self->pdata->irq->start, self); + disable_irq_wake(self->pdata->irq); + free_irq(self->pdata->irq, self); } else { sdio_claim_host(self->func); ret = sdio_release_irq(self->func); @@ -174,12 +173,10 @@ static int cw1200_sdio_irq_unsubscribe(struct hwbus_priv *self) static int cw1200_sdio_off(const struct cw1200_platform_data_sdio *pdata) { - const struct resource *reset = pdata->reset; - - if (reset) { - gpio_set_value(reset->start, 0); + if (pdata->reset) { + gpio_set_value(pdata->reset, 0); msleep(30); /* Min is 2 * CLK32K cycles */ - gpio_free(reset->start); + gpio_free(pdata->reset); } if (pdata->power_ctrl) @@ -192,20 +189,17 @@ static int cw1200_sdio_off(const struct cw1200_platform_data_sdio *pdata) static int cw1200_sdio_on(const struct cw1200_platform_data_sdio *pdata) { - const struct resource *reset = pdata->reset; - const struct resource *powerup = pdata->powerup; - /* Ensure I/Os are pulled low */ - if (reset) { - gpio_request(reset->start, reset->name); - gpio_direction_output(reset->start, 0); + if (pdata->reset) { + gpio_request(pdata->reset, "cw1200_wlan_reset"); + gpio_direction_output(pdata->reset, 0); } - if (powerup) { - gpio_request(powerup->start, powerup->name); - gpio_direction_output(powerup->start, 0); + if (pdata->powerup) { + gpio_request(pdata->powerup, "cw1200_wlan_powerup"); + gpio_direction_output(pdata->powerup, 0); } - if (reset || powerup) - msleep(50); /* Settle time */ + if (pdata->reset || pdata->powerup) + msleep(10); /* Settle time? */ /* Enable 3v3 and 1v8 to hardware */ if (pdata->power_ctrl) { @@ -225,13 +219,13 @@ static int cw1200_sdio_on(const struct cw1200_platform_data_sdio *pdata) } /* Enable POWERUP signal */ - if (powerup) { - gpio_set_value(powerup->start, 1); + if (pdata->powerup) { + gpio_set_value(pdata->powerup, 1); msleep(250); /* or more..? */ } /* Enable RSTn signal */ - if (reset) { - gpio_set_value(reset->start, 1); + if (pdata->reset) { + gpio_set_value(pdata->reset, 1); msleep(50); /* Or more..? */ } return 0; @@ -252,7 +246,7 @@ static int cw1200_sdio_pm(struct hwbus_priv *self, bool suspend) int ret = 0; if (self->pdata->irq) - ret = irq_set_irq_wake(self->pdata->irq->start, suspend); + ret = irq_set_irq_wake(self->pdata->irq, suspend); return ret; } @@ -274,7 +268,7 @@ static int cw1200_sdio_probe(struct sdio_func *func, pr_info("cw1200_wlan_sdio: Probe called\n"); - /* We are only able to handle the wlan function */ + /* We are only able to handle the wlan function */ if (func->num != 0x01) return -ENODEV; diff --git a/drivers/net/wireless/cw1200/cw1200_spi.c b/drivers/net/wireless/cw1200/cw1200_spi.c index da5d24cc67db..d8dc7c6bb96d 100644 --- a/drivers/net/wireless/cw1200/cw1200_spi.c +++ b/drivers/net/wireless/cw1200/cw1200_spi.c @@ -268,12 +268,10 @@ static int cw1200_spi_irq_unsubscribe(struct hwbus_priv *self) static int cw1200_spi_off(const struct cw1200_platform_data_spi *pdata) { - const struct resource *reset = pdata->reset; - - if (reset) { - gpio_set_value(reset->start, 0); + if (pdata->reset) { + gpio_set_value(pdata->reset, 0); msleep(30); /* Min is 2 * CLK32K cycles */ - gpio_free(reset->start); + gpio_free(pdata->reset); } if (pdata->power_ctrl) @@ -286,19 +284,16 @@ static int cw1200_spi_off(const struct cw1200_platform_data_spi *pdata) static int cw1200_spi_on(const struct cw1200_platform_data_spi *pdata) { - const struct resource *reset = pdata->reset; - const struct resource *powerup = pdata->powerup; - /* Ensure I/Os are pulled low */ - if (reset) { - gpio_request(reset->start, reset->name); - gpio_direction_output(reset->start, 0); + if (pdata->reset) { + gpio_request(pdata->reset, "cw1200_wlan_reset"); + gpio_direction_output(pdata->reset, 0); } - if (powerup) { - gpio_request(powerup->start, powerup->name); - gpio_direction_output(powerup->start, 0); + if (pdata->powerup) { + gpio_request(pdata->powerup, "cw1200_wlan_powerup"); + gpio_direction_output(pdata->powerup, 0); } - if (reset || powerup) + if (pdata->reset || pdata->powerup) msleep(10); /* Settle time? */ /* Enable 3v3 and 1v8 to hardware */ @@ -319,13 +314,13 @@ static int cw1200_spi_on(const struct cw1200_platform_data_spi *pdata) } /* Enable POWERUP signal */ - if (powerup) { - gpio_set_value(powerup->start, 1); + if (pdata->powerup) { + gpio_set_value(pdata->powerup, 1); msleep(250); /* or more..? */ } /* Enable RSTn signal */ - if (reset) { - gpio_set_value(reset->start, 1); + if (pdata->reset) { + gpio_set_value(pdata->reset, 1); msleep(50); /* Or more..? */ } return 0; diff --git a/include/linux/platform_data/cw1200_platform.h b/include/linux/platform_data/cw1200_platform.h index c168fa512347..4454c16ea3e5 100644 --- a/include/linux/platform_data/cw1200_platform.h +++ b/include/linux/platform_data/cw1200_platform.h @@ -14,8 +14,8 @@ struct cw1200_platform_data_spi { /* All others are optional */ bool have_5ghz; - const struct resource *reset; /* GPIO to RSTn signal */ - const struct resource *powerup; /* GPIO to POWERUP signal */ + int reset; /* GPIO to RSTn signal (0 disables) */ + int powerup; /* GPIO to POWERUP signal (0 disables) */ int (*power_ctrl)(const struct cw1200_platform_data_spi *pdata, bool enable); /* Control 3v3 / 1v8 supply */ int (*clk_ctrl)(const struct cw1200_platform_data_spi *pdata, @@ -28,11 +28,11 @@ struct cw1200_platform_data_sdio { u16 ref_clk; /* REQUIRED (in KHz) */ /* All others are optional */ - const struct resource *irq; /* if using GPIO for IRQ */ bool have_5ghz; - bool no_nptb; /* SDIO hardware does not support non-power-of-2-blocksizes */ - const struct resource *reset; /* GPIO to RSTn signal */ - const struct resource *powerup; /* GPIO to POWERUP signal */ + bool no_nptb; /* SDIO hardware does not support non-power-of-2-blocksizes */ + int reset; /* GPIO to RSTn signal (0 disables) */ + int powerup; /* GPIO to POWERUP signal (0 disables) */ + int irq; /* IRQ line or 0 to use SDIO IRQ */ int (*power_ctrl)(const struct cw1200_platform_data_sdio *pdata, bool enable); /* Control 3v3 / 1v8 supply */ int (*clk_ctrl)(const struct cw1200_platform_data_sdio *pdata, -- cgit v1.2.3 From 7c0b6f49dbea0b09b1de8aa5c942af9c2ad8b54c Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sun, 2 Jun 2013 11:35:31 -0400 Subject: cw1200: Rework SDIO platform support to prevent build problems. Based on discussions with And Bergmann, this patch changes the SDIO platform code to default to supporting the Sagrad devices, allowing for it to be overridden in board setup code. This renders the cw1200_sagrad module suplerflous, so it is now removed. It also moves the documentation that was in the cw1200_sagrad source to the platform header. Signed-off-by: Solomon Peachy Signed-off-by: John W. Linville --- drivers/net/wireless/cw1200/Kconfig | 17 ++--- drivers/net/wireless/cw1200/Makefile | 2 - drivers/net/wireless/cw1200/cw1200_sagrad.c | 106 -------------------------- drivers/net/wireless/cw1200/cw1200_sdio.c | 26 ++++++- include/linux/platform_data/cw1200_platform.h | 37 ++++++++- 5 files changed, 66 insertions(+), 122 deletions(-) delete mode 100644 drivers/net/wireless/cw1200/cw1200_sagrad.c (limited to 'include/linux/platform_data') diff --git a/drivers/net/wireless/cw1200/Kconfig b/drivers/net/wireless/cw1200/Kconfig index 13e36119ae9f..7a585d4e9c8e 100644 --- a/drivers/net/wireless/cw1200/Kconfig +++ b/drivers/net/wireless/cw1200/Kconfig @@ -13,20 +13,19 @@ config CW1200_WLAN_SDIO depends on CW1200 && MMC help Enable support for the CW1200 connected via an SDIO bus. + By default this driver only supports the Sagrad SG901-1091/1098 EVK + and similar designs that utilize a hardware reset circuit. To + support different CW1200 SDIO designs you will need to override + the default platform data by calling cw1200_sdio_set_platform_data() + in your board setup file. config CW1200_WLAN_SPI tristate "Support SPI platforms" depends on CW1200 && SPI help - Enables support for the CW1200 connected via a SPI bus. - -config CW1200_WLAN_SAGRAD - tristate "Support Sagrad SG901-1091/1098 modules" - depends on CW1200_WLAN_SDIO - help - This provides the platform data glue to support the - Sagrad SG901-1091/1098 modules in their standard SDIO EVK. - It also includes example SPI platform data. + Enables support for the CW1200 connected via a SPI bus. You will + need to add appropriate platform data glue in your board setup + file. menu "Driver debug features" depends on CW1200 && DEBUG_FS diff --git a/drivers/net/wireless/cw1200/Makefile b/drivers/net/wireless/cw1200/Makefile index 1aa3682066e0..bc6cbf91f26e 100644 --- a/drivers/net/wireless/cw1200/Makefile +++ b/drivers/net/wireless/cw1200/Makefile @@ -16,9 +16,7 @@ cw1200_core-$(CONFIG_PM) += pm.o cw1200_wlan_sdio-y := cw1200_sdio.o cw1200_wlan_spi-y := cw1200_spi.o -cw1200_wlan_sagrad-y := cw1200_sagrad.o obj-$(CONFIG_CW1200) += cw1200_core.o obj-$(CONFIG_CW1200_WLAN_SDIO) += cw1200_wlan_sdio.o obj-$(CONFIG_CW1200_WLAN_SPI) += cw1200_wlan_spi.o -obj-$(CONFIG_CW1200_WLAN_SAGRAD) += cw1200_wlan_sagrad.o diff --git a/drivers/net/wireless/cw1200/cw1200_sagrad.c b/drivers/net/wireless/cw1200/cw1200_sagrad.c deleted file mode 100644 index 3f884ac96ccc..000000000000 --- a/drivers/net/wireless/cw1200/cw1200_sagrad.c +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Platform glue data for ST-Ericsson CW1200 driver - * - * Copyright (c) 2013, Sagrad, Inc - * Author: Solomon Peachy - * - * 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 - -MODULE_AUTHOR("Solomon Peachy "); -MODULE_DESCRIPTION("ST-Ericsson CW1200 Platform glue driver"); -MODULE_LICENSE("GPL"); - -/* Define just one of these. Feel free to customize as needed */ -#define SAGRAD_1091_1098_EVK_SDIO -/* #define SAGRAD_1091_1098_EVK_SPI */ - -#ifdef SAGRAD_1091_1098_EVK_SDIO -static int cw1200_power_ctrl(const struct cw1200_platform_data_sdio *pdata, - bool enable) -{ - /* Control 3v3 and 1v8 to hardware as appropriate */ - /* Note this is not needed if it's controlled elsewhere or always on */ - - /* May require delay for power to stabilize */ - return 0; -} - -static int cw1200_clk_ctrl(const struct cw1200_platform_data_sdio *pdata, - bool enable) -{ - /* Turn CLK_32K off and on as appropriate. */ - /* Note this is not needed if it's always on */ - - /* May require delay for clock to stabilize */ - return 0; -} - -static struct cw1200_platform_data_sdio cw1200_platform_data = { - .ref_clk = 38400, - .have_5ghz = false, -#if 0 - .reset = GPIO_RF_RESET, /* Replace as appropriate */ - .powerup = GPIO_RF_POWERUP, /* Replace as appropriate */ - .irq = GPIO_TO_IRQ(GPIO_RF_IRQ), /* Replace as appropriate */ -#endif - .power_ctrl = cw1200_power_ctrl, - .clk_ctrl = cw1200_clk_ctrl, -/* .macaddr = ??? */ - .sdd_file = "sdd_sagrad_1091_1098.bin", -}; -#endif - -#ifdef SAGRAD_1091_1098_EVK_SPI -static int cw1200_power_ctrl(const struct cw1200_platform_data_spi *pdata, - bool enable) -{ - /* Control 3v3 and 1v8 to hardware as appropriate */ - /* Note this is not needed if it's controlled elsewhere or always on */ - - /* May require delay for power to stabilize */ - return 0; -} -static int cw1200_clk_ctrl(const struct cw1200_platform_data_spi *pdata, - bool enable) -{ - /* Turn CLK_32K off and on as appropriate. */ - /* Note this is not needed if it's always on */ - - /* May require delay for clock to stabilize */ - return 0; -} - -static struct cw1200_platform_data_spi cw1200_platform_data = { - .ref_clk = 38400, - .spi_bits_per_word = 16, - .reset = GPIO_RF_RESET, /* Replace as appropriate */ - .powerup = GPIO_RF_POWERUP, /* Replace as appropriate */ - .power_ctrl = cw1200_power_ctrl, - .clk_ctrl = cw1200_clk_ctrl, -/* .macaddr = ??? */ - .sdd_file = "sdd_sagrad_1091_1098.bin", -}; -static struct spi_board_info myboard_spi_devices[] __initdata = { - { - .modalias = "cw1200_wlan_spi", - .max_speed_hz = 10000000, /* 52MHz Max */ - .bus_num = 0, - .irq = WIFI_IRQ, - .platform_data = &cw1200_platform_data, - .chip_select = 0, - }, -}; -#endif - - -const void *cw1200_get_platform_data(void) -{ - return &cw1200_platform_data; -} -EXPORT_SYMBOL_GPL(cw1200_get_platform_data); diff --git a/drivers/net/wireless/cw1200/cw1200_sdio.c b/drivers/net/wireless/cw1200/cw1200_sdio.c index 574cf727567c..4b3148e47ee7 100644 --- a/drivers/net/wireless/cw1200/cw1200_sdio.c +++ b/drivers/net/wireless/cw1200/cw1200_sdio.c @@ -29,6 +29,21 @@ MODULE_LICENSE("GPL"); #define SDIO_BLOCK_SIZE (512) +/* Default platform data for Sagrad modules */ +static struct cw1200_platform_data_sdio sagrad_109x_evk_platform_data = { + .ref_clk = 38400, + .have_5ghz = false, + .sdd_file = "sdd_sagrad_1091_1098.bin", +}; + +/* Allow platform data to be overridden */ +static struct cw1200_platform_data_sdio *global_plat_data = &sagrad_109x_evk_platform_data; + +void __init cw1200_sdio_set_platform_data(struct cw1200_platform_data_sdio *pdata) +{ + global_plat_data = pdata; +} + struct hwbus_priv { struct sdio_func *func; struct cw1200_common *core; @@ -261,7 +276,7 @@ static struct hwbus_ops cw1200_sdio_hwbus_ops = { /* Probe Function to be called by SDIO stack when device is discovered */ static int cw1200_sdio_probe(struct sdio_func *func, - const struct sdio_device_id *id) + const struct sdio_device_id *id) { struct hwbus_priv *self; int status; @@ -280,7 +295,7 @@ static int cw1200_sdio_probe(struct sdio_func *func, func->card->quirks |= MMC_QUIRK_LENIENT_FN0; - self->pdata = cw1200_get_platform_data(); + self->pdata = global_plat_data; /* FIXME */ self->func = func; sdio_set_drvdata(func, self); sdio_claim_host(func); @@ -374,7 +389,8 @@ static int __init cw1200_sdio_init(void) const struct cw1200_platform_data_sdio *pdata; int ret; - pdata = cw1200_get_platform_data(); + /* FIXME -- this won't support multiple devices */ + pdata = global_plat_data; if (cw1200_sdio_on(pdata)) { ret = -1; @@ -396,7 +412,9 @@ err: static void __exit cw1200_sdio_exit(void) { const struct cw1200_platform_data_sdio *pdata; - pdata = cw1200_get_platform_data(); + + /* FIXME -- this won't support multiple devices */ + pdata = global_plat_data; sdio_unregister_driver(&sdio_driver); cw1200_sdio_off(pdata); } diff --git a/include/linux/platform_data/cw1200_platform.h b/include/linux/platform_data/cw1200_platform.h index 4454c16ea3e5..c6fbc3ce4ab0 100644 --- a/include/linux/platform_data/cw1200_platform.h +++ b/include/linux/platform_data/cw1200_platform.h @@ -41,6 +41,41 @@ struct cw1200_platform_data_sdio { const char *sdd_file; /* if NULL, will use default for detected hw type */ }; -const void *cw1200_get_platform_data(void); + +/* An example of SPI support in your board setup file: + + static struct cw1200_platform_data_spi cw1200_platform_data = { + .ref_clk = 38400, + .spi_bits_per_word = 16, + .reset = GPIO_RF_RESET, + .powerup = GPIO_RF_POWERUP, + .macaddr = wifi_mac_addr, + .sdd_file = "sdd_sagrad_1091_1098.bin", + }; + static struct spi_board_info myboard_spi_devices[] __initdata = { + { + .modalias = "cw1200_wlan_spi", + .max_speed_hz = 52000000, + .bus_num = 0, + .irq = WIFI_IRQ, + .platform_data = &cw1200_platform_data, + .chip_select = 0, + }, + }; + + */ + +/* An example of SDIO support in your board setup file: + + static struct cw1200_platform_data_sdio my_cw1200_platform_data = { + .ref_clk = 38400, + .have_5ghz = false, + .sdd_file = "sdd_myplatform.bin", + }; + cw1200_sdio_set_platform_data(&my_cw1200_platform_data); + + */ + +void __init cw1200_sdio_set_platform_data(struct cw1200_platform_data_sdio *pdata); #endif /* CW1200_PLAT_H_INCLUDED */ -- cgit v1.2.3 From 4da2a54a842db6921289e3e25b0739531a594dea Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sun, 2 Jun 2013 11:35:32 -0400 Subject: cw1200: rename the cw1200 platform definition header My previous patch just moved the file, but it also needed to be renamed to conform to proper conventions. Signed-off-by: Solomon Peachy Signed-off-by: John W. Linville --- drivers/net/wireless/cw1200/cw1200_sdio.c | 2 +- drivers/net/wireless/cw1200/cw1200_spi.c | 2 +- include/linux/platform_data/cw1200_platform.h | 81 --------------------------- include/linux/platform_data/net-cw1200.h | 81 +++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 83 deletions(-) delete mode 100644 include/linux/platform_data/cw1200_platform.h create mode 100644 include/linux/platform_data/net-cw1200.h (limited to 'include/linux/platform_data') diff --git a/drivers/net/wireless/cw1200/cw1200_sdio.c b/drivers/net/wireless/cw1200/cw1200_sdio.c index 4b3148e47ee7..bb1f405315e4 100644 --- a/drivers/net/wireless/cw1200/cw1200_sdio.c +++ b/drivers/net/wireless/cw1200/cw1200_sdio.c @@ -20,7 +20,7 @@ #include "cw1200.h" #include "hwbus.h" -#include +#include #include "hwio.h" MODULE_AUTHOR("Dmitry Tarnyagin "); diff --git a/drivers/net/wireless/cw1200/cw1200_spi.c b/drivers/net/wireless/cw1200/cw1200_spi.c index d8dc7c6bb96d..e58f0a5bafa9 100644 --- a/drivers/net/wireless/cw1200/cw1200_spi.c +++ b/drivers/net/wireless/cw1200/cw1200_spi.c @@ -25,7 +25,7 @@ #include "cw1200.h" #include "hwbus.h" -#include +#include #include "hwio.h" MODULE_AUTHOR("Solomon Peachy "); diff --git a/include/linux/platform_data/cw1200_platform.h b/include/linux/platform_data/cw1200_platform.h deleted file mode 100644 index c6fbc3ce4ab0..000000000000 --- a/include/linux/platform_data/cw1200_platform.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) ST-Ericsson SA 2011 - * - * Author: Dmitry Tarnyagin - * License terms: GNU General Public License (GPL) version 2 - */ - -#ifndef CW1200_PLAT_H_INCLUDED -#define CW1200_PLAT_H_INCLUDED - -struct cw1200_platform_data_spi { - u8 spi_bits_per_word; /* REQUIRED */ - u16 ref_clk; /* REQUIRED (in KHz) */ - - /* All others are optional */ - bool have_5ghz; - int reset; /* GPIO to RSTn signal (0 disables) */ - int powerup; /* GPIO to POWERUP signal (0 disables) */ - int (*power_ctrl)(const struct cw1200_platform_data_spi *pdata, - bool enable); /* Control 3v3 / 1v8 supply */ - int (*clk_ctrl)(const struct cw1200_platform_data_spi *pdata, - bool enable); /* Control CLK32K */ - const u8 *macaddr; /* if NULL, use cw1200_mac_template module parameter */ - const char *sdd_file; /* if NULL, will use default for detected hw type */ -}; - -struct cw1200_platform_data_sdio { - u16 ref_clk; /* REQUIRED (in KHz) */ - - /* All others are optional */ - bool have_5ghz; - bool no_nptb; /* SDIO hardware does not support non-power-of-2-blocksizes */ - int reset; /* GPIO to RSTn signal (0 disables) */ - int powerup; /* GPIO to POWERUP signal (0 disables) */ - int irq; /* IRQ line or 0 to use SDIO IRQ */ - int (*power_ctrl)(const struct cw1200_platform_data_sdio *pdata, - bool enable); /* Control 3v3 / 1v8 supply */ - int (*clk_ctrl)(const struct cw1200_platform_data_sdio *pdata, - bool enable); /* Control CLK32K */ - const u8 *macaddr; /* if NULL, use cw1200_mac_template module parameter */ - const char *sdd_file; /* if NULL, will use default for detected hw type */ -}; - - -/* An example of SPI support in your board setup file: - - static struct cw1200_platform_data_spi cw1200_platform_data = { - .ref_clk = 38400, - .spi_bits_per_word = 16, - .reset = GPIO_RF_RESET, - .powerup = GPIO_RF_POWERUP, - .macaddr = wifi_mac_addr, - .sdd_file = "sdd_sagrad_1091_1098.bin", - }; - static struct spi_board_info myboard_spi_devices[] __initdata = { - { - .modalias = "cw1200_wlan_spi", - .max_speed_hz = 52000000, - .bus_num = 0, - .irq = WIFI_IRQ, - .platform_data = &cw1200_platform_data, - .chip_select = 0, - }, - }; - - */ - -/* An example of SDIO support in your board setup file: - - static struct cw1200_platform_data_sdio my_cw1200_platform_data = { - .ref_clk = 38400, - .have_5ghz = false, - .sdd_file = "sdd_myplatform.bin", - }; - cw1200_sdio_set_platform_data(&my_cw1200_platform_data); - - */ - -void __init cw1200_sdio_set_platform_data(struct cw1200_platform_data_sdio *pdata); - -#endif /* CW1200_PLAT_H_INCLUDED */ diff --git a/include/linux/platform_data/net-cw1200.h b/include/linux/platform_data/net-cw1200.h new file mode 100644 index 000000000000..c6fbc3ce4ab0 --- /dev/null +++ b/include/linux/platform_data/net-cw1200.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Dmitry Tarnyagin + * License terms: GNU General Public License (GPL) version 2 + */ + +#ifndef CW1200_PLAT_H_INCLUDED +#define CW1200_PLAT_H_INCLUDED + +struct cw1200_platform_data_spi { + u8 spi_bits_per_word; /* REQUIRED */ + u16 ref_clk; /* REQUIRED (in KHz) */ + + /* All others are optional */ + bool have_5ghz; + int reset; /* GPIO to RSTn signal (0 disables) */ + int powerup; /* GPIO to POWERUP signal (0 disables) */ + int (*power_ctrl)(const struct cw1200_platform_data_spi *pdata, + bool enable); /* Control 3v3 / 1v8 supply */ + int (*clk_ctrl)(const struct cw1200_platform_data_spi *pdata, + bool enable); /* Control CLK32K */ + const u8 *macaddr; /* if NULL, use cw1200_mac_template module parameter */ + const char *sdd_file; /* if NULL, will use default for detected hw type */ +}; + +struct cw1200_platform_data_sdio { + u16 ref_clk; /* REQUIRED (in KHz) */ + + /* All others are optional */ + bool have_5ghz; + bool no_nptb; /* SDIO hardware does not support non-power-of-2-blocksizes */ + int reset; /* GPIO to RSTn signal (0 disables) */ + int powerup; /* GPIO to POWERUP signal (0 disables) */ + int irq; /* IRQ line or 0 to use SDIO IRQ */ + int (*power_ctrl)(const struct cw1200_platform_data_sdio *pdata, + bool enable); /* Control 3v3 / 1v8 supply */ + int (*clk_ctrl)(const struct cw1200_platform_data_sdio *pdata, + bool enable); /* Control CLK32K */ + const u8 *macaddr; /* if NULL, use cw1200_mac_template module parameter */ + const char *sdd_file; /* if NULL, will use default for detected hw type */ +}; + + +/* An example of SPI support in your board setup file: + + static struct cw1200_platform_data_spi cw1200_platform_data = { + .ref_clk = 38400, + .spi_bits_per_word = 16, + .reset = GPIO_RF_RESET, + .powerup = GPIO_RF_POWERUP, + .macaddr = wifi_mac_addr, + .sdd_file = "sdd_sagrad_1091_1098.bin", + }; + static struct spi_board_info myboard_spi_devices[] __initdata = { + { + .modalias = "cw1200_wlan_spi", + .max_speed_hz = 52000000, + .bus_num = 0, + .irq = WIFI_IRQ, + .platform_data = &cw1200_platform_data, + .chip_select = 0, + }, + }; + + */ + +/* An example of SDIO support in your board setup file: + + static struct cw1200_platform_data_sdio my_cw1200_platform_data = { + .ref_clk = 38400, + .have_5ghz = false, + .sdd_file = "sdd_myplatform.bin", + }; + cw1200_sdio_set_platform_data(&my_cw1200_platform_data); + + */ + +void __init cw1200_sdio_set_platform_data(struct cw1200_platform_data_sdio *pdata); + +#endif /* CW1200_PLAT_H_INCLUDED */ -- cgit v1.2.3 From 1237e598a94b5a44a0162a4f4534d18ef8a81a7d Mon Sep 17 00:00:00 2001 From: Philippe Begnic Date: Mon, 27 May 2013 14:41:29 +0200 Subject: clk: ux500: Pass clock base adresses in initcall for u8540 and u9540 Align on u8500 version, pass clock base address in clk_init functions for u8540 and u9540. Signed-off-by: Linus Walleij Signed-off-by: Philippe Begnic Reviewed-by: Ulf Hansson Signed-off-by: Mike Turquette --- arch/arm/mach-ux500/cpu.c | 6 ++++-- drivers/clk/ux500/u8540_clk.c | 4 ++-- drivers/clk/ux500/u9540_clk.c | 4 ++-- include/linux/platform_data/clk-ux500.h | 6 ++++-- 4 files changed, 12 insertions(+), 8 deletions(-) (limited to 'include/linux/platform_data') diff --git a/arch/arm/mach-ux500/cpu.c b/arch/arm/mach-ux500/cpu.c index b6145ea51641..e6fb0239151b 100644 --- a/arch/arm/mach-ux500/cpu.c +++ b/arch/arm/mach-ux500/cpu.c @@ -76,13 +76,15 @@ void __init ux500_init_irq(void) } else if (cpu_is_u9540()) { prcmu_early_init(U8500_PRCMU_BASE, SZ_8K - 1); ux500_pm_init(U8500_PRCMU_BASE, SZ_8K - 1); - u8500_clk_init(U8500_CLKRST1_BASE, U8500_CLKRST2_BASE, + u9540_clk_init(U8500_CLKRST1_BASE, U8500_CLKRST2_BASE, U8500_CLKRST3_BASE, U8500_CLKRST5_BASE, U8500_CLKRST6_BASE); } else if (cpu_is_u8540()) { prcmu_early_init(U8500_PRCMU_BASE, SZ_8K + SZ_4K - 1); ux500_pm_init(U8500_PRCMU_BASE, SZ_8K + SZ_4K - 1); - u8540_clk_init(); + u8540_clk_init(U8500_CLKRST1_BASE, U8500_CLKRST2_BASE, + U8500_CLKRST3_BASE, U8500_CLKRST5_BASE, + U8500_CLKRST6_BASE); } } diff --git a/drivers/clk/ux500/u8540_clk.c b/drivers/clk/ux500/u8540_clk.c index 10adfd2ead21..90f3c88694b9 100644 --- a/drivers/clk/ux500/u8540_clk.c +++ b/drivers/clk/ux500/u8540_clk.c @@ -12,10 +12,10 @@ #include #include #include - #include "clk.h" -void u8540_clk_init(void) +void u8540_clk_init(u32 clkrst1_base, u32 clkrst2_base, u32 clkrst3_base, + u32 clkrst5_base, u32 clkrst6_base) { /* register clocks here */ } diff --git a/drivers/clk/ux500/u9540_clk.c b/drivers/clk/ux500/u9540_clk.c index dbc0191e16c8..44794782e7e0 100644 --- a/drivers/clk/ux500/u9540_clk.c +++ b/drivers/clk/ux500/u9540_clk.c @@ -12,10 +12,10 @@ #include #include #include - #include "clk.h" -void u9540_clk_init(void) +void u9540_clk_init(u32 clkrst1_base, u32 clkrst2_base, u32 clkrst3_base, + u32 clkrst5_base, u32 clkrst6_base) { /* register clocks here */ } diff --git a/include/linux/platform_data/clk-ux500.h b/include/linux/platform_data/clk-ux500.h index 320d9c39ea0a..9d98f3aaa16c 100644 --- a/include/linux/platform_data/clk-ux500.h +++ b/include/linux/platform_data/clk-ux500.h @@ -12,7 +12,9 @@ void u8500_clk_init(u32 clkrst1_base, u32 clkrst2_base, u32 clkrst3_base, u32 clkrst5_base, u32 clkrst6_base); -void u9540_clk_init(void); -void u8540_clk_init(void); +void u9540_clk_init(u32 clkrst1_base, u32 clkrst2_base, u32 clkrst3_base, + u32 clkrst5_base, u32 clkrst6_base); +void u8540_clk_init(u32 clkrst1_base, u32 clkrst2_base, u32 clkrst3_base, + u32 clkrst5_base, u32 clkrst6_base); #endif /* __CLK_UX500_H */ -- cgit v1.2.3 From a3e509bb328287beba05017037e505bc53b62724 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Tue, 21 May 2013 18:49:58 +0200 Subject: input: mfd: ti_am335x_tsc remove remaining platform data pieces The two header files removed here are unused and have no users as this platform was never used with platform devices. Signed-off-by: Sebastian Andrzej Siewior --- include/linux/input/ti_am335x_tsc.h | 35 ----------------------------- include/linux/mfd/ti_am335x_tscadc.h | 5 ----- include/linux/platform_data/ti_am335x_adc.h | 14 ------------ 3 files changed, 54 deletions(-) delete mode 100644 include/linux/input/ti_am335x_tsc.h delete mode 100644 include/linux/platform_data/ti_am335x_adc.h (limited to 'include/linux/platform_data') diff --git a/include/linux/input/ti_am335x_tsc.h b/include/linux/input/ti_am335x_tsc.h deleted file mode 100644 index 6a66b4d1ac2c..000000000000 --- a/include/linux/input/ti_am335x_tsc.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef __LINUX_TI_AM335X_TSC_H -#define __LINUX_TI_AM335X_TSC_H - -/** - * struct tsc_data Touchscreen wire configuration - * @wires: Wires refer to application modes - * i.e. 4/5/8 wire touchscreen support - * on the platform. - * @x_plate_resistance: X plate resistance. - * @steps_to_configure: The sequencer supports a total of - * 16 programmable steps. - * A step configured to read a single - * co-ordinate value, can be applied - * more number of times for better results. - * @wire_config: Different EVM's could have a different order - * for connecting wires on touchscreen. - * We need to provide an 8 bit number where in - * the 1st four bits represent the analog lines - * and the next 4 bits represent positive/ - * negative terminal on that input line. - * Notations to represent the input lines and - * terminals resoectively is as follows: - * AIN0 = 0, AIN1 = 1 and so on till AIN7 = 7. - * XP = 0, XN = 1, YP = 2, YN = 3. - * - */ - -struct tsc_data { - int wires; - int x_plate_resistance; - int steps_to_configure; - int wire_config[10]; -}; - -#endif diff --git a/include/linux/mfd/ti_am335x_tscadc.h b/include/linux/mfd/ti_am335x_tscadc.h index fe54ba4a3b2f..533f200e6d0e 100644 --- a/include/linux/mfd/ti_am335x_tscadc.h +++ b/include/linux/mfd/ti_am335x_tscadc.h @@ -120,11 +120,6 @@ #define TSCADC_CELLS 2 -struct mfd_tscadc_board { - struct tsc_data *tsc_init; - struct adc_data *adc_init; -}; - struct ti_tscadc_dev { struct device *dev; struct regmap *regmap_tscadc; diff --git a/include/linux/platform_data/ti_am335x_adc.h b/include/linux/platform_data/ti_am335x_adc.h deleted file mode 100644 index e41d5834cb84..000000000000 --- a/include/linux/platform_data/ti_am335x_adc.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef __LINUX_TI_AM335X_ADC_H -#define __LINUX_TI_AM335X_ADC_H - -/** - * struct adc_data ADC Input information - * @adc_channels: Number of analog inputs - * available for ADC. - */ - -struct adc_data { - unsigned int adc_channels; -}; - -#endif -- cgit v1.2.3 From 0a085a9482fa51efb58c9d351ea98e83c5df93fc Mon Sep 17 00:00:00 2001 From: Chao Xie Date: Sun, 5 May 2013 20:24:58 -0700 Subject: Input: pxa27x-keypad - use matrix_keymap for matrix keys pxa27x-keypad includes matrix keys. Make use of matrix_keymap for the matrix keys. Signed-off-by: Chao Xie Signed-off-by: Dmitry Torokhov --- arch/arm/mach-mmp/aspenite.c | 10 +++-- arch/arm/mach-mmp/teton_bga.c | 8 +++- arch/arm/mach-pxa/em-x270.c | 20 +++++++--- arch/arm/mach-pxa/ezx.c | 60 ++++++++++++++++++++--------- arch/arm/mach-pxa/littleton.c | 10 +++-- arch/arm/mach-pxa/mainstone.c | 10 +++-- arch/arm/mach-pxa/mioa701.c | 11 ++++-- arch/arm/mach-pxa/palmld.c | 10 +++-- arch/arm/mach-pxa/palmt5.c | 10 +++-- arch/arm/mach-pxa/palmtreo.c | 23 +++++++---- arch/arm/mach-pxa/palmtx.c | 10 +++-- arch/arm/mach-pxa/palmz72.c | 10 +++-- arch/arm/mach-pxa/tavorevb.c | 10 +++-- arch/arm/mach-pxa/z2.c | 10 +++-- arch/arm/mach-pxa/zylonite.c | 10 +++-- drivers/input/keyboard/Kconfig | 1 + drivers/input/keyboard/pxa27x_keypad.c | 35 +++++++++++------ include/linux/platform_data/keypad-pxa27x.h | 3 +- 18 files changed, 180 insertions(+), 81 deletions(-) (limited to 'include/linux/platform_data') diff --git a/arch/arm/mach-mmp/aspenite.c b/arch/arm/mach-mmp/aspenite.c index 9f64d5632e07..1e233467fffa 100644 --- a/arch/arm/mach-mmp/aspenite.c +++ b/arch/arm/mach-mmp/aspenite.c @@ -205,7 +205,7 @@ struct pxa168fb_mach_info aspenite_lcd_info = { .invert_pixclock = 0, }; -static unsigned int aspenite_matrix_key_map[] = { +static const unsigned int aspenite_matrix_key_map[] = { KEY(0, 6, KEY_UP), /* SW 4 */ KEY(0, 7, KEY_DOWN), /* SW 5 */ KEY(1, 6, KEY_LEFT), /* SW 6 */ @@ -214,11 +214,15 @@ static unsigned int aspenite_matrix_key_map[] = { KEY(4, 7, KEY_ESC), /* SW 9 */ }; +static struct matrix_keymap_data aspenite_matrix_keymap_data = { + .keymap = aspenite_matrix_key_map, + .keymap_size = ARRAY_SIZE(aspenite_matrix_key_map), +}; + static struct pxa27x_keypad_platform_data aspenite_keypad_info __initdata = { .matrix_key_rows = 5, .matrix_key_cols = 8, - .matrix_key_map = aspenite_matrix_key_map, - .matrix_key_map_size = ARRAY_SIZE(aspenite_matrix_key_map), + .matrix_keymap_data = &aspenite_matrix_keymap_data, .debounce_interval = 30, }; diff --git a/arch/arm/mach-mmp/teton_bga.c b/arch/arm/mach-mmp/teton_bga.c index 8609967975ed..d8967fa48374 100644 --- a/arch/arm/mach-mmp/teton_bga.c +++ b/arch/arm/mach-mmp/teton_bga.c @@ -56,11 +56,15 @@ static unsigned int teton_bga_matrix_key_map[] = { KEY(1, 7, KEY_RIGHT), }; +static struct matrix_keymap_data teton_bga_matrix_keymap_data = { + .keymap = teton_bga_matrix_key_map, + .keymap_size = ARRAY_SIZE(teton_bga_matrix_key_map), +}; + static struct pxa27x_keypad_platform_data teton_bga_keypad_info __initdata = { .matrix_key_rows = 2, .matrix_key_cols = 8, - .matrix_key_map = teton_bga_matrix_key_map, - .matrix_key_map_size = ARRAY_SIZE(teton_bga_matrix_key_map), + .matrix_keymap_data = &teton_bga_matrix_keymap_data, .debounce_interval = 30, }; diff --git a/arch/arm/mach-pxa/em-x270.c b/arch/arm/mach-pxa/em-x270.c index 446563a7d1ad..f6726bb4eb95 100644 --- a/arch/arm/mach-pxa/em-x270.c +++ b/arch/arm/mach-pxa/em-x270.c @@ -833,21 +833,25 @@ static inline void em_x270_init_ac97(void) {} #endif #if defined(CONFIG_KEYBOARD_PXA27x) || defined(CONFIG_KEYBOARD_PXA27x_MODULE) -static unsigned int em_x270_module_matrix_keys[] = { +static const unsigned int em_x270_module_matrix_keys[] = { KEY(0, 0, KEY_A), KEY(1, 0, KEY_UP), KEY(2, 1, KEY_B), KEY(0, 2, KEY_LEFT), KEY(1, 1, KEY_ENTER), KEY(2, 0, KEY_RIGHT), KEY(0, 1, KEY_C), KEY(1, 2, KEY_DOWN), KEY(2, 2, KEY_D), }; +static struct matrix_keymap_data em_x270_matrix_keymap_data = { + .keymap = em_x270_module_matrix_keys, + .keymap_size = ARRAY_SIZE(em_x270_module_matrix_keys), +}; + struct pxa27x_keypad_platform_data em_x270_module_keypad_info = { /* code map for the matrix keys */ .matrix_key_rows = 3, .matrix_key_cols = 3, - .matrix_key_map = em_x270_module_matrix_keys, - .matrix_key_map_size = ARRAY_SIZE(em_x270_module_matrix_keys), + .matrix_keymap_data = &em_x270_matrix_keymap_data, }; -static unsigned int em_x270_exeda_matrix_keys[] = { +static const unsigned int em_x270_exeda_matrix_keys[] = { KEY(0, 0, KEY_RIGHTSHIFT), KEY(0, 1, KEY_RIGHTCTRL), KEY(0, 2, KEY_RIGHTALT), KEY(0, 3, KEY_SPACE), KEY(0, 4, KEY_LEFTALT), KEY(0, 5, KEY_LEFTCTRL), @@ -889,12 +893,16 @@ static unsigned int em_x270_exeda_matrix_keys[] = { KEY(7, 6, 0), KEY(7, 7, 0), }; +static struct matrix_keymap_data em_x270_exeda_matrix_keymap_data = { + .keymap = em_x270_exeda_matrix_keys, + .keymap_size = ARRAY_SIZE(em_x270_exeda_matrix_keys), +}; + struct pxa27x_keypad_platform_data em_x270_exeda_keypad_info = { /* code map for the matrix keys */ .matrix_key_rows = 8, .matrix_key_cols = 8, - .matrix_key_map = em_x270_exeda_matrix_keys, - .matrix_key_map_size = ARRAY_SIZE(em_x270_exeda_matrix_keys), + .matrix_keymap_data = &em_x270_exeda_matrix_keymap_data, }; static void __init em_x270_init_keypad(void) diff --git a/arch/arm/mach-pxa/ezx.c b/arch/arm/mach-pxa/ezx.c index dca10709be8f..fe2eb8394dff 100644 --- a/arch/arm/mach-pxa/ezx.c +++ b/arch/arm/mach-pxa/ezx.c @@ -392,7 +392,7 @@ static unsigned long e6_pin_config[] __initdata = { /* KEYPAD */ #ifdef CONFIG_MACH_EZX_A780 -static unsigned int a780_key_map[] = { +static const unsigned int a780_key_map[] = { KEY(0, 0, KEY_SEND), KEY(0, 1, KEY_BACK), KEY(0, 2, KEY_END), @@ -424,11 +424,15 @@ static unsigned int a780_key_map[] = { KEY(4, 4, KEY_DOWN), }; +static struct matrix_keymap_data a780_matrix_keymap_data = { + .keymap = a780_key_map, + .keymap_size = ARRAY_SIZE(a780_key_map), +}; + static struct pxa27x_keypad_platform_data a780_keypad_platform_data = { .matrix_key_rows = 5, .matrix_key_cols = 5, - .matrix_key_map = a780_key_map, - .matrix_key_map_size = ARRAY_SIZE(a780_key_map), + .matrix_keymap_data = &a780_matrix_keymap_data, .direct_key_map = { KEY_CAMERA }, .direct_key_num = 1, @@ -438,7 +442,7 @@ static struct pxa27x_keypad_platform_data a780_keypad_platform_data = { #endif /* CONFIG_MACH_EZX_A780 */ #ifdef CONFIG_MACH_EZX_E680 -static unsigned int e680_key_map[] = { +static const unsigned int e680_key_map[] = { KEY(0, 0, KEY_UP), KEY(0, 1, KEY_RIGHT), KEY(0, 2, KEY_RESERVED), @@ -455,11 +459,15 @@ static unsigned int e680_key_map[] = { KEY(2, 3, KEY_KPENTER), }; +static struct matrix_keymap_data e680_matrix_keymap_data = { + .keymap = e680_key_map, + .keymap_size = ARRAY_SIZE(e680_key_map), +}; + static struct pxa27x_keypad_platform_data e680_keypad_platform_data = { .matrix_key_rows = 3, .matrix_key_cols = 4, - .matrix_key_map = e680_key_map, - .matrix_key_map_size = ARRAY_SIZE(e680_key_map), + .matrix_keymap_data = &e680_matrix_keymap_data, .direct_key_map = { KEY_CAMERA, @@ -476,7 +484,7 @@ static struct pxa27x_keypad_platform_data e680_keypad_platform_data = { #endif /* CONFIG_MACH_EZX_E680 */ #ifdef CONFIG_MACH_EZX_A1200 -static unsigned int a1200_key_map[] = { +static const unsigned int a1200_key_map[] = { KEY(0, 0, KEY_RESERVED), KEY(0, 1, KEY_RIGHT), KEY(0, 2, KEY_PAGEDOWN), @@ -513,18 +521,22 @@ static unsigned int a1200_key_map[] = { KEY(4, 5, KEY_RESERVED), }; +static struct matrix_keymap_data a1200_matrix_keymap_data = { + .keymap = a1200_key_map, + .keymap_size = ARRAY_SIZE(a1200_key_map), +}; + static struct pxa27x_keypad_platform_data a1200_keypad_platform_data = { .matrix_key_rows = 5, .matrix_key_cols = 6, - .matrix_key_map = a1200_key_map, - .matrix_key_map_size = ARRAY_SIZE(a1200_key_map), + .matrix_keymap_data = &a1200_matrix_keymap_data, .debounce_interval = 30, }; #endif /* CONFIG_MACH_EZX_A1200 */ #ifdef CONFIG_MACH_EZX_E6 -static unsigned int e6_key_map[] = { +static const unsigned int e6_key_map[] = { KEY(0, 0, KEY_RESERVED), KEY(0, 1, KEY_RIGHT), KEY(0, 2, KEY_PAGEDOWN), @@ -561,18 +573,22 @@ static unsigned int e6_key_map[] = { KEY(4, 5, KEY_PREVIOUSSONG), }; +static struct matrix_keymap_data e6_keymap_data = { + .keymap = e6_key_map, + .keymap_size = ARRAY_SIZE(e6_key_map), +}; + static struct pxa27x_keypad_platform_data e6_keypad_platform_data = { .matrix_key_rows = 5, .matrix_key_cols = 6, - .matrix_key_map = e6_key_map, - .matrix_key_map_size = ARRAY_SIZE(e6_key_map), + .matrix_keymap_data = &e6_keymap_data, .debounce_interval = 30, }; #endif /* CONFIG_MACH_EZX_E6 */ #ifdef CONFIG_MACH_EZX_A910 -static unsigned int a910_key_map[] = { +static const unsigned int a910_key_map[] = { KEY(0, 0, KEY_NUMERIC_6), KEY(0, 1, KEY_RIGHT), KEY(0, 2, KEY_PAGEDOWN), @@ -609,18 +625,22 @@ static unsigned int a910_key_map[] = { KEY(4, 5, KEY_RESERVED), }; +static struct matrix_keymap_data a910_matrix_keymap_data = { + .keymap = a910_key_map, + .keymap_size = ARRAY_SIZE(a910_key_map), +}; + static struct pxa27x_keypad_platform_data a910_keypad_platform_data = { .matrix_key_rows = 5, .matrix_key_cols = 6, - .matrix_key_map = a910_key_map, - .matrix_key_map_size = ARRAY_SIZE(a910_key_map), + .matrix_keymap_data = &a910_matrix_keymap_data, .debounce_interval = 30, }; #endif /* CONFIG_MACH_EZX_A910 */ #ifdef CONFIG_MACH_EZX_E2 -static unsigned int e2_key_map[] = { +static const unsigned int e2_key_map[] = { KEY(0, 0, KEY_NUMERIC_6), KEY(0, 1, KEY_RIGHT), KEY(0, 2, KEY_NUMERIC_9), @@ -657,11 +677,15 @@ static unsigned int e2_key_map[] = { KEY(4, 5, KEY_RESERVED), }; +static struct matrix_keymap_data e2_matrix_keymap_data = { + .keymap = e2_key_map, + .keymap_size = ARRAY_SIZE(e2_key_map), +}; + static struct pxa27x_keypad_platform_data e2_keypad_platform_data = { .matrix_key_rows = 5, .matrix_key_cols = 6, - .matrix_key_map = e2_key_map, - .matrix_key_map_size = ARRAY_SIZE(e2_key_map), + .matrix_keymap_data = &e2_matrix_keymap_data, .debounce_interval = 30, }; diff --git a/arch/arm/mach-pxa/littleton.c b/arch/arm/mach-pxa/littleton.c index e848c4607baf..5d665588c7eb 100644 --- a/arch/arm/mach-pxa/littleton.c +++ b/arch/arm/mach-pxa/littleton.c @@ -222,7 +222,7 @@ static inline void littleton_init_spi(void) {} #endif #if defined(CONFIG_KEYBOARD_PXA27x) || defined(CONFIG_KEYBOARD_PXA27x_MODULE) -static unsigned int littleton_matrix_key_map[] = { +static const unsigned int littleton_matrix_key_map[] = { /* KEY(row, col, key_code) */ KEY(1, 3, KEY_0), KEY(0, 0, KEY_1), KEY(1, 0, KEY_2), KEY(2, 0, KEY_3), KEY(0, 1, KEY_4), KEY(1, 1, KEY_5), KEY(2, 1, KEY_6), KEY(0, 2, KEY_7), @@ -249,11 +249,15 @@ static unsigned int littleton_matrix_key_map[] = { KEY(3, 1, KEY_F23), /* soft2 */ }; +static struct matrix_keymap_data littleton_matrix_keymap_data = { + .keymap = littleton_matrix_key_map, + .keymap_size = ARRAY_SIZE(littleton_matrix_key_map), +}; + static struct pxa27x_keypad_platform_data littleton_keypad_info = { .matrix_key_rows = 6, .matrix_key_cols = 5, - .matrix_key_map = littleton_matrix_key_map, - .matrix_key_map_size = ARRAY_SIZE(littleton_matrix_key_map), + .matrix_keymap_data = &littleton_matrix_keymap_data, .enable_rotary0 = 1, .rotary0_up_key = KEY_UP, diff --git a/arch/arm/mach-pxa/mainstone.c b/arch/arm/mach-pxa/mainstone.c index 7a12c1ba90ff..d2c652318376 100644 --- a/arch/arm/mach-pxa/mainstone.c +++ b/arch/arm/mach-pxa/mainstone.c @@ -498,7 +498,7 @@ static struct pxaohci_platform_data mainstone_ohci_platform_data = { }; #if defined(CONFIG_KEYBOARD_PXA27x) || defined(CONFIG_KEYBOARD_PXA27x_MODULE) -static unsigned int mainstone_matrix_keys[] = { +static const unsigned int mainstone_matrix_keys[] = { KEY(0, 0, KEY_A), KEY(1, 0, KEY_B), KEY(2, 0, KEY_C), KEY(3, 0, KEY_D), KEY(4, 0, KEY_E), KEY(5, 0, KEY_F), KEY(0, 1, KEY_G), KEY(1, 1, KEY_H), KEY(2, 1, KEY_I), @@ -527,11 +527,15 @@ static unsigned int mainstone_matrix_keys[] = { KEY(4, 6, KEY_SELECT), }; +static struct matrix_keymap_data mainstone_matrix_keymap_data = { + .keymap = mainstone_matrix_keys, + .keymap_size = ARRAY_SIZE(mainstone_matrix_keys), +}; + struct pxa27x_keypad_platform_data mainstone_keypad_info = { .matrix_key_rows = 6, .matrix_key_cols = 7, - .matrix_key_map = mainstone_matrix_keys, - .matrix_key_map_size = ARRAY_SIZE(mainstone_matrix_keys), + .matrix_keymap_data = &mainstone_matrix_keymap_data, .enable_rotary0 = 1, .rotary0_up_key = KEY_UP, diff --git a/arch/arm/mach-pxa/mioa701.c b/arch/arm/mach-pxa/mioa701.c index f8979b943cbf..654b0ac84dea 100644 --- a/arch/arm/mach-pxa/mioa701.c +++ b/arch/arm/mach-pxa/mioa701.c @@ -222,7 +222,7 @@ static struct pxafb_mach_info mioa701_pxafb_info = { /* * Keyboard configuration */ -static unsigned int mioa701_matrix_keys[] = { +static const unsigned int mioa701_matrix_keys[] = { KEY(0, 0, KEY_UP), KEY(0, 1, KEY_RIGHT), KEY(0, 2, KEY_MEDIA), @@ -233,11 +233,16 @@ static unsigned int mioa701_matrix_keys[] = { KEY(2, 1, KEY_PHONE), /* Phone Green key */ KEY(2, 2, KEY_CAMERA) /* Camera key */ }; + +static struct matrix_keymap_data mioa701_matrix_keymap_data = { + .keymap = mioa701_matrix_keys, + .keymap_size = ARRAY_SIZE(mioa701_matrix_keys), +}; + static struct pxa27x_keypad_platform_data mioa701_keypad_info = { .matrix_key_rows = 3, .matrix_key_cols = 3, - .matrix_key_map = mioa701_matrix_keys, - .matrix_key_map_size = ARRAY_SIZE(mioa701_matrix_keys), + .matrix_keymap_data = &mioa701_matrix_keymap_data, }; /* diff --git a/arch/arm/mach-pxa/palmld.c b/arch/arm/mach-pxa/palmld.c index 909b713e5789..cf210b11ffcc 100644 --- a/arch/arm/mach-pxa/palmld.c +++ b/arch/arm/mach-pxa/palmld.c @@ -173,7 +173,7 @@ static inline void palmld_nor_init(void) {} * GPIO keyboard ******************************************************************************/ #if defined(CONFIG_KEYBOARD_PXA27x) || defined(CONFIG_KEYBOARD_PXA27x_MODULE) -static unsigned int palmld_matrix_keys[] = { +static const unsigned int palmld_matrix_keys[] = { KEY(0, 1, KEY_F2), KEY(0, 2, KEY_UP), @@ -190,11 +190,15 @@ static unsigned int palmld_matrix_keys[] = { KEY(3, 2, KEY_LEFT), }; +static struct matrix_keymap_data palmld_matrix_keymap_data = { + .keymap = palmld_matrix_keys, + .keymap_size = ARRAY_SIZE(palmld_matrix_keys), +}; + static struct pxa27x_keypad_platform_data palmld_keypad_platform_data = { .matrix_key_rows = 4, .matrix_key_cols = 3, - .matrix_key_map = palmld_matrix_keys, - .matrix_key_map_size = ARRAY_SIZE(palmld_matrix_keys), + .matrix_keymap_data = &palmld_matrix_keymap_data, .debounce_interval = 30, }; diff --git a/arch/arm/mach-pxa/palmt5.c b/arch/arm/mach-pxa/palmt5.c index 5033fd07968f..3ed9b029428b 100644 --- a/arch/arm/mach-pxa/palmt5.c +++ b/arch/arm/mach-pxa/palmt5.c @@ -108,7 +108,7 @@ static unsigned long palmt5_pin_config[] __initdata = { * GPIO keyboard ******************************************************************************/ #if defined(CONFIG_KEYBOARD_PXA27x) || defined(CONFIG_KEYBOARD_PXA27x_MODULE) -static unsigned int palmt5_matrix_keys[] = { +static const unsigned int palmt5_matrix_keys[] = { KEY(0, 0, KEY_POWER), KEY(0, 1, KEY_F1), KEY(0, 2, KEY_ENTER), @@ -124,11 +124,15 @@ static unsigned int palmt5_matrix_keys[] = { KEY(3, 2, KEY_LEFT), }; +static struct matrix_keymap_data palmt5_matrix_keymap_data = { + .keymap = palmt5_matrix_keys, + .keymap_size = ARRAY_SIZE(palmt5_matrix_keys), +}; + static struct pxa27x_keypad_platform_data palmt5_keypad_platform_data = { .matrix_key_rows = 4, .matrix_key_cols = 3, - .matrix_key_map = palmt5_matrix_keys, - .matrix_key_map_size = ARRAY_SIZE(palmt5_matrix_keys), + .matrix_keymap_data = &palmt5_matrix_keymap_data, .debounce_interval = 30, }; diff --git a/arch/arm/mach-pxa/palmtreo.c b/arch/arm/mach-pxa/palmtreo.c index d82a50b4a803..d8b937c870de 100644 --- a/arch/arm/mach-pxa/palmtreo.c +++ b/arch/arm/mach-pxa/palmtreo.c @@ -168,7 +168,7 @@ static unsigned long centro685_pin_config[] __initdata = { * GPIO keyboard ******************************************************************************/ #if IS_ENABLED(CONFIG_KEYBOARD_PXA27x) -static unsigned int treo680_matrix_keys[] = { +static const unsigned int treo680_matrix_keys[] = { KEY(0, 0, KEY_F8), /* Red/Off/Power */ KEY(0, 1, KEY_LEFT), KEY(0, 2, KEY_LEFTCTRL), /* Alternate */ @@ -227,7 +227,7 @@ static unsigned int treo680_matrix_keys[] = { KEY(7, 5, KEY_I), }; -static unsigned int centro_matrix_keys[] = { +static const unsigned int centro_matrix_keys[] = { KEY(0, 0, KEY_F9), /* Home */ KEY(0, 1, KEY_LEFT), KEY(0, 2, KEY_LEFTCTRL), /* Alternate */ @@ -286,11 +286,20 @@ static unsigned int centro_matrix_keys[] = { KEY(7, 5, KEY_I), }; +static struct matrix_keymap_data treo680_matrix_keymap_data = { + .keymap = treo680_matrix_keys, + .keymap_size = ARRAY_SIZE(treo680_matrix_keys), +}; + +static struct matrix_keymap_data centro_matrix_keymap_data = { + .keymap = centro_matrix_keys, + .keymap_size = ARRAY_SIZE(centro_matrix_keys), +}; + static struct pxa27x_keypad_platform_data treo680_keypad_pdata = { .matrix_key_rows = 8, .matrix_key_cols = 7, - .matrix_key_map = treo680_matrix_keys, - .matrix_key_map_size = ARRAY_SIZE(treo680_matrix_keys), + .matrix_keymap_data = &treo680_matrix_keymap_data, .direct_key_map = { KEY_CONNECT }, .direct_key_num = 1, @@ -301,10 +310,8 @@ static void __init palmtreo_kpc_init(void) { static struct pxa27x_keypad_platform_data *data = &treo680_keypad_pdata; - if (machine_is_centro()) { - data->matrix_key_map = centro_matrix_keys; - data->matrix_key_map_size = ARRAY_SIZE(centro_matrix_keys); - } + if (machine_is_centro()) + data->matrix_keymap_data = ¢ro_matrix_keymap_data; pxa_set_keypad_info(&treo680_keypad_pdata); } diff --git a/arch/arm/mach-pxa/palmtx.c b/arch/arm/mach-pxa/palmtx.c index 627c93a7364c..83f830dd8ad8 100644 --- a/arch/arm/mach-pxa/palmtx.c +++ b/arch/arm/mach-pxa/palmtx.c @@ -176,7 +176,7 @@ static inline void palmtx_nor_init(void) {} * GPIO keyboard ******************************************************************************/ #if defined(CONFIG_KEYBOARD_PXA27x) || defined(CONFIG_KEYBOARD_PXA27x_MODULE) -static unsigned int palmtx_matrix_keys[] = { +static const unsigned int palmtx_matrix_keys[] = { KEY(0, 0, KEY_POWER), KEY(0, 1, KEY_F1), KEY(0, 2, KEY_ENTER), @@ -192,11 +192,15 @@ static unsigned int palmtx_matrix_keys[] = { KEY(3, 2, KEY_LEFT), }; +static struct matrix_keymap_data palmtx_matrix_keymap_data = { + .keymap = palmtx_matrix_keys, + .keymap_size = ARRAY_SIZE(palmtx_matrix_keys), +}; + static struct pxa27x_keypad_platform_data palmtx_keypad_platform_data = { .matrix_key_rows = 4, .matrix_key_cols = 3, - .matrix_key_map = palmtx_matrix_keys, - .matrix_key_map_size = ARRAY_SIZE(palmtx_matrix_keys), + .matrix_keymap_data = &palmtx_matrix_keymap_data, .debounce_interval = 30, }; diff --git a/arch/arm/mach-pxa/palmz72.c b/arch/arm/mach-pxa/palmz72.c index 18b7fcd98592..1a35ddf218da 100644 --- a/arch/arm/mach-pxa/palmz72.c +++ b/arch/arm/mach-pxa/palmz72.c @@ -140,7 +140,7 @@ static unsigned long palmz72_pin_config[] __initdata = { * GPIO keyboard ******************************************************************************/ #if defined(CONFIG_KEYBOARD_PXA27x) || defined(CONFIG_KEYBOARD_PXA27x_MODULE) -static unsigned int palmz72_matrix_keys[] = { +static const unsigned int palmz72_matrix_keys[] = { KEY(0, 0, KEY_POWER), KEY(0, 1, KEY_F1), KEY(0, 2, KEY_ENTER), @@ -156,11 +156,15 @@ static unsigned int palmz72_matrix_keys[] = { KEY(3, 2, KEY_LEFT), }; +static struct matrix_keymap_data almz72_matrix_keymap_data = { + .keymap = palmz72_matrix_keys, + .keymap_size = ARRAY_SIZE(palmz72_matrix_keys), +}; + static struct pxa27x_keypad_platform_data palmz72_keypad_platform_data = { .matrix_key_rows = 4, .matrix_key_cols = 3, - .matrix_key_map = palmz72_matrix_keys, - .matrix_key_map_size = ARRAY_SIZE(palmz72_matrix_keys), + .matrix_keymap_data = &almz72_matrix_keymap_data, .debounce_interval = 30, }; diff --git a/arch/arm/mach-pxa/tavorevb.c b/arch/arm/mach-pxa/tavorevb.c index f55979c09a5f..4680efe55345 100644 --- a/arch/arm/mach-pxa/tavorevb.c +++ b/arch/arm/mach-pxa/tavorevb.c @@ -106,7 +106,7 @@ static struct platform_device smc91x_device = { }; #if defined(CONFIG_KEYBOARD_PXA27x) || defined(CONFIG_KEYBOARD_PXA27x_MODULE) -static unsigned int tavorevb_matrix_key_map[] = { +static const unsigned int tavorevb_matrix_key_map[] = { /* KEY(row, col, key_code) */ KEY(0, 4, KEY_A), KEY(0, 5, KEY_B), KEY(0, 6, KEY_C), KEY(1, 4, KEY_E), KEY(1, 5, KEY_F), KEY(1, 6, KEY_G), @@ -147,11 +147,15 @@ static unsigned int tavorevb_matrix_key_map[] = { KEY(3, 3, KEY_F23), /* soft2 */ }; +static struct matrix_keymap_data tavorevb_matrix_keymap_data = { + .keymap = tavorevb_matrix_key_map, + .keymap_size = ARRAY_SIZE(tavorevb_matrix_key_map), +}; + static struct pxa27x_keypad_platform_data tavorevb_keypad_info = { .matrix_key_rows = 7, .matrix_key_cols = 7, - .matrix_key_map = tavorevb_matrix_key_map, - .matrix_key_map_size = ARRAY_SIZE(tavorevb_matrix_key_map), + .matrix_keymap_data = &tavorevb_matrix_keymap_data, .debounce_interval = 30, }; diff --git a/arch/arm/mach-pxa/z2.c b/arch/arm/mach-pxa/z2.c index 989903a7e467..2513d8f4931f 100644 --- a/arch/arm/mach-pxa/z2.c +++ b/arch/arm/mach-pxa/z2.c @@ -345,7 +345,7 @@ static inline void z2_leds_init(void) {} * GPIO keyboard ******************************************************************************/ #if defined(CONFIG_KEYBOARD_PXA27x) || defined(CONFIG_KEYBOARD_PXA27x_MODULE) -static unsigned int z2_matrix_keys[] = { +static const unsigned int z2_matrix_keys[] = { KEY(0, 0, KEY_OPTION), KEY(1, 0, KEY_UP), KEY(2, 0, KEY_DOWN), @@ -405,11 +405,15 @@ static unsigned int z2_matrix_keys[] = { KEY(5, 7, KEY_DOT), }; +static struct matrix_keymap_data z2_matrix_keymap_data = { + .keymap = z2_matrix_keys, + .keymap_size = ARRAY_SIZE(z2_matrix_keys), +}; + static struct pxa27x_keypad_platform_data z2_keypad_platform_data = { .matrix_key_rows = 7, .matrix_key_cols = 8, - .matrix_key_map = z2_matrix_keys, - .matrix_key_map_size = ARRAY_SIZE(z2_matrix_keys), + .matrix_keymap_data = &z2_matrix_keymap_data, .debounce_interval = 30, }; diff --git a/arch/arm/mach-pxa/zylonite.c b/arch/arm/mach-pxa/zylonite.c index 1f00d650ac27..36cf7cf95ec1 100644 --- a/arch/arm/mach-pxa/zylonite.c +++ b/arch/arm/mach-pxa/zylonite.c @@ -263,7 +263,7 @@ static inline void zylonite_init_mmc(void) {} #endif #if defined(CONFIG_KEYBOARD_PXA27x) || defined(CONFIG_KEYBOARD_PXA27x_MODULE) -static unsigned int zylonite_matrix_key_map[] = { +static const unsigned int zylonite_matrix_key_map[] = { /* KEY(row, col, key_code) */ KEY(0, 0, KEY_A), KEY(0, 1, KEY_B), KEY(0, 2, KEY_C), KEY(0, 5, KEY_D), KEY(1, 0, KEY_E), KEY(1, 1, KEY_F), KEY(1, 2, KEY_G), KEY(1, 5, KEY_H), @@ -306,11 +306,15 @@ static unsigned int zylonite_matrix_key_map[] = { KEY(0, 3, KEY_AUX), /* contact */ }; +static struct matrix_keymap_data zylonite_matrix_keymap_data = { + .keymap = zylonite_matrix_key_map, + .keymap_size = ARRAY_SIZE(zylonite_matrix_key_map), +}; + static struct pxa27x_keypad_platform_data zylonite_keypad_info = { .matrix_key_rows = 8, .matrix_key_cols = 8, - .matrix_key_map = zylonite_matrix_key_map, - .matrix_key_map_size = ARRAY_SIZE(zylonite_matrix_key_map), + .matrix_keymap_data = &zylonite_matrix_keymap_data, .enable_rotary0 = 1, .rotary0_up_key = KEY_UP, diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 37c366623fc0..706e11bb6a32 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -451,6 +451,7 @@ config KEYBOARD_OPENCORES config KEYBOARD_PXA27x tristate "PXA27x/PXA3xx keypad support" depends on PXA27x || PXA3xx || ARCH_MMP + select INPUT_MATRIXKMAP help Enable support for PXA27x/PXA3xx keypad controller. diff --git a/drivers/input/keyboard/pxa27x_keypad.c b/drivers/input/keyboard/pxa27x_keypad.c index b674e7aca404..5b2d8764dd37 100644 --- a/drivers/input/keyboard/pxa27x_keypad.c +++ b/drivers/input/keyboard/pxa27x_keypad.c @@ -118,25 +118,30 @@ struct pxa27x_keypad { unsigned int direct_key_mask; }; -static void pxa27x_keypad_build_keycode(struct pxa27x_keypad *keypad) +static int pxa27x_keypad_build_keycode(struct pxa27x_keypad *keypad) { struct pxa27x_keypad_platform_data *pdata = keypad->pdata; struct input_dev *input_dev = keypad->input_dev; + const struct matrix_keymap_data *keymap_data = + pdata ? pdata->matrix_keymap_data : NULL; unsigned short keycode; int i; + int error; - for (i = 0; i < pdata->matrix_key_map_size; i++) { - unsigned int key = pdata->matrix_key_map[i]; - unsigned int row = KEY_ROW(key); - unsigned int col = KEY_COL(key); - unsigned int scancode = MATRIX_SCAN_CODE(row, col, - MATRIX_ROW_SHIFT); + error = matrix_keypad_build_keymap(keymap_data, NULL, + pdata->matrix_key_rows, + pdata->matrix_key_cols, + keypad->keycodes, input_dev); + if (error) + return error; - keycode = KEY_VAL(key); - keypad->keycodes[scancode] = keycode; - __set_bit(keycode, input_dev->keybit); - } + /* + * The keycodes may not only include matrix keys but also the direct + * or rotary keys. + */ + input_dev->keycodemax = ARRAY_SIZE(keypad->keycodes); + /* For direct keys. */ for (i = 0; i < pdata->direct_key_num; i++) { keycode = pdata->direct_key_map[i]; keypad->keycodes[MAX_MATRIX_KEY_NUM + i] = keycode; @@ -178,6 +183,8 @@ static void pxa27x_keypad_build_keycode(struct pxa27x_keypad *keypad) } __clear_bit(KEY_RESERVED, input_dev->keybit); + + return 0; } static void pxa27x_keypad_scan_matrix(struct pxa27x_keypad *keypad) @@ -555,7 +562,11 @@ static int pxa27x_keypad_probe(struct platform_device *pdev) input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); input_set_capability(input_dev, EV_MSC, MSC_SCAN); - pxa27x_keypad_build_keycode(keypad); + error = pxa27x_keypad_build_keycode(keypad); + if (error) { + dev_err(&pdev->dev, "failed to build keycode\n"); + goto failed_put_clk; + } if ((pdata->enable_rotary0 && keypad->rotary_rel_code[0] != -1) || (pdata->enable_rotary1 && keypad->rotary_rel_code[1] != -1)) { diff --git a/include/linux/platform_data/keypad-pxa27x.h b/include/linux/platform_data/keypad-pxa27x.h index 5ce8d5e6ea51..24625569d16d 100644 --- a/include/linux/platform_data/keypad-pxa27x.h +++ b/include/linux/platform_data/keypad-pxa27x.h @@ -36,10 +36,9 @@ struct pxa27x_keypad_platform_data { /* code map for the matrix keys */ + const struct matrix_keymap_data *matrix_keymap_data; unsigned int matrix_key_rows; unsigned int matrix_key_cols; - unsigned int *matrix_key_map; - int matrix_key_map_size; /* direct keys */ int direct_key_num; -- cgit v1.2.3 From 99b82abb0a35b07310ea6334257829af168c8e08 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Thu, 13 Jun 2013 18:54:44 +0200 Subject: pwm: Add Renesas TPU PWM driver The Timer Pulse Unit (TPU) is a 4-channels 16-bit timer used to generate waveforms. This driver exposes PWM functions through the PWM API for other drivers to use. The code is loosely based on the leds-renesas-tpu driver by Magnus Damm and the TPU PWM driver shipped in the Armadillo EVA 800 kernel sources. Signed-off-by: Laurent Pinchart Signed-off-by: Axel Lin Tested-by: Simon Horman Signed-off-by: Thierry Reding --- drivers/pwm/Kconfig | 10 + drivers/pwm/Makefile | 1 + drivers/pwm/pwm-renesas-tpu.c | 475 ++++++++++++++++++++++++++ include/linux/platform_data/pwm-renesas-tpu.h | 16 + 4 files changed, 502 insertions(+) create mode 100644 drivers/pwm/pwm-renesas-tpu.c create mode 100644 include/linux/platform_data/pwm-renesas-tpu.h (limited to 'include/linux/platform_data') diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 406a4d94ddb9..75840b5cea6d 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -128,6 +128,16 @@ config PWM_PXA To compile this driver as a module, choose M here: the module will be called pwm-pxa. +config PWM_RENESAS_TPU + tristate "Renesas TPU PWM support" + depends on ARCH_SHMOBILE + help + This driver exposes the Timer Pulse Unit (TPU) PWM controller found + in Renesas chips through the PWM API. + + To compile this driver as a module, choose M here: the module + will be called pwm-renesas-tpu. + config PWM_SAMSUNG tristate "Samsung PWM support" depends on PLAT_SAMSUNG diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 859692224044..77a8c185c5b2 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_PWM_MXS) += pwm-mxs.o obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o +obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o diff --git a/drivers/pwm/pwm-renesas-tpu.c b/drivers/pwm/pwm-renesas-tpu.c new file mode 100644 index 000000000000..96e0cc488a4e --- /dev/null +++ b/drivers/pwm/pwm-renesas-tpu.c @@ -0,0 +1,475 @@ +/* + * R-Mobile TPU PWM driver + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TPU_TSTR 0x00 /* Timer start register (shared) */ + +#define TPU_TCRn 0x00 /* Timer control register */ +#define TPU_TCR_CCLR_NONE (0 << 5) +#define TPU_TCR_CCLR_TGRA (1 << 5) +#define TPU_TCR_CCLR_TGRB (2 << 5) +#define TPU_TCR_CCLR_TGRC (5 << 5) +#define TPU_TCR_CCLR_TGRD (6 << 5) +#define TPU_TCR_CKEG_RISING (0 << 3) +#define TPU_TCR_CKEG_FALLING (1 << 3) +#define TPU_TCR_CKEG_BOTH (2 << 3) +#define TPU_TMDRn 0x04 /* Timer mode register */ +#define TPU_TMDR_BFWT (1 << 6) +#define TPU_TMDR_BFB (1 << 5) +#define TPU_TMDR_BFA (1 << 4) +#define TPU_TMDR_MD_NORMAL (0 << 0) +#define TPU_TMDR_MD_PWM (2 << 0) +#define TPU_TIORn 0x08 /* Timer I/O control register */ +#define TPU_TIOR_IOA_0 (0 << 0) +#define TPU_TIOR_IOA_0_CLR (1 << 0) +#define TPU_TIOR_IOA_0_SET (2 << 0) +#define TPU_TIOR_IOA_0_TOGGLE (3 << 0) +#define TPU_TIOR_IOA_1 (4 << 0) +#define TPU_TIOR_IOA_1_CLR (5 << 0) +#define TPU_TIOR_IOA_1_SET (6 << 0) +#define TPU_TIOR_IOA_1_TOGGLE (7 << 0) +#define TPU_TIERn 0x0c /* Timer interrupt enable register */ +#define TPU_TSRn 0x10 /* Timer status register */ +#define TPU_TCNTn 0x14 /* Timer counter */ +#define TPU_TGRAn 0x18 /* Timer general register A */ +#define TPU_TGRBn 0x1c /* Timer general register B */ +#define TPU_TGRCn 0x20 /* Timer general register C */ +#define TPU_TGRDn 0x24 /* Timer general register D */ + +#define TPU_CHANNEL_OFFSET 0x10 +#define TPU_CHANNEL_SIZE 0x40 + +enum tpu_pin_state { + TPU_PIN_INACTIVE, /* Pin is driven inactive */ + TPU_PIN_PWM, /* Pin is driven by PWM */ + TPU_PIN_ACTIVE, /* Pin is driven active */ +}; + +struct tpu_device; + +struct tpu_pwm_device { + bool timer_on; /* Whether the timer is running */ + + struct tpu_device *tpu; + unsigned int channel; /* Channel number in the TPU */ + + enum pwm_polarity polarity; + unsigned int prescaler; + u16 period; + u16 duty; +}; + +struct tpu_device { + struct platform_device *pdev; + struct tpu_pwm_platform_data *pdata; + struct pwm_chip chip; + spinlock_t lock; + + void __iomem *base; + struct clk *clk; +}; + +#define to_tpu_device(c) container_of(c, struct tpu_device, chip) + +static void tpu_pwm_write(struct tpu_pwm_device *pwm, int reg_nr, u16 value) +{ + void __iomem *base = pwm->tpu->base + TPU_CHANNEL_OFFSET + + pwm->channel * TPU_CHANNEL_SIZE; + + iowrite16(value, base + reg_nr); +} + +static void tpu_pwm_set_pin(struct tpu_pwm_device *pwm, + enum tpu_pin_state state) +{ + static const char * const states[] = { "inactive", "PWM", "active" }; + + dev_dbg(&pwm->tpu->pdev->dev, "%u: configuring pin as %s\n", + pwm->channel, states[state]); + + switch (state) { + case TPU_PIN_INACTIVE: + tpu_pwm_write(pwm, TPU_TIORn, + pwm->polarity == PWM_POLARITY_INVERSED ? + TPU_TIOR_IOA_1 : TPU_TIOR_IOA_0); + break; + case TPU_PIN_PWM: + tpu_pwm_write(pwm, TPU_TIORn, + pwm->polarity == PWM_POLARITY_INVERSED ? + TPU_TIOR_IOA_0_SET : TPU_TIOR_IOA_1_CLR); + break; + case TPU_PIN_ACTIVE: + tpu_pwm_write(pwm, TPU_TIORn, + pwm->polarity == PWM_POLARITY_INVERSED ? + TPU_TIOR_IOA_0 : TPU_TIOR_IOA_1); + break; + } +} + +static void tpu_pwm_start_stop(struct tpu_pwm_device *pwm, int start) +{ + unsigned long flags; + u16 value; + + spin_lock_irqsave(&pwm->tpu->lock, flags); + value = ioread16(pwm->tpu->base + TPU_TSTR); + + if (start) + value |= 1 << pwm->channel; + else + value &= ~(1 << pwm->channel); + + iowrite16(value, pwm->tpu->base + TPU_TSTR); + spin_unlock_irqrestore(&pwm->tpu->lock, flags); +} + +static int tpu_pwm_timer_start(struct tpu_pwm_device *pwm) +{ + int ret; + + if (!pwm->timer_on) { + /* Wake up device and enable clock. */ + pm_runtime_get_sync(&pwm->tpu->pdev->dev); + ret = clk_prepare_enable(pwm->tpu->clk); + if (ret) { + dev_err(&pwm->tpu->pdev->dev, "cannot enable clock\n"); + return ret; + } + pwm->timer_on = true; + } + + /* + * Make sure the channel is stopped, as we need to reconfigure it + * completely. First drive the pin to the inactive state to avoid + * glitches. + */ + tpu_pwm_set_pin(pwm, TPU_PIN_INACTIVE); + tpu_pwm_start_stop(pwm, false); + + /* + * - Clear TCNT on TGRB match + * - Count on rising edge + * - Set prescaler + * - Output 0 until TGRA, output 1 until TGRB (active low polarity) + * - Output 1 until TGRA, output 0 until TGRB (active high polarity + * - PWM mode + */ + tpu_pwm_write(pwm, TPU_TCRn, TPU_TCR_CCLR_TGRB | TPU_TCR_CKEG_RISING | + pwm->prescaler); + tpu_pwm_write(pwm, TPU_TMDRn, TPU_TMDR_MD_PWM); + tpu_pwm_set_pin(pwm, TPU_PIN_PWM); + tpu_pwm_write(pwm, TPU_TGRAn, pwm->duty); + tpu_pwm_write(pwm, TPU_TGRBn, pwm->period); + + dev_dbg(&pwm->tpu->pdev->dev, "%u: TGRA 0x%04x TGRB 0x%04x\n", + pwm->channel, pwm->duty, pwm->period); + + /* Start the channel. */ + tpu_pwm_start_stop(pwm, true); + + return 0; +} + +static void tpu_pwm_timer_stop(struct tpu_pwm_device *pwm) +{ + if (!pwm->timer_on) + return; + + /* Disable channel. */ + tpu_pwm_start_stop(pwm, false); + + /* Stop clock and mark device as idle. */ + clk_disable_unprepare(pwm->tpu->clk); + pm_runtime_put(&pwm->tpu->pdev->dev); + + pwm->timer_on = false; +} + +/* ----------------------------------------------------------------------------- + * PWM API + */ + +static int tpu_pwm_request(struct pwm_chip *chip, struct pwm_device *_pwm) +{ + struct tpu_device *tpu = to_tpu_device(chip); + struct tpu_pwm_device *pwm; + + if (_pwm->hwpwm >= TPU_CHANNEL_MAX) + return -EINVAL; + + pwm = kzalloc(sizeof(*pwm), GFP_KERNEL); + if (pwm == NULL) + return -ENOMEM; + + pwm->tpu = tpu; + pwm->channel = _pwm->hwpwm; + pwm->polarity = tpu->pdata ? tpu->pdata->channels[pwm->channel].polarity + : PWM_POLARITY_NORMAL; + pwm->prescaler = 0; + pwm->period = 0; + pwm->duty = 0; + + pwm->timer_on = false; + + pwm_set_chip_data(_pwm, pwm); + + return 0; +} + +static void tpu_pwm_free(struct pwm_chip *chip, struct pwm_device *_pwm) +{ + struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); + + tpu_pwm_timer_stop(pwm); + kfree(pwm); +} + +static int tpu_pwm_config(struct pwm_chip *chip, struct pwm_device *_pwm, + int duty_ns, int period_ns) +{ + static const unsigned int prescalers[] = { 1, 4, 16, 64 }; + struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); + struct tpu_device *tpu = to_tpu_device(chip); + unsigned int prescaler; + bool duty_only = false; + u32 clk_rate; + u32 period; + u32 duty; + int ret; + + /* + * Pick a prescaler to avoid overflowing the counter. + * TODO: Pick the highest acceptable prescaler. + */ + clk_rate = clk_get_rate(tpu->clk); + + for (prescaler = 0; prescaler < ARRAY_SIZE(prescalers); ++prescaler) { + period = clk_rate / prescalers[prescaler] + / (NSEC_PER_SEC / period_ns); + if (period <= 0xffff) + break; + } + + if (prescaler == ARRAY_SIZE(prescalers) || period == 0) { + dev_err(&tpu->pdev->dev, "clock rate mismatch\n"); + return -ENOTSUPP; + } + + if (duty_ns) { + duty = clk_rate / prescalers[prescaler] + / (NSEC_PER_SEC / duty_ns); + if (duty > period) + return -EINVAL; + } else { + duty = 0; + } + + dev_dbg(&tpu->pdev->dev, + "rate %u, prescaler %u, period %u, duty %u\n", + clk_rate, prescalers[prescaler], period, duty); + + if (pwm->prescaler == prescaler && pwm->period == period) + duty_only = true; + + pwm->prescaler = prescaler; + pwm->period = period; + pwm->duty = duty; + + /* If the channel is disabled we're done. */ + if (!test_bit(PWMF_ENABLED, &_pwm->flags)) + return 0; + + if (duty_only && pwm->timer_on) { + /* + * If only the duty cycle changed and the timer is already + * running, there's no need to reconfigure it completely, Just + * modify the duty cycle. + */ + tpu_pwm_write(pwm, TPU_TGRAn, pwm->duty); + dev_dbg(&tpu->pdev->dev, "%u: TGRA 0x%04x\n", pwm->channel, + pwm->duty); + } else { + /* Otherwise perform a full reconfiguration. */ + ret = tpu_pwm_timer_start(pwm); + if (ret < 0) + return ret; + } + + if (duty == 0 || duty == period) { + /* + * To avoid running the timer when not strictly required, handle + * 0% and 100% duty cycles as fixed levels and stop the timer. + */ + tpu_pwm_set_pin(pwm, duty ? TPU_PIN_ACTIVE : TPU_PIN_INACTIVE); + tpu_pwm_timer_stop(pwm); + } + + return 0; +} + +static int tpu_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *_pwm, + enum pwm_polarity polarity) +{ + struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); + + pwm->polarity = polarity; + + return 0; +} + +static int tpu_pwm_enable(struct pwm_chip *chip, struct pwm_device *_pwm) +{ + struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); + int ret; + + ret = tpu_pwm_timer_start(pwm); + if (ret < 0) + return ret; + + /* + * To avoid running the timer when not strictly required, handle 0% and + * 100% duty cycles as fixed levels and stop the timer. + */ + if (pwm->duty == 0 || pwm->duty == pwm->period) { + tpu_pwm_set_pin(pwm, pwm->duty ? + TPU_PIN_ACTIVE : TPU_PIN_INACTIVE); + tpu_pwm_timer_stop(pwm); + } + + return 0; +} + +static void tpu_pwm_disable(struct pwm_chip *chip, struct pwm_device *_pwm) +{ + struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); + + /* The timer must be running to modify the pin output configuration. */ + tpu_pwm_timer_start(pwm); + tpu_pwm_set_pin(pwm, TPU_PIN_INACTIVE); + tpu_pwm_timer_stop(pwm); +} + +static const struct pwm_ops tpu_pwm_ops = { + .request = tpu_pwm_request, + .free = tpu_pwm_free, + .config = tpu_pwm_config, + .set_polarity = tpu_pwm_set_polarity, + .enable = tpu_pwm_enable, + .disable = tpu_pwm_disable, + .owner = THIS_MODULE, +}; + +/* ----------------------------------------------------------------------------- + * Probe and remove + */ + +static int tpu_probe(struct platform_device *pdev) +{ + struct tpu_device *tpu; + struct resource *res; + int ret; + + tpu = devm_kzalloc(&pdev->dev, sizeof(*tpu), GFP_KERNEL); + if (tpu == NULL) { + dev_err(&pdev->dev, "failed to allocate driver data\n"); + return -ENOMEM; + } + + tpu->pdata = pdev->dev.platform_data; + + /* Map memory, get clock and pin control. */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get I/O memory\n"); + return -ENXIO; + } + + tpu->base = devm_ioremap_resource(&pdev->dev, res); + if (tpu->base == NULL) { + dev_err(&pdev->dev, "failed to remap I/O memory\n"); + return -ENXIO; + } + + tpu->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(tpu->clk)) { + dev_err(&pdev->dev, "cannot get clock\n"); + return PTR_ERR(tpu->clk); + } + + /* Initialize and register the device. */ + platform_set_drvdata(pdev, tpu); + + spin_lock_init(&tpu->lock); + tpu->pdev = pdev; + + tpu->chip.dev = &pdev->dev; + tpu->chip.ops = &tpu_pwm_ops; + tpu->chip.base = -1; + tpu->chip.npwm = TPU_CHANNEL_MAX; + + ret = pwmchip_add(&tpu->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register PWM chip\n"); + return ret; + } + + dev_info(&pdev->dev, "TPU PWM %d registered\n", tpu->pdev->id); + + pm_runtime_enable(&pdev->dev); + + return 0; +} + +static int tpu_remove(struct platform_device *pdev) +{ + struct tpu_device *tpu = platform_get_drvdata(pdev); + int ret; + + ret = pwmchip_remove(&tpu->chip); + if (ret) + return ret; + + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static struct platform_driver tpu_driver = { + .probe = tpu_probe, + .remove = tpu_remove, + .driver = { + .name = "renesas-tpu-pwm", + .owner = THIS_MODULE, + } +}; + +module_platform_driver(tpu_driver); + +MODULE_AUTHOR("Laurent Pinchart "); +MODULE_DESCRIPTION("Renesas TPU PWM Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/platform_data/pwm-renesas-tpu.h b/include/linux/platform_data/pwm-renesas-tpu.h new file mode 100644 index 000000000000..a7220b10ddab --- /dev/null +++ b/include/linux/platform_data/pwm-renesas-tpu.h @@ -0,0 +1,16 @@ +#ifndef __PWM_RENESAS_TPU_H__ +#define __PWM_RENESAS_TPU_H__ + +#include + +#define TPU_CHANNEL_MAX 4 + +struct tpu_pwm_channel_data { + enum pwm_polarity polarity; +}; + +struct tpu_pwm_platform_data { + struct tpu_pwm_channel_data channels[TPU_CHANNEL_MAX]; +}; + +#endif /* __PWM_RENESAS_TPU_H__ */ -- cgit v1.2.3 From 4bf8e1962f91eed5dbee168d2348983dda0a518f Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 19 Jun 2013 13:54:11 +0200 Subject: drm: Renesas R-Car Display Unit DRM driver The R-Car Display Unit (DU) DRM driver supports both superposition processors and all eight planes in RGB and YUV formats with alpha blending. Only VGA and LVDS encoders and connectors are currently supported. Signed-off-by: Laurent Pinchart Signed-off-by: Dave Airlie --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/rcar-du/Kconfig | 9 + drivers/gpu/drm/rcar-du/Makefile | 8 + drivers/gpu/drm/rcar-du/rcar_du_crtc.c | 595 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/rcar-du/rcar_du_crtc.h | 50 +++ drivers/gpu/drm/rcar-du/rcar_du_drv.c | 325 +++++++++++++++++ drivers/gpu/drm/rcar-du/rcar_du_drv.h | 66 ++++ drivers/gpu/drm/rcar-du/rcar_du_kms.c | 245 +++++++++++++ drivers/gpu/drm/rcar-du/rcar_du_kms.h | 59 ++++ drivers/gpu/drm/rcar-du/rcar_du_lvds.c | 216 ++++++++++++ drivers/gpu/drm/rcar-du/rcar_du_lvds.h | 24 ++ drivers/gpu/drm/rcar-du/rcar_du_plane.c | 507 +++++++++++++++++++++++++++ drivers/gpu/drm/rcar-du/rcar_du_plane.h | 67 ++++ drivers/gpu/drm/rcar-du/rcar_du_regs.h | 445 ++++++++++++++++++++++++ drivers/gpu/drm/rcar-du/rcar_du_vga.c | 149 ++++++++ drivers/gpu/drm/rcar-du/rcar_du_vga.h | 24 ++ include/linux/platform_data/rcar-du.h | 54 +++ 18 files changed, 2846 insertions(+) create mode 100644 drivers/gpu/drm/rcar-du/Kconfig create mode 100644 drivers/gpu/drm/rcar-du/Makefile create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_crtc.c create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_crtc.h create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_drv.c create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_drv.h create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_kms.c create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_kms.h create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_lvds.c create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_lvds.h create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_plane.c create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_plane.h create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_regs.h create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_vga.c create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_vga.h create mode 100644 include/linux/platform_data/rcar-du.h (limited to 'include/linux/platform_data') diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index b16c50ee769c..71ca63b79a4f 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -213,6 +213,8 @@ source "drivers/gpu/drm/mgag200/Kconfig" source "drivers/gpu/drm/cirrus/Kconfig" +source "drivers/gpu/drm/rcar-du/Kconfig" + source "drivers/gpu/drm/shmobile/Kconfig" source "drivers/gpu/drm/omapdrm/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 1ecbe5b7312d..801bcafa3028 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -49,6 +49,7 @@ obj-$(CONFIG_DRM_EXYNOS) +=exynos/ obj-$(CONFIG_DRM_GMA500) += gma500/ obj-$(CONFIG_DRM_UDL) += udl/ obj-$(CONFIG_DRM_AST) += ast/ +obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/ obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/ obj-$(CONFIG_DRM_OMAP) += omapdrm/ obj-$(CONFIG_DRM_TILCDC) += tilcdc/ diff --git a/drivers/gpu/drm/rcar-du/Kconfig b/drivers/gpu/drm/rcar-du/Kconfig new file mode 100644 index 000000000000..72887df8dd76 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/Kconfig @@ -0,0 +1,9 @@ +config DRM_RCAR_DU + tristate "DRM Support for R-Car Display Unit" + depends on DRM && ARM + select DRM_KMS_HELPER + select DRM_KMS_CMA_HELPER + select DRM_GEM_CMA_HELPER + help + Choose this option if you have an R-Car chipset. + If M is selected the module will be called rcar-du-drm. diff --git a/drivers/gpu/drm/rcar-du/Makefile b/drivers/gpu/drm/rcar-du/Makefile new file mode 100644 index 000000000000..7333c0094015 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/Makefile @@ -0,0 +1,8 @@ +rcar-du-drm-y := rcar_du_crtc.o \ + rcar_du_drv.o \ + rcar_du_kms.o \ + rcar_du_lvds.o \ + rcar_du_plane.o \ + rcar_du_vga.o + +obj-$(CONFIG_DRM_RCAR_DU) += rcar-du-drm.o diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c new file mode 100644 index 000000000000..24183fb93592 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c @@ -0,0 +1,595 @@ +/* + * rcar_du_crtc.c -- R-Car Display Unit CRTCs + * + * 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 "rcar_du_crtc.h" +#include "rcar_du_drv.h" +#include "rcar_du_kms.h" +#include "rcar_du_lvds.h" +#include "rcar_du_plane.h" +#include "rcar_du_regs.h" +#include "rcar_du_vga.h" + +#define to_rcar_crtc(c) container_of(c, struct rcar_du_crtc, crtc) + +static u32 rcar_du_crtc_read(struct rcar_du_crtc *rcrtc, u32 reg) +{ + struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private; + + return rcar_du_read(rcdu, rcrtc->mmio_offset + reg); +} + +static void rcar_du_crtc_write(struct rcar_du_crtc *rcrtc, u32 reg, u32 data) +{ + struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private; + + rcar_du_write(rcdu, rcrtc->mmio_offset + reg, data); +} + +static void rcar_du_crtc_clr(struct rcar_du_crtc *rcrtc, u32 reg, u32 clr) +{ + struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private; + + rcar_du_write(rcdu, rcrtc->mmio_offset + reg, + rcar_du_read(rcdu, rcrtc->mmio_offset + reg) & ~clr); +} + +static void rcar_du_crtc_set(struct rcar_du_crtc *rcrtc, u32 reg, u32 set) +{ + struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private; + + rcar_du_write(rcdu, rcrtc->mmio_offset + reg, + rcar_du_read(rcdu, rcrtc->mmio_offset + reg) | set); +} + +static void rcar_du_crtc_clr_set(struct rcar_du_crtc *rcrtc, u32 reg, + u32 clr, u32 set) +{ + struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private; + u32 value = rcar_du_read(rcdu, rcrtc->mmio_offset + reg); + + rcar_du_write(rcdu, rcrtc->mmio_offset + reg, (value & ~clr) | set); +} + +static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc) +{ + struct drm_crtc *crtc = &rcrtc->crtc; + struct rcar_du_device *rcdu = crtc->dev->dev_private; + const struct drm_display_mode *mode = &crtc->mode; + unsigned long clk; + u32 value; + u32 div; + + /* Dot clock */ + clk = clk_get_rate(rcdu->clock); + div = DIV_ROUND_CLOSEST(clk, mode->clock * 1000); + div = clamp(div, 1U, 64U) - 1; + + rcar_du_write(rcdu, rcrtc->index ? ESCR2 : ESCR, + ESCR_DCLKSEL_CLKS | div); + rcar_du_write(rcdu, rcrtc->index ? OTAR2 : OTAR, 0); + + /* Signal polarities */ + value = ((mode->flags & DRM_MODE_FLAG_PVSYNC) ? 0 : DSMR_VSL) + | ((mode->flags & DRM_MODE_FLAG_PHSYNC) ? 0 : DSMR_HSL) + | DSMR_DIPM_DE; + rcar_du_crtc_write(rcrtc, DSMR, value); + + /* Display timings */ + rcar_du_crtc_write(rcrtc, HDSR, mode->htotal - mode->hsync_start - 19); + rcar_du_crtc_write(rcrtc, HDER, mode->htotal - mode->hsync_start + + mode->hdisplay - 19); + rcar_du_crtc_write(rcrtc, HSWR, mode->hsync_end - + mode->hsync_start - 1); + rcar_du_crtc_write(rcrtc, HCR, mode->htotal - 1); + + rcar_du_crtc_write(rcrtc, VDSR, mode->vtotal - mode->vsync_end - 2); + rcar_du_crtc_write(rcrtc, VDER, mode->vtotal - mode->vsync_end + + mode->vdisplay - 2); + rcar_du_crtc_write(rcrtc, VSPR, mode->vtotal - mode->vsync_end + + mode->vsync_start - 1); + rcar_du_crtc_write(rcrtc, VCR, mode->vtotal - 1); + + rcar_du_crtc_write(rcrtc, DESR, mode->htotal - mode->hsync_start); + rcar_du_crtc_write(rcrtc, DEWR, mode->hdisplay); +} + +static void rcar_du_crtc_set_routing(struct rcar_du_crtc *rcrtc) +{ + struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private; + u32 dorcr = rcar_du_read(rcdu, DORCR); + + 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. + */ + if (rcrtc->outputs & (1 << 1) && rcrtc->index == 0) + dorcr |= DORCR_PG2D_DS1; + else + dorcr |= DORCR_PG2T | DORCR_DK2S | DORCR_PG2D_DS2; + + rcar_du_write(rcdu, DORCR, dorcr); +} + +static void __rcar_du_start_stop(struct rcar_du_device *rcdu, bool start) +{ + rcar_du_write(rcdu, DSYSR, + (rcar_du_read(rcdu, DSYSR) & ~(DSYSR_DRES | DSYSR_DEN)) | + (start ? DSYSR_DEN : DSYSR_DRES)); +} + +static void rcar_du_start_stop(struct rcar_du_device *rcdu, bool start) +{ + /* Many of the configuration bits are only updated when the display + * reset (DRES) bit in DSYSR is set to 1, disabling *both* CRTCs. Some + * of those bits could be pre-configured, but others (especially the + * bits related to plane assignment to display timing controllers) need + * to be modified at runtime. + * + * Restart the display controller if a start is requested. Sorry for the + * flicker. It should be possible to move most of the "DRES-update" bits + * setup to driver initialization time and minimize the number of cases + * when the display controller will have to be restarted. + */ + if (start) { + if (rcdu->used_crtcs++ != 0) + __rcar_du_start_stop(rcdu, false); + __rcar_du_start_stop(rcdu, true); + } else { + if (--rcdu->used_crtcs == 0) + __rcar_du_start_stop(rcdu, false); + } +} + +void rcar_du_crtc_route_output(struct drm_crtc *crtc, unsigned int output) +{ + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + + /* Store the route from the CRTC output to the DU output. The DU will be + * configured when starting the CRTC. + */ + rcrtc->outputs |= 1 << output; +} + +void rcar_du_crtc_update_planes(struct drm_crtc *crtc) +{ + struct rcar_du_device *rcdu = crtc->dev->dev_private; + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + struct rcar_du_plane *planes[RCAR_DU_NUM_HW_PLANES]; + unsigned int num_planes = 0; + unsigned int prio = 0; + unsigned int i; + u32 dptsr = 0; + u32 dspr = 0; + + for (i = 0; i < ARRAY_SIZE(rcdu->planes.planes); ++i) { + struct rcar_du_plane *plane = &rcdu->planes.planes[i]; + unsigned int j; + + if (plane->crtc != &rcrtc->crtc || !plane->enabled) + continue; + + /* Insert the plane in the sorted planes array. */ + for (j = num_planes++; j > 0; --j) { + if (planes[j-1]->zpos <= plane->zpos) + break; + planes[j] = planes[j-1]; + } + + planes[j] = plane; + prio += plane->format->planes * 4; + } + + for (i = 0; i < num_planes; ++i) { + struct rcar_du_plane *plane = planes[i]; + unsigned int index = plane->hwindex; + + prio -= 4; + dspr |= (index + 1) << prio; + dptsr |= DPTSR_PnDK(index) | DPTSR_PnTS(index); + + if (plane->format->planes == 2) { + index = (index + 1) % 8; + + prio -= 4; + dspr |= (index + 1) << prio; + dptsr |= DPTSR_PnDK(index) | DPTSR_PnTS(index); + } + } + + /* Select display timing and dot clock generator 2 for planes associated + * with superposition controller 2. + */ + if (rcrtc->index) { + u32 value = rcar_du_read(rcdu, DPTSR); + + /* The DPTSR register is updated when the display controller is + * stopped. We thus need to restart the DU. Once again, sorry + * for the flicker. One way to mitigate the issue would be to + * pre-associate planes with CRTCs (either with a fixed 4/4 + * split, or through a module parameter). Flicker would then + * occur only if we need to break the pre-association. + */ + if (value != dptsr) { + rcar_du_write(rcdu, DPTSR, dptsr); + if (rcdu->used_crtcs) { + __rcar_du_start_stop(rcdu, false); + __rcar_du_start_stop(rcdu, true); + } + } + } + + rcar_du_write(rcdu, rcrtc->index ? DS2PR : DS1PR, dspr); +} + +static void rcar_du_crtc_start(struct rcar_du_crtc *rcrtc) +{ + struct drm_crtc *crtc = &rcrtc->crtc; + struct rcar_du_device *rcdu = crtc->dev->dev_private; + unsigned int i; + + if (rcrtc->started) + return; + + if (WARN_ON(rcrtc->plane->format == NULL)) + return; + + /* Set display off and background to black */ + rcar_du_crtc_write(rcrtc, DOOR, DOOR_RGB(0, 0, 0)); + rcar_du_crtc_write(rcrtc, BPOR, BPOR_RGB(0, 0, 0)); + + /* Configure display timings and output routing */ + rcar_du_crtc_set_display_timing(rcrtc); + rcar_du_crtc_set_routing(rcrtc); + + mutex_lock(&rcdu->planes.lock); + rcrtc->plane->enabled = true; + rcar_du_crtc_update_planes(crtc); + mutex_unlock(&rcdu->planes.lock); + + /* Setup planes. */ + for (i = 0; i < ARRAY_SIZE(rcdu->planes.planes); ++i) { + struct rcar_du_plane *plane = &rcdu->planes.planes[i]; + + if (plane->crtc != crtc || !plane->enabled) + continue; + + rcar_du_plane_setup(plane); + } + + /* Select master sync mode. This enables display operation in master + * sync mode (with the HSYNC and VSYNC signals configured as outputs and + * actively driven). + */ + rcar_du_crtc_clr_set(rcrtc, DSYSR, DSYSR_TVM_MASK, DSYSR_TVM_MASTER); + + rcar_du_start_stop(rcdu, true); + + rcrtc->started = true; +} + +static void rcar_du_crtc_stop(struct rcar_du_crtc *rcrtc) +{ + struct drm_crtc *crtc = &rcrtc->crtc; + struct rcar_du_device *rcdu = crtc->dev->dev_private; + + if (!rcrtc->started) + return; + + mutex_lock(&rcdu->planes.lock); + rcrtc->plane->enabled = false; + rcar_du_crtc_update_planes(crtc); + mutex_unlock(&rcdu->planes.lock); + + /* Select switch sync mode. This stops display operation and configures + * the HSYNC and VSYNC signals as inputs. + */ + rcar_du_crtc_clr_set(rcrtc, DSYSR, DSYSR_TVM_MASK, DSYSR_TVM_SWITCH); + + rcar_du_start_stop(rcdu, false); + + rcrtc->started = false; +} + +void rcar_du_crtc_suspend(struct rcar_du_crtc *rcrtc) +{ + struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private; + + rcar_du_crtc_stop(rcrtc); + rcar_du_put(rcdu); +} + +void rcar_du_crtc_resume(struct rcar_du_crtc *rcrtc) +{ + struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private; + + if (rcrtc->dpms != DRM_MODE_DPMS_ON) + return; + + rcar_du_get(rcdu); + rcar_du_crtc_start(rcrtc); +} + +static void rcar_du_crtc_update_base(struct rcar_du_crtc *rcrtc) +{ + struct drm_crtc *crtc = &rcrtc->crtc; + + rcar_du_plane_compute_base(rcrtc->plane, crtc->fb); + rcar_du_plane_update_base(rcrtc->plane); +} + +static void rcar_du_crtc_dpms(struct drm_crtc *crtc, int mode) +{ + struct rcar_du_device *rcdu = crtc->dev->dev_private; + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + + if (rcrtc->dpms == mode) + return; + + if (mode == DRM_MODE_DPMS_ON) { + rcar_du_get(rcdu); + rcar_du_crtc_start(rcrtc); + } else { + rcar_du_crtc_stop(rcrtc); + rcar_du_put(rcdu); + } + + rcrtc->dpms = mode; +} + +static bool rcar_du_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* TODO Fixup modes */ + return true; +} + +static void rcar_du_crtc_mode_prepare(struct drm_crtc *crtc) +{ + struct rcar_du_device *rcdu = crtc->dev->dev_private; + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + + /* We need to access the hardware during mode set, acquire a reference + * to the DU. + */ + rcar_du_get(rcdu); + + /* Stop the CRTC and release the plane. Force the DPMS mode to off as a + * result. + */ + rcar_du_crtc_stop(rcrtc); + rcar_du_plane_release(rcrtc->plane); + + rcrtc->dpms = DRM_MODE_DPMS_OFF; +} + +static int rcar_du_crtc_mode_set(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, + int x, int y, + struct drm_framebuffer *old_fb) +{ + struct rcar_du_device *rcdu = crtc->dev->dev_private; + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + const struct rcar_du_format_info *format; + int ret; + + format = rcar_du_format_info(crtc->fb->pixel_format); + if (format == NULL) { + dev_dbg(rcdu->dev, "mode_set: unsupported format %08x\n", + crtc->fb->pixel_format); + ret = -EINVAL; + goto error; + } + + ret = rcar_du_plane_reserve(rcrtc->plane, format); + if (ret < 0) + goto error; + + rcrtc->plane->format = format; + rcrtc->plane->pitch = crtc->fb->pitches[0]; + + rcrtc->plane->src_x = x; + rcrtc->plane->src_y = y; + rcrtc->plane->width = mode->hdisplay; + rcrtc->plane->height = mode->vdisplay; + + rcar_du_plane_compute_base(rcrtc->plane, crtc->fb); + + rcrtc->outputs = 0; + + return 0; + +error: + /* There's no rollback/abort operation to clean up in case of error. We + * thus need to release the reference to the DU acquired in prepare() + * here. + */ + rcar_du_put(rcdu); + return ret; +} + +static void rcar_du_crtc_mode_commit(struct drm_crtc *crtc) +{ + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + + /* We're done, restart the CRTC and set the DPMS mode to on. The + * reference to the DU acquired at prepare() time will thus be released + * by the DPMS handler (possibly called by the disable() handler). + */ + rcar_du_crtc_start(rcrtc); + rcrtc->dpms = DRM_MODE_DPMS_ON; +} + +static int rcar_du_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + + rcrtc->plane->src_x = x; + rcrtc->plane->src_y = y; + + rcar_du_crtc_update_base(to_rcar_crtc(crtc)); + + return 0; +} + +static void rcar_du_crtc_disable(struct drm_crtc *crtc) +{ + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + + rcar_du_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); + rcar_du_plane_release(rcrtc->plane); +} + +static const struct drm_crtc_helper_funcs crtc_helper_funcs = { + .dpms = rcar_du_crtc_dpms, + .mode_fixup = rcar_du_crtc_mode_fixup, + .prepare = rcar_du_crtc_mode_prepare, + .commit = rcar_du_crtc_mode_commit, + .mode_set = rcar_du_crtc_mode_set, + .mode_set_base = rcar_du_crtc_mode_set_base, + .disable = rcar_du_crtc_disable, +}; + +void rcar_du_crtc_cancel_page_flip(struct rcar_du_crtc *rcrtc, + struct drm_file *file) +{ + struct drm_pending_vblank_event *event; + struct drm_device *dev = rcrtc->crtc.dev; + unsigned long flags; + + /* Destroy the pending vertical blanking event associated with the + * pending page flip, if any, and disable vertical blanking interrupts. + */ + spin_lock_irqsave(&dev->event_lock, flags); + event = rcrtc->event; + if (event && event->base.file_priv == file) { + rcrtc->event = NULL; + event->base.destroy(&event->base); + drm_vblank_put(dev, rcrtc->index); + } + spin_unlock_irqrestore(&dev->event_lock, flags); +} + +static void rcar_du_crtc_finish_page_flip(struct rcar_du_crtc *rcrtc) +{ + struct drm_pending_vblank_event *event; + struct drm_device *dev = rcrtc->crtc.dev; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + event = rcrtc->event; + rcrtc->event = NULL; + spin_unlock_irqrestore(&dev->event_lock, flags); + + if (event == NULL) + return; + + spin_lock_irqsave(&dev->event_lock, flags); + drm_send_vblank_event(dev, rcrtc->index, event); + spin_unlock_irqrestore(&dev->event_lock, flags); + + drm_vblank_put(dev, rcrtc->index); +} + +static int rcar_du_crtc_page_flip(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event) +{ + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + struct drm_device *dev = rcrtc->crtc.dev; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + if (rcrtc->event != NULL) { + spin_unlock_irqrestore(&dev->event_lock, flags); + return -EBUSY; + } + spin_unlock_irqrestore(&dev->event_lock, flags); + + crtc->fb = fb; + rcar_du_crtc_update_base(rcrtc); + + if (event) { + event->pipe = rcrtc->index; + drm_vblank_get(dev, rcrtc->index); + spin_lock_irqsave(&dev->event_lock, flags); + rcrtc->event = event; + spin_unlock_irqrestore(&dev->event_lock, flags); + } + + return 0; +} + +static const struct drm_crtc_funcs crtc_funcs = { + .destroy = drm_crtc_cleanup, + .set_config = drm_crtc_helper_set_config, + .page_flip = rcar_du_crtc_page_flip, +}; + +int rcar_du_crtc_create(struct rcar_du_device *rcdu, unsigned int index) +{ + struct rcar_du_crtc *rcrtc = &rcdu->crtcs[index]; + struct drm_crtc *crtc = &rcrtc->crtc; + int ret; + + rcrtc->mmio_offset = index ? DISP2_REG_OFFSET : 0; + rcrtc->index = index; + rcrtc->dpms = DRM_MODE_DPMS_OFF; + rcrtc->plane = &rcdu->planes.planes[index]; + + rcrtc->plane->crtc = crtc; + + ret = drm_crtc_init(rcdu->ddev, crtc, &crtc_funcs); + if (ret < 0) + return ret; + + drm_crtc_helper_add(crtc, &crtc_helper_funcs); + + return 0; +} + +void rcar_du_crtc_enable_vblank(struct rcar_du_crtc *rcrtc, bool enable) +{ + if (enable) { + rcar_du_crtc_write(rcrtc, DSRCR, DSRCR_VBCL); + rcar_du_crtc_set(rcrtc, DIER, DIER_VBE); + } else { + rcar_du_crtc_clr(rcrtc, DIER, DIER_VBE); + } +} + +void rcar_du_crtc_irq(struct rcar_du_crtc *rcrtc) +{ + u32 status; + + status = rcar_du_crtc_read(rcrtc, DSSR); + rcar_du_crtc_write(rcrtc, DSRCR, status & DSRCR_MASK); + + if (status & DSSR_VBK) { + drm_handle_vblank(rcrtc->crtc.dev, rcrtc->index); + rcar_du_crtc_finish_page_flip(rcrtc); + } +} diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h new file mode 100644 index 000000000000..2a0365bcbd14 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h @@ -0,0 +1,50 @@ +/* + * rcar_du_crtc.h -- R-Car Display Unit CRTCs + * + * 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 __RCAR_DU_CRTC_H__ +#define __RCAR_DU_CRTC_H__ + +#include + +#include +#include + +struct rcar_du_device; +struct rcar_du_plane; + +struct rcar_du_crtc { + struct drm_crtc crtc; + + unsigned int mmio_offset; + unsigned int index; + bool started; + + struct drm_pending_vblank_event *event; + unsigned int outputs; + int dpms; + + struct rcar_du_plane *plane; +}; + +int rcar_du_crtc_create(struct rcar_du_device *rcdu, unsigned int index); +void rcar_du_crtc_enable_vblank(struct rcar_du_crtc *rcrtc, bool enable); +void rcar_du_crtc_irq(struct rcar_du_crtc *rcrtc); +void rcar_du_crtc_cancel_page_flip(struct rcar_du_crtc *rcrtc, + struct drm_file *file); +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_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 new file mode 100644 index 000000000000..003b34ee38e3 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c @@ -0,0 +1,325 @@ +/* + * rcar_du_drv.c -- R-Car Display Unit DRM 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 +#include +#include + +#include "rcar_du_crtc.h" +#include "rcar_du_drv.h" +#include "rcar_du_kms.h" +#include "rcar_du_regs.h" + +/* ----------------------------------------------------------------------------- + * Core device operations + */ + +/* + * rcar_du_get - Acquire a reference to the DU + * + * Acquiring a reference enables the device clock and setup core registers. A + * reference must be held before accessing any hardware registers. + * + * This function must be called with the DRM mode_config lock held. + * + * Return 0 in case of success or a negative error code otherwise. + */ +int rcar_du_get(struct rcar_du_device *rcdu) +{ + int ret; + + if (rcdu->use_count) + goto done; + + /* Enable clocks before accessing the hardware. */ + ret = clk_prepare_enable(rcdu->clock); + if (ret < 0) + return ret; + + /* Enable extended features */ + rcar_du_write(rcdu, DEFR, DEFR_CODE | DEFR_DEFE); + rcar_du_write(rcdu, DEFR2, DEFR2_CODE | DEFR2_DEFE2G); + rcar_du_write(rcdu, DEFR3, DEFR3_CODE | DEFR3_DEFE3); + rcar_du_write(rcdu, DEFR4, DEFR4_CODE); + rcar_du_write(rcdu, DEFR5, DEFR5_CODE | DEFR5_DEFE5); + + /* Use DS1PR and DS2PR to configure planes priorities and connects the + * superposition 0 to DU0 pins. DU1 pins will be configured dynamically. + */ + rcar_du_write(rcdu, DORCR, DORCR_PG1D_DS1 | DORCR_DPRS); + +done: + rcdu->use_count++; + return 0; +} + +/* + * rcar_du_put - Release a reference to the DU + * + * Releasing the last reference disables the device clock. + * + * This function must be called with the DRM mode_config lock held. + */ +void rcar_du_put(struct rcar_du_device *rcdu) +{ + if (--rcdu->use_count) + return; + + clk_disable_unprepare(rcdu->clock); +} + +/* ----------------------------------------------------------------------------- + * DRM operations + */ + +static int rcar_du_unload(struct drm_device *dev) +{ + drm_kms_helper_poll_fini(dev); + drm_mode_config_cleanup(dev); + drm_vblank_cleanup(dev); + drm_irq_uninstall(dev); + + dev->dev_private = NULL; + + return 0; +} + +static int rcar_du_load(struct drm_device *dev, unsigned long flags) +{ + struct platform_device *pdev = dev->platformdev; + struct rcar_du_platform_data *pdata = pdev->dev.platform_data; + struct rcar_du_device *rcdu; + struct resource *ioarea; + struct resource *mem; + int ret; + + if (pdata == NULL) { + dev_err(dev->dev, "no platform data\n"); + return -ENODEV; + } + + rcdu = devm_kzalloc(&pdev->dev, sizeof(*rcdu), GFP_KERNEL); + if (rcdu == NULL) { + dev_err(dev->dev, "failed to allocate private data\n"); + return -ENOMEM; + } + + rcdu->dev = &pdev->dev; + rcdu->pdata = pdata; + rcdu->ddev = dev; + dev->dev_private = rcdu; + + /* I/O resources and clocks */ + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (mem == NULL) { + dev_err(&pdev->dev, "failed to get memory resource\n"); + return -EINVAL; + } + + ioarea = devm_request_mem_region(&pdev->dev, mem->start, + resource_size(mem), pdev->name); + if (ioarea == NULL) { + dev_err(&pdev->dev, "failed to request memory region\n"); + return -EBUSY; + } + + rcdu->mmio = devm_ioremap_nocache(&pdev->dev, ioarea->start, + resource_size(ioarea)); + if (rcdu->mmio == NULL) { + dev_err(&pdev->dev, "failed to remap memory resource\n"); + return -ENOMEM; + } + + rcdu->clock = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(rcdu->clock)) { + dev_err(&pdev->dev, "failed to get clock\n"); + return -ENOENT; + } + + /* DRM/KMS objects */ + ret = rcar_du_modeset_init(rcdu); + if (ret < 0) { + dev_err(&pdev->dev, "failed to initialize DRM/KMS\n"); + goto done; + } + + /* IRQ and vblank handling */ + ret = drm_vblank_init(dev, (1 << rcdu->num_crtcs) - 1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to initialize vblank\n"); + goto done; + } + + ret = drm_irq_install(dev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to install IRQ handler\n"); + goto done; + } + + platform_set_drvdata(pdev, rcdu); + +done: + if (ret) + rcar_du_unload(dev); + + return ret; +} + +static void rcar_du_preclose(struct drm_device *dev, struct drm_file *file) +{ + struct rcar_du_device *rcdu = dev->dev_private; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rcdu->crtcs); ++i) + rcar_du_crtc_cancel_page_flip(&rcdu->crtcs[i], file); +} + +static irqreturn_t rcar_du_irq(int irq, void *arg) +{ + struct drm_device *dev = arg; + struct rcar_du_device *rcdu = dev->dev_private; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rcdu->crtcs); ++i) + rcar_du_crtc_irq(&rcdu->crtcs[i]); + + return IRQ_HANDLED; +} + +static int rcar_du_enable_vblank(struct drm_device *dev, int crtc) +{ + struct rcar_du_device *rcdu = dev->dev_private; + + rcar_du_crtc_enable_vblank(&rcdu->crtcs[crtc], true); + + return 0; +} + +static void rcar_du_disable_vblank(struct drm_device *dev, int crtc) +{ + struct rcar_du_device *rcdu = dev->dev_private; + + rcar_du_crtc_enable_vblank(&rcdu->crtcs[crtc], false); +} + +static const struct file_operations rcar_du_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .poll = drm_poll, + .read = drm_read, + .fasync = drm_fasync, + .llseek = no_llseek, + .mmap = drm_gem_cma_mmap, +}; + +static struct drm_driver rcar_du_driver = { + .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET + | DRIVER_PRIME, + .load = rcar_du_load, + .unload = rcar_du_unload, + .preclose = rcar_du_preclose, + .irq_handler = rcar_du_irq, + .get_vblank_counter = drm_vblank_count, + .enable_vblank = rcar_du_enable_vblank, + .disable_vblank = rcar_du_disable_vblank, + .gem_free_object = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_import = drm_gem_cma_dmabuf_import, + .gem_prime_export = drm_gem_cma_dmabuf_export, + .dumb_create = drm_gem_cma_dumb_create, + .dumb_map_offset = drm_gem_cma_dumb_map_offset, + .dumb_destroy = drm_gem_cma_dumb_destroy, + .fops = &rcar_du_fops, + .name = "rcar-du", + .desc = "Renesas R-Car Display Unit", + .date = "20130110", + .major = 1, + .minor = 0, +}; + +/* ----------------------------------------------------------------------------- + * Power management + */ + +#if CONFIG_PM_SLEEP +static int rcar_du_pm_suspend(struct device *dev) +{ + struct rcar_du_device *rcdu = dev_get_drvdata(dev); + + drm_kms_helper_poll_disable(rcdu->ddev); + /* TODO Suspend the CRTC */ + + return 0; +} + +static int rcar_du_pm_resume(struct device *dev) +{ + struct rcar_du_device *rcdu = dev_get_drvdata(dev); + + /* TODO Resume the CRTC */ + + drm_kms_helper_poll_enable(rcdu->ddev); + return 0; +} +#endif + +static const struct dev_pm_ops rcar_du_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(rcar_du_pm_suspend, rcar_du_pm_resume) +}; + +/* ----------------------------------------------------------------------------- + * Platform driver + */ + +static int rcar_du_probe(struct platform_device *pdev) +{ + return drm_platform_init(&rcar_du_driver, pdev); +} + +static int rcar_du_remove(struct platform_device *pdev) +{ + drm_platform_exit(&rcar_du_driver, pdev); + + return 0; +} + +static struct platform_driver rcar_du_platform_driver = { + .probe = rcar_du_probe, + .remove = rcar_du_remove, + .driver = { + .owner = THIS_MODULE, + .name = "rcar-du", + .pm = &rcar_du_pm_ops, + }, +}; + +module_platform_driver(rcar_du_platform_driver); + +MODULE_AUTHOR("Laurent Pinchart "); +MODULE_DESCRIPTION("Renesas R-Car Display Unit DRM Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.h b/drivers/gpu/drm/rcar-du/rcar_du_drv.h new file mode 100644 index 000000000000..193cc59d495c --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.h @@ -0,0 +1,66 @@ +/* + * rcar_du_drv.h -- R-Car Display Unit DRM 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 __RCAR_DU_DRV_H__ +#define __RCAR_DU_DRV_H__ + +#include +#include +#include + +#include "rcar_du_crtc.h" +#include "rcar_du_plane.h" + +struct clk; +struct device; +struct drm_device; + +struct rcar_du_device { + struct device *dev; + const struct rcar_du_platform_data *pdata; + + void __iomem *mmio; + struct clk *clock; + unsigned int use_count; + + struct drm_device *ddev; + + struct rcar_du_crtc crtcs[2]; + unsigned int used_crtcs; + unsigned int num_crtcs; + + struct { + struct rcar_du_plane planes[RCAR_DU_NUM_SW_PLANES]; + unsigned int free; + struct mutex lock; + + struct drm_property *alpha; + struct drm_property *colorkey; + struct drm_property *zpos; + } planes; +}; + +int rcar_du_get(struct rcar_du_device *rcdu); +void rcar_du_put(struct rcar_du_device *rcdu); + +static inline u32 rcar_du_read(struct rcar_du_device *rcdu, u32 reg) +{ + return ioread32(rcdu->mmio + reg); +} + +static inline void rcar_du_write(struct rcar_du_device *rcdu, u32 reg, u32 data) +{ + iowrite32(data, rcdu->mmio + reg); +} + +#endif /* __RCAR_DU_DRV_H__ */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_kms.c b/drivers/gpu/drm/rcar-du/rcar_du_kms.c new file mode 100644 index 000000000000..9c63f39658de --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_kms.c @@ -0,0 +1,245 @@ +/* + * rcar_du_kms.c -- R-Car Display Unit Mode Setting + * + * 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 "rcar_du_crtc.h" +#include "rcar_du_drv.h" +#include "rcar_du_kms.h" +#include "rcar_du_lvds.h" +#include "rcar_du_regs.h" +#include "rcar_du_vga.h" + +/* ----------------------------------------------------------------------------- + * Format helpers + */ + +static const struct rcar_du_format_info rcar_du_format_infos[] = { + { + .fourcc = DRM_FORMAT_RGB565, + .bpp = 16, + .planes = 1, + .pnmr = PnMR_SPIM_TP | PnMR_DDDF_16BPP, + .edf = PnDDCR4_EDF_NONE, + }, { + .fourcc = DRM_FORMAT_ARGB1555, + .bpp = 16, + .planes = 1, + .pnmr = PnMR_SPIM_ALP | PnMR_DDDF_ARGB, + .edf = PnDDCR4_EDF_NONE, + }, { + .fourcc = DRM_FORMAT_XRGB1555, + .bpp = 16, + .planes = 1, + .pnmr = PnMR_SPIM_ALP | PnMR_DDDF_ARGB, + .edf = PnDDCR4_EDF_NONE, + }, { + .fourcc = DRM_FORMAT_XRGB8888, + .bpp = 32, + .planes = 1, + .pnmr = PnMR_SPIM_TP | PnMR_DDDF_16BPP, + .edf = PnDDCR4_EDF_RGB888, + }, { + .fourcc = DRM_FORMAT_ARGB8888, + .bpp = 32, + .planes = 1, + .pnmr = PnMR_SPIM_ALP | PnMR_DDDF_16BPP, + .edf = PnDDCR4_EDF_ARGB8888, + }, { + .fourcc = DRM_FORMAT_UYVY, + .bpp = 16, + .planes = 1, + .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC, + .edf = PnDDCR4_EDF_NONE, + }, { + .fourcc = DRM_FORMAT_YUYV, + .bpp = 16, + .planes = 1, + .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC, + .edf = PnDDCR4_EDF_NONE, + }, { + .fourcc = DRM_FORMAT_NV12, + .bpp = 12, + .planes = 2, + .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC, + .edf = PnDDCR4_EDF_NONE, + }, { + .fourcc = DRM_FORMAT_NV21, + .bpp = 12, + .planes = 2, + .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC, + .edf = PnDDCR4_EDF_NONE, + }, { + /* In YUV 4:2:2, only NV16 is supported (NV61 isn't) */ + .fourcc = DRM_FORMAT_NV16, + .bpp = 16, + .planes = 2, + .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC, + .edf = PnDDCR4_EDF_NONE, + }, +}; + +const struct rcar_du_format_info *rcar_du_format_info(u32 fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rcar_du_format_infos); ++i) { + if (rcar_du_format_infos[i].fourcc == fourcc) + return &rcar_du_format_infos[i]; + } + + return NULL; +} + +/* ----------------------------------------------------------------------------- + * Common connector and encoder functions + */ + +struct drm_encoder * +rcar_du_connector_best_encoder(struct drm_connector *connector) +{ + struct rcar_du_connector *rcon = to_rcar_connector(connector); + + return &rcon->encoder->encoder; +} + +void rcar_du_encoder_mode_prepare(struct drm_encoder *encoder) +{ +} + +void rcar_du_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct rcar_du_encoder *renc = to_rcar_encoder(encoder); + + rcar_du_crtc_route_output(encoder->crtc, renc->output); +} + +void rcar_du_encoder_mode_commit(struct drm_encoder *encoder) +{ +} + +/* ----------------------------------------------------------------------------- + * Frame buffer + */ + +static struct drm_framebuffer * +rcar_du_fb_create(struct drm_device *dev, struct drm_file *file_priv, + struct drm_mode_fb_cmd2 *mode_cmd) +{ + const struct rcar_du_format_info *format; + + format = rcar_du_format_info(mode_cmd->pixel_format); + if (format == NULL) { + dev_dbg(dev->dev, "unsupported pixel format %08x\n", + mode_cmd->pixel_format); + return ERR_PTR(-EINVAL); + } + + if (mode_cmd->pitches[0] & 15 || mode_cmd->pitches[0] >= 8192) { + dev_dbg(dev->dev, "invalid pitch value %u\n", + mode_cmd->pitches[0]); + return ERR_PTR(-EINVAL); + } + + if (format->planes == 2) { + if (mode_cmd->pitches[1] != mode_cmd->pitches[0]) { + dev_dbg(dev->dev, + "luma and chroma pitches do not match\n"); + return ERR_PTR(-EINVAL); + } + } + + return drm_fb_cma_create(dev, file_priv, mode_cmd); +} + +static const struct drm_mode_config_funcs rcar_du_mode_config_funcs = { + .fb_create = rcar_du_fb_create, +}; + +int rcar_du_modeset_init(struct rcar_du_device *rcdu) +{ + struct drm_device *dev = rcdu->ddev; + struct drm_encoder *encoder; + unsigned int i; + int ret; + + drm_mode_config_init(rcdu->ddev); + + rcdu->ddev->mode_config.min_width = 0; + rcdu->ddev->mode_config.min_height = 0; + rcdu->ddev->mode_config.max_width = 4095; + rcdu->ddev->mode_config.max_height = 2047; + rcdu->ddev->mode_config.funcs = &rcar_du_mode_config_funcs; + + ret = rcar_du_plane_init(rcdu); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(rcdu->crtcs); ++i) + rcar_du_crtc_create(rcdu, i); + + rcdu->used_crtcs = 0; + rcdu->num_crtcs = i; + + for (i = 0; i < rcdu->pdata->num_encoders; ++i) { + const struct rcar_du_encoder_data *pdata = + &rcdu->pdata->encoders[i]; + + if (pdata->output >= ARRAY_SIZE(rcdu->crtcs)) { + dev_warn(rcdu->dev, + "encoder %u references unexisting output %u, skipping\n", + i, pdata->output); + continue; + } + + switch (pdata->encoder) { + case RCAR_DU_ENCODER_VGA: + rcar_du_vga_init(rcdu, &pdata->u.vga, pdata->output); + break; + + case RCAR_DU_ENCODER_LVDS: + rcar_du_lvds_init(rcdu, &pdata->u.lvds, pdata->output); + break; + + default: + break; + } + } + + /* 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. + */ + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + struct rcar_du_encoder *renc = to_rcar_encoder(encoder); + + encoder->possible_crtcs = (1 << 0) | (1 << renc->output); + encoder->possible_clones = 1 << 0; + } + + ret = rcar_du_plane_register(rcdu); + if (ret < 0) + return ret; + + drm_kms_helper_poll_init(rcdu->ddev); + + drm_helper_disable_unused_functions(rcdu->ddev); + + return 0; +} diff --git a/drivers/gpu/drm/rcar-du/rcar_du_kms.h b/drivers/gpu/drm/rcar-du/rcar_du_kms.h new file mode 100644 index 000000000000..e4d8db069a06 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_kms.h @@ -0,0 +1,59 @@ +/* + * rcar_du_kms.h -- R-Car Display Unit Mode Setting + * + * 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 __RCAR_DU_KMS_H__ +#define __RCAR_DU_KMS_H__ + +#include + +#include + +struct rcar_du_device; + +struct rcar_du_format_info { + u32 fourcc; + unsigned int bpp; + unsigned int planes; + unsigned int pnmr; + unsigned int edf; +}; + +struct rcar_du_encoder { + struct drm_encoder encoder; + unsigned int output; +}; + +#define to_rcar_encoder(e) \ + container_of(e, struct rcar_du_encoder, encoder) + +struct rcar_du_connector { + struct drm_connector connector; + struct rcar_du_encoder *encoder; +}; + +#define to_rcar_connector(c) \ + container_of(c, struct rcar_du_connector, connector) + +const struct rcar_du_format_info *rcar_du_format_info(u32 fourcc); + +struct drm_encoder * +rcar_du_connector_best_encoder(struct drm_connector *connector); +void rcar_du_encoder_mode_prepare(struct drm_encoder *encoder); +void rcar_du_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode); +void rcar_du_encoder_mode_commit(struct drm_encoder *encoder); + +int rcar_du_modeset_init(struct rcar_du_device *rcdu); + +#endif /* __RCAR_DU_KMS_H__ */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvds.c b/drivers/gpu/drm/rcar-du/rcar_du_lvds.c new file mode 100644 index 000000000000..7aefe7267e1d --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_lvds.c @@ -0,0 +1,216 @@ +/* + * rcar_du_lvds.c -- R-Car Display Unit LVDS Encoder and Connector + * + * 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 "rcar_du_drv.h" +#include "rcar_du_kms.h" +#include "rcar_du_lvds.h" + +struct rcar_du_lvds_connector { + struct rcar_du_connector connector; + + const struct rcar_du_panel_data *panel; +}; + +#define to_rcar_lvds_connector(c) \ + container_of(c, struct rcar_du_lvds_connector, connector.connector) + +/* ----------------------------------------------------------------------------- + * Connector + */ + +static int rcar_du_lvds_connector_get_modes(struct drm_connector *connector) +{ + struct rcar_du_lvds_connector *lvdscon = to_rcar_lvds_connector(connector); + struct drm_display_mode *mode; + + mode = drm_mode_create(connector->dev); + if (mode == NULL) + return 0; + + mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER; + mode->clock = lvdscon->panel->mode.clock; + mode->hdisplay = lvdscon->panel->mode.hdisplay; + mode->hsync_start = lvdscon->panel->mode.hsync_start; + mode->hsync_end = lvdscon->panel->mode.hsync_end; + mode->htotal = lvdscon->panel->mode.htotal; + mode->vdisplay = lvdscon->panel->mode.vdisplay; + mode->vsync_start = lvdscon->panel->mode.vsync_start; + mode->vsync_end = lvdscon->panel->mode.vsync_end; + mode->vtotal = lvdscon->panel->mode.vtotal; + mode->flags = lvdscon->panel->mode.flags; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + return 1; +} + +static int rcar_du_lvds_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static const struct drm_connector_helper_funcs connector_helper_funcs = { + .get_modes = rcar_du_lvds_connector_get_modes, + .mode_valid = rcar_du_lvds_connector_mode_valid, + .best_encoder = rcar_du_connector_best_encoder, +}; + +static void rcar_du_lvds_connector_destroy(struct drm_connector *connector) +{ + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); +} + +static enum drm_connector_status +rcar_du_lvds_connector_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static const struct drm_connector_funcs connector_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = rcar_du_lvds_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = rcar_du_lvds_connector_destroy, +}; + +static int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu, + struct rcar_du_encoder *renc, + const struct rcar_du_panel_data *panel) +{ + struct rcar_du_lvds_connector *lvdscon; + struct drm_connector *connector; + int ret; + + lvdscon = devm_kzalloc(rcdu->dev, sizeof(*lvdscon), GFP_KERNEL); + if (lvdscon == NULL) + return -ENOMEM; + + lvdscon->panel = panel; + + connector = &lvdscon->connector.connector; + connector->display_info.width_mm = panel->width_mm; + connector->display_info.height_mm = panel->height_mm; + + ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs, + DRM_MODE_CONNECTOR_LVDS); + if (ret < 0) + return ret; + + drm_connector_helper_add(connector, &connector_helper_funcs); + ret = drm_sysfs_connector_add(connector); + if (ret < 0) + return ret; + + drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF); + drm_object_property_set_value(&connector->base, + rcdu->ddev->mode_config.dpms_property, DRM_MODE_DPMS_OFF); + + ret = drm_mode_connector_attach_encoder(connector, &renc->encoder); + if (ret < 0) + return ret; + + connector->encoder = &renc->encoder; + lvdscon->connector.encoder = renc; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Encoder + */ + +static void rcar_du_lvds_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +} + +static bool rcar_du_lvds_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + const struct drm_display_mode *panel_mode; + struct drm_device *dev = encoder->dev; + struct drm_connector *connector; + bool found = false; + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (connector->encoder == encoder) { + found = true; + break; + } + } + + if (!found) { + dev_dbg(dev->dev, "mode_fixup: no connector found\n"); + return false; + } + + if (list_empty(&connector->modes)) { + dev_dbg(dev->dev, "mode_fixup: empty modes list\n"); + return false; + } + + panel_mode = list_first_entry(&connector->modes, + struct drm_display_mode, head); + + /* We're not allowed to modify the resolution. */ + if (mode->hdisplay != panel_mode->hdisplay || + mode->vdisplay != panel_mode->vdisplay) + return false; + + /* The flat panel mode is fixed, just copy it to the adjusted mode. */ + drm_mode_copy(adjusted_mode, panel_mode); + + return true; +} + +static const struct drm_encoder_helper_funcs encoder_helper_funcs = { + .dpms = rcar_du_lvds_encoder_dpms, + .mode_fixup = rcar_du_lvds_encoder_mode_fixup, + .prepare = rcar_du_encoder_mode_prepare, + .commit = rcar_du_encoder_mode_commit, + .mode_set = rcar_du_encoder_mode_set, +}; + +static const struct drm_encoder_funcs encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +int rcar_du_lvds_init(struct rcar_du_device *rcdu, + const struct rcar_du_encoder_lvds_data *data, + unsigned int output) +{ + struct rcar_du_encoder *renc; + int ret; + + renc = devm_kzalloc(rcdu->dev, sizeof(*renc), GFP_KERNEL); + if (renc == NULL) + return -ENOMEM; + + renc->output = output; + + ret = drm_encoder_init(rcdu->ddev, &renc->encoder, &encoder_funcs, + DRM_MODE_ENCODER_LVDS); + if (ret < 0) + return ret; + + drm_encoder_helper_add(&renc->encoder, &encoder_helper_funcs); + + return rcar_du_lvds_connector_init(rcdu, renc, &data->panel); +} diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvds.h b/drivers/gpu/drm/rcar-du/rcar_du_lvds.h new file mode 100644 index 000000000000..b47f8328e103 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_lvds.h @@ -0,0 +1,24 @@ +/* + * rcar_du_lvds.h -- R-Car Display Unit LVDS Encoder and Connector + * + * 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 __RCAR_DU_LVDS_H__ +#define __RCAR_DU_LVDS_H__ + +struct rcar_du_device; +struct rcar_du_encoder_lvds_data; + +int rcar_du_lvds_init(struct rcar_du_device *rcdu, + const struct rcar_du_encoder_lvds_data *data, + unsigned int output); + +#endif /* __RCAR_DU_LVDS_H__ */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_plane.c b/drivers/gpu/drm/rcar-du/rcar_du_plane.c new file mode 100644 index 000000000000..a65f81ddf51d --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_plane.c @@ -0,0 +1,507 @@ +/* + * rcar_du_plane.c -- R-Car Display Unit Planes + * + * 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 "rcar_du_drv.h" +#include "rcar_du_kms.h" +#include "rcar_du_plane.h" +#include "rcar_du_regs.h" + +#define RCAR_DU_COLORKEY_NONE (0 << 24) +#define RCAR_DU_COLORKEY_SOURCE (1 << 24) +#define RCAR_DU_COLORKEY_MASK (1 << 24) + +struct rcar_du_kms_plane { + struct drm_plane plane; + struct rcar_du_plane *hwplane; +}; + +static inline struct rcar_du_plane *to_rcar_plane(struct drm_plane *plane) +{ + return container_of(plane, struct rcar_du_kms_plane, plane)->hwplane; +} + +static u32 rcar_du_plane_read(struct rcar_du_device *rcdu, + unsigned int index, u32 reg) +{ + return rcar_du_read(rcdu, index * PLANE_OFF + reg); +} + +static void rcar_du_plane_write(struct rcar_du_device *rcdu, + unsigned int index, u32 reg, u32 data) +{ + rcar_du_write(rcdu, index * PLANE_OFF + reg, data); +} + +int rcar_du_plane_reserve(struct rcar_du_plane *plane, + const struct rcar_du_format_info *format) +{ + struct rcar_du_device *rcdu = plane->dev; + unsigned int i; + int ret = -EBUSY; + + mutex_lock(&rcdu->planes.lock); + + for (i = 0; i < ARRAY_SIZE(rcdu->planes.planes); ++i) { + if (!(rcdu->planes.free & (1 << i))) + continue; + + if (format->planes == 1 || + rcdu->planes.free & (1 << ((i + 1) % 8))) + break; + } + + if (i == ARRAY_SIZE(rcdu->planes.planes)) + goto done; + + rcdu->planes.free &= ~(1 << i); + if (format->planes == 2) + rcdu->planes.free &= ~(1 << ((i + 1) % 8)); + + plane->hwindex = i; + + ret = 0; + +done: + mutex_unlock(&rcdu->planes.lock); + return ret; +} + +void rcar_du_plane_release(struct rcar_du_plane *plane) +{ + struct rcar_du_device *rcdu = plane->dev; + + if (plane->hwindex == -1) + return; + + mutex_lock(&rcdu->planes.lock); + rcdu->planes.free |= 1 << plane->hwindex; + if (plane->format->planes == 2) + rcdu->planes.free |= 1 << ((plane->hwindex + 1) % 8); + mutex_unlock(&rcdu->planes.lock); + + plane->hwindex = -1; +} + +void rcar_du_plane_update_base(struct rcar_du_plane *plane) +{ + struct rcar_du_device *rcdu = plane->dev; + unsigned int index = plane->hwindex; + + /* According to the datasheet the Y position is expressed in raster line + * units. However, 32bpp formats seem to require a doubled Y position + * value. Similarly, for the second plane, NV12 and NV21 formats seem to + * require a halved Y position value. + */ + rcar_du_plane_write(rcdu, index, PnSPXR, plane->src_x); + rcar_du_plane_write(rcdu, index, PnSPYR, plane->src_y * + (plane->format->bpp == 32 ? 2 : 1)); + rcar_du_plane_write(rcdu, index, PnDSA0R, plane->dma[0]); + + if (plane->format->planes == 2) { + index = (index + 1) % 8; + + rcar_du_plane_write(rcdu, index, PnSPXR, plane->src_x); + rcar_du_plane_write(rcdu, index, PnSPYR, plane->src_y * + (plane->format->bpp == 16 ? 2 : 1) / 2); + rcar_du_plane_write(rcdu, index, PnDSA0R, plane->dma[1]); + } +} + +void rcar_du_plane_compute_base(struct rcar_du_plane *plane, + struct drm_framebuffer *fb) +{ + struct drm_gem_cma_object *gem; + + gem = drm_fb_cma_get_gem_obj(fb, 0); + plane->dma[0] = gem->paddr + fb->offsets[0]; + + if (plane->format->planes == 2) { + gem = drm_fb_cma_get_gem_obj(fb, 1); + plane->dma[1] = gem->paddr + fb->offsets[1]; + } +} + +static void rcar_du_plane_setup_mode(struct rcar_du_plane *plane, + unsigned int index) +{ + struct rcar_du_device *rcdu = plane->dev; + u32 colorkey; + u32 pnmr; + + /* The PnALPHAR register controls alpha-blending in 16bpp formats + * (ARGB1555 and XRGB1555). + * + * For ARGB, set the alpha value to 0, and enable alpha-blending when + * the A bit is 0. This maps A=0 to alpha=0 and A=1 to alpha=255. + * + * For XRGB, set the alpha value to the plane-wide alpha value and + * enable alpha-blending regardless of the X bit value. + */ + if (plane->format->fourcc != DRM_FORMAT_XRGB1555) + rcar_du_plane_write(rcdu, index, PnALPHAR, PnALPHAR_ABIT_0); + else + rcar_du_plane_write(rcdu, index, PnALPHAR, + PnALPHAR_ABIT_X | plane->alpha); + + pnmr = PnMR_BM_MD | plane->format->pnmr; + + /* Disable color keying when requested. YUV formats have the + * PnMR_SPIM_TP_OFF bit set in their pnmr field, disabling color keying + * automatically. + */ + if ((plane->colorkey & RCAR_DU_COLORKEY_MASK) == RCAR_DU_COLORKEY_NONE) + pnmr |= PnMR_SPIM_TP_OFF; + + /* For packed YUV formats we need to select the U/V order. */ + if (plane->format->fourcc == DRM_FORMAT_YUYV) + pnmr |= PnMR_YCDF_YUYV; + + rcar_du_plane_write(rcdu, index, PnMR, pnmr); + + switch (plane->format->fourcc) { + case DRM_FORMAT_RGB565: + colorkey = ((plane->colorkey & 0xf80000) >> 8) + | ((plane->colorkey & 0x00fc00) >> 5) + | ((plane->colorkey & 0x0000f8) >> 3); + rcar_du_plane_write(rcdu, index, PnTC2R, colorkey); + break; + + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_XRGB1555: + colorkey = ((plane->colorkey & 0xf80000) >> 9) + | ((plane->colorkey & 0x00f800) >> 6) + | ((plane->colorkey & 0x0000f8) >> 3); + rcar_du_plane_write(rcdu, index, PnTC2R, colorkey); + break; + + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + rcar_du_plane_write(rcdu, index, PnTC3R, + PnTC3R_CODE | (plane->colorkey & 0xffffff)); + break; + } +} + +static void __rcar_du_plane_setup(struct rcar_du_plane *plane, + unsigned int index) +{ + struct rcar_du_device *rcdu = plane->dev; + u32 ddcr2 = PnDDCR2_CODE; + u32 ddcr4; + u32 mwr; + + /* Data format + * + * The data format is selected by the DDDF field in PnMR and the EDF + * field in DDCR4. + */ + ddcr4 = rcar_du_plane_read(rcdu, index, PnDDCR4); + ddcr4 &= ~PnDDCR4_EDF_MASK; + ddcr4 |= plane->format->edf | PnDDCR4_CODE; + + rcar_du_plane_setup_mode(plane, index); + + if (plane->format->planes == 2) { + if (plane->hwindex != index) { + if (plane->format->fourcc == DRM_FORMAT_NV12 || + plane->format->fourcc == DRM_FORMAT_NV21) + ddcr2 |= PnDDCR2_Y420; + + if (plane->format->fourcc == DRM_FORMAT_NV21) + ddcr2 |= PnDDCR2_NV21; + + ddcr2 |= PnDDCR2_DIVU; + } else { + ddcr2 |= PnDDCR2_DIVY; + } + } + + rcar_du_plane_write(rcdu, index, PnDDCR2, ddcr2); + rcar_du_plane_write(rcdu, index, PnDDCR4, ddcr4); + + /* Memory pitch (expressed in pixels) */ + if (plane->format->planes == 2) + mwr = plane->pitch; + else + mwr = plane->pitch * 8 / plane->format->bpp; + + rcar_du_plane_write(rcdu, index, PnMWR, mwr); + + /* Destination position and size */ + rcar_du_plane_write(rcdu, index, PnDSXR, plane->width); + rcar_du_plane_write(rcdu, index, PnDSYR, plane->height); + rcar_du_plane_write(rcdu, index, PnDPXR, plane->dst_x); + rcar_du_plane_write(rcdu, index, PnDPYR, plane->dst_y); + + /* Wrap-around and blinking, disabled */ + rcar_du_plane_write(rcdu, index, PnWASPR, 0); + rcar_du_plane_write(rcdu, index, PnWAMWR, 4095); + rcar_du_plane_write(rcdu, index, PnBTR, 0); + rcar_du_plane_write(rcdu, index, PnMLR, 0); +} + +void rcar_du_plane_setup(struct rcar_du_plane *plane) +{ + __rcar_du_plane_setup(plane, plane->hwindex); + if (plane->format->planes == 2) + __rcar_du_plane_setup(plane, (plane->hwindex + 1) % 8); + + rcar_du_plane_update_base(plane); +} + +static int +rcar_du_plane_update(struct drm_plane *plane, struct drm_crtc *crtc, + struct drm_framebuffer *fb, int crtc_x, int crtc_y, + unsigned int crtc_w, unsigned int crtc_h, + uint32_t src_x, uint32_t src_y, + uint32_t src_w, uint32_t src_h) +{ + struct rcar_du_plane *rplane = to_rcar_plane(plane); + struct rcar_du_device *rcdu = plane->dev->dev_private; + const struct rcar_du_format_info *format; + unsigned int nplanes; + int ret; + + format = rcar_du_format_info(fb->pixel_format); + if (format == NULL) { + dev_dbg(rcdu->dev, "%s: unsupported format %08x\n", __func__, + fb->pixel_format); + return -EINVAL; + } + + if (src_w >> 16 != crtc_w || src_h >> 16 != crtc_h) { + dev_dbg(rcdu->dev, "%s: scaling not supported\n", __func__); + return -EINVAL; + } + + nplanes = rplane->format ? rplane->format->planes : 0; + + /* Reallocate hardware planes if the number of required planes has + * changed. + */ + if (format->planes != nplanes) { + rcar_du_plane_release(rplane); + ret = rcar_du_plane_reserve(rplane, format); + if (ret < 0) + return ret; + } + + rplane->crtc = crtc; + rplane->format = format; + rplane->pitch = fb->pitches[0]; + + rplane->src_x = src_x >> 16; + rplane->src_y = src_y >> 16; + rplane->dst_x = crtc_x; + rplane->dst_y = crtc_y; + rplane->width = crtc_w; + rplane->height = crtc_h; + + rcar_du_plane_compute_base(rplane, fb); + rcar_du_plane_setup(rplane); + + mutex_lock(&rcdu->planes.lock); + rplane->enabled = true; + rcar_du_crtc_update_planes(rplane->crtc); + mutex_unlock(&rcdu->planes.lock); + + return 0; +} + +static int rcar_du_plane_disable(struct drm_plane *plane) +{ + struct rcar_du_device *rcdu = plane->dev->dev_private; + struct rcar_du_plane *rplane = to_rcar_plane(plane); + + if (!rplane->enabled) + return 0; + + mutex_lock(&rcdu->planes.lock); + rplane->enabled = false; + rcar_du_crtc_update_planes(rplane->crtc); + mutex_unlock(&rcdu->planes.lock); + + rcar_du_plane_release(rplane); + + rplane->crtc = NULL; + rplane->format = NULL; + + return 0; +} + +/* Both the .set_property and the .update_plane operations are called with the + * mode_config lock held. There is this no need to explicitly protect access to + * the alpha and colorkey fields and the mode register. + */ +static void rcar_du_plane_set_alpha(struct rcar_du_plane *plane, u32 alpha) +{ + if (plane->alpha == alpha) + return; + + plane->alpha = alpha; + if (!plane->enabled || plane->format->fourcc != DRM_FORMAT_XRGB1555) + return; + + rcar_du_plane_setup_mode(plane, plane->hwindex); +} + +static void rcar_du_plane_set_colorkey(struct rcar_du_plane *plane, + u32 colorkey) +{ + if (plane->colorkey == colorkey) + return; + + plane->colorkey = colorkey; + if (!plane->enabled) + return; + + rcar_du_plane_setup_mode(plane, plane->hwindex); +} + +static void rcar_du_plane_set_zpos(struct rcar_du_plane *plane, + unsigned int zpos) +{ + struct rcar_du_device *rcdu = plane->dev; + + mutex_lock(&rcdu->planes.lock); + if (plane->zpos == zpos) + goto done; + + plane->zpos = zpos; + if (!plane->enabled) + goto done; + + rcar_du_crtc_update_planes(plane->crtc); + +done: + mutex_unlock(&rcdu->planes.lock); +} + +static int rcar_du_plane_set_property(struct drm_plane *plane, + struct drm_property *property, + uint64_t value) +{ + struct rcar_du_device *rcdu = plane->dev->dev_private; + struct rcar_du_plane *rplane = to_rcar_plane(plane); + + if (property == rcdu->planes.alpha) + rcar_du_plane_set_alpha(rplane, value); + else if (property == rcdu->planes.colorkey) + rcar_du_plane_set_colorkey(rplane, value); + else if (property == rcdu->planes.zpos) + rcar_du_plane_set_zpos(rplane, value); + else + return -EINVAL; + + return 0; +} + +static const struct drm_plane_funcs rcar_du_plane_funcs = { + .update_plane = rcar_du_plane_update, + .disable_plane = rcar_du_plane_disable, + .set_property = rcar_du_plane_set_property, + .destroy = drm_plane_cleanup, +}; + +static const uint32_t formats[] = { + DRM_FORMAT_RGB565, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_UYVY, + DRM_FORMAT_YUYV, + DRM_FORMAT_NV12, + DRM_FORMAT_NV21, + DRM_FORMAT_NV16, +}; + +int rcar_du_plane_init(struct rcar_du_device *rcdu) +{ + unsigned int i; + + mutex_init(&rcdu->planes.lock); + rcdu->planes.free = 0xff; + + rcdu->planes.alpha = + drm_property_create_range(rcdu->ddev, 0, "alpha", 0, 255); + if (rcdu->planes.alpha == NULL) + return -ENOMEM; + + /* The color key is expressed as an RGB888 triplet stored in a 32-bit + * integer in XRGB8888 format. Bit 24 is used as a flag to disable (0) + * or enable source color keying (1). + */ + rcdu->planes.colorkey = + drm_property_create_range(rcdu->ddev, 0, "colorkey", + 0, 0x01ffffff); + if (rcdu->planes.colorkey == NULL) + return -ENOMEM; + + rcdu->planes.zpos = + drm_property_create_range(rcdu->ddev, 0, "zpos", 1, 7); + if (rcdu->planes.zpos == NULL) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(rcdu->planes.planes); ++i) { + struct rcar_du_plane *plane = &rcdu->planes.planes[i]; + + plane->dev = rcdu; + plane->hwindex = -1; + plane->alpha = 255; + plane->colorkey = RCAR_DU_COLORKEY_NONE; + plane->zpos = 0; + } + + return 0; +} + +int rcar_du_plane_register(struct rcar_du_device *rcdu) +{ + unsigned int i; + int ret; + + for (i = 0; i < RCAR_DU_NUM_KMS_PLANES; ++i) { + struct rcar_du_kms_plane *plane; + + plane = devm_kzalloc(rcdu->dev, sizeof(*plane), GFP_KERNEL); + if (plane == NULL) + return -ENOMEM; + + plane->hwplane = &rcdu->planes.planes[i + 2]; + plane->hwplane->zpos = 1; + + ret = drm_plane_init(rcdu->ddev, &plane->plane, + (1 << rcdu->num_crtcs) - 1, + &rcar_du_plane_funcs, formats, + ARRAY_SIZE(formats), false); + if (ret < 0) + return ret; + + drm_object_attach_property(&plane->plane.base, + rcdu->planes.alpha, 255); + drm_object_attach_property(&plane->plane.base, + rcdu->planes.colorkey, + RCAR_DU_COLORKEY_NONE); + drm_object_attach_property(&plane->plane.base, + rcdu->planes.zpos, 1); + } + + return 0; +} diff --git a/drivers/gpu/drm/rcar-du/rcar_du_plane.h b/drivers/gpu/drm/rcar-du/rcar_du_plane.h new file mode 100644 index 000000000000..5397dba2fe57 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_plane.h @@ -0,0 +1,67 @@ +/* + * rcar_du_plane.h -- R-Car Display Unit Planes + * + * 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 __RCAR_DU_PLANE_H__ +#define __RCAR_DU_PLANE_H__ + +struct drm_crtc; +struct drm_framebuffer; +struct rcar_du_device; +struct rcar_du_format_info; + +/* The RCAR DU has 8 hardware planes, shared between KMS planes and CRTCs. As + * using KMS planes requires at least one of the CRTCs being enabled, no more + * than 7 KMS planes can be available. We thus create 7 KMS planes and + * 9 software planes (one for each KMS planes and one for each CRTC). + */ + +#define RCAR_DU_NUM_KMS_PLANES 7 +#define RCAR_DU_NUM_HW_PLANES 8 +#define RCAR_DU_NUM_SW_PLANES 9 + +struct rcar_du_plane { + struct rcar_du_device *dev; + struct drm_crtc *crtc; + + bool enabled; + + int hwindex; /* 0-based, -1 means unused */ + unsigned int alpha; + unsigned int colorkey; + unsigned int zpos; + + const struct rcar_du_format_info *format; + + unsigned long dma[2]; + unsigned int pitch; + + unsigned int width; + unsigned int height; + + unsigned int src_x; + unsigned int src_y; + unsigned int dst_x; + unsigned int dst_y; +}; + +int rcar_du_plane_init(struct rcar_du_device *rcdu); +int rcar_du_plane_register(struct rcar_du_device *rcdu); +void rcar_du_plane_setup(struct rcar_du_plane *plane); +void rcar_du_plane_update_base(struct rcar_du_plane *plane); +void rcar_du_plane_compute_base(struct rcar_du_plane *plane, + struct drm_framebuffer *fb); +int rcar_du_plane_reserve(struct rcar_du_plane *plane, + const struct rcar_du_format_info *format); +void rcar_du_plane_release(struct rcar_du_plane *plane); + +#endif /* __RCAR_DU_PLANE_H__ */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_regs.h b/drivers/gpu/drm/rcar-du/rcar_du_regs.h new file mode 100644 index 000000000000..69f21f19b51c --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_regs.h @@ -0,0 +1,445 @@ +/* + * rcar_du_regs.h -- R-Car Display Unit 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 __RCAR_DU_REGS_H__ +#define __RCAR_DU_REGS_H__ + +#define DISP2_REG_OFFSET 0x30000 + +/* ----------------------------------------------------------------------------- + * Display Control Registers + */ + +#define DSYSR 0x00000 /* display 1 */ +#define D2SYSR 0x30000 /* display 2 */ +#define DSYSR_ILTS (1 << 29) +#define DSYSR_DSEC (1 << 20) +#define DSYSR_IUPD (1 << 16) +#define DSYSR_DRES (1 << 9) +#define DSYSR_DEN (1 << 8) +#define DSYSR_TVM_MASTER (0 << 6) +#define DSYSR_TVM_SWITCH (1 << 6) +#define DSYSR_TVM_TVSYNC (2 << 6) +#define DSYSR_TVM_MASK (3 << 6) +#define DSYSR_SCM_INT_NONE (0 << 4) +#define DSYSR_SCM_INT_SYNC (2 << 4) +#define DSYSR_SCM_INT_VIDEO (3 << 4) + +#define DSMR 0x00004 +#define D2SMR 0x30004 +#define DSMR_VSPM (1 << 28) +#define DSMR_ODPM (1 << 27) +#define DSMR_DIPM_DISP (0 << 25) +#define DSMR_DIPM_CSYNC (1 << 25) +#define DSMR_DIPM_DE (3 << 25) +#define DSMR_DIPM_MASK (3 << 25) +#define DSMR_CSPM (1 << 24) +#define DSMR_DIL (1 << 19) +#define DSMR_VSL (1 << 18) +#define DSMR_HSL (1 << 17) +#define DSMR_DDIS (1 << 16) +#define DSMR_CDEL (1 << 15) +#define DSMR_CDEM_CDE (0 << 13) +#define DSMR_CDEM_LOW (2 << 13) +#define DSMR_CDEM_HIGH (3 << 13) +#define DSMR_CDEM_MASK (3 << 13) +#define DSMR_CDED (1 << 12) +#define DSMR_ODEV (1 << 8) +#define DSMR_CSY_VH_OR (0 << 6) +#define DSMR_CSY_333 (2 << 6) +#define DSMR_CSY_222 (3 << 6) +#define DSMR_CSY_MASK (3 << 6) + +#define DSSR 0x00008 +#define D2SSR 0x30008 +#define DSSR_VC1FB_DSA0 (0 << 30) +#define DSSR_VC1FB_DSA1 (1 << 30) +#define DSSR_VC1FB_DSA2 (2 << 30) +#define DSSR_VC1FB_INIT (3 << 30) +#define DSSR_VC1FB_MASK (3 << 30) +#define DSSR_VC0FB_DSA0 (0 << 28) +#define DSSR_VC0FB_DSA1 (1 << 28) +#define DSSR_VC0FB_DSA2 (2 << 28) +#define DSSR_VC0FB_INIT (3 << 28) +#define DSSR_VC0FB_MASK (3 << 28) +#define DSSR_DFB(n) (1 << ((n)+15)) +#define DSSR_TVR (1 << 15) +#define DSSR_FRM (1 << 14) +#define DSSR_VBK (1 << 11) +#define DSSR_RINT (1 << 9) +#define DSSR_HBK (1 << 8) +#define DSSR_ADC(n) (1 << ((n)-1)) + +#define DSRCR 0x0000c +#define D2SRCR 0x3000c +#define DSRCR_TVCL (1 << 15) +#define DSRCR_FRCL (1 << 14) +#define DSRCR_VBCL (1 << 11) +#define DSRCR_RICL (1 << 9) +#define DSRCR_HBCL (1 << 8) +#define DSRCR_ADCL(n) (1 << ((n)-1)) +#define DSRCR_MASK 0x0000cbff + +#define DIER 0x00010 +#define D2IER 0x30010 +#define DIER_TVE (1 << 15) +#define DIER_FRE (1 << 14) +#define DIER_VBE (1 << 11) +#define DIER_RIE (1 << 9) +#define DIER_HBE (1 << 8) +#define DIER_ADCE(n) (1 << ((n)-1)) + +#define CPCR 0x00014 +#define CPCR_CP4CE (1 << 19) +#define CPCR_CP3CE (1 << 18) +#define CPCR_CP2CE (1 << 17) +#define CPCR_CP1CE (1 << 16) + +#define DPPR 0x00018 +#define DPPR_DPE(n) (1 << ((n)*4-1)) +#define DPPR_DPS(n, p) (((p)-1) << DPPR_DPS_SHIFT(n)) +#define DPPR_DPS_SHIFT(n) (((n)-1)*4) +#define DPPR_BPP16 (DPPR_DPE(8) | DPPR_DPS(8, 1)) /* plane1 */ +#define DPPR_BPP32_P1 (DPPR_DPE(7) | DPPR_DPS(7, 1)) +#define DPPR_BPP32_P2 (DPPR_DPE(8) | DPPR_DPS(8, 2)) +#define DPPR_BPP32 (DPPR_BPP32_P1 | DPPR_BPP32_P2) /* plane1 & 2 */ + +#define DEFR 0x00020 +#define D2EFR 0x30020 +#define DEFR_CODE (0x7773 << 16) +#define DEFR_EXSL (1 << 12) +#define DEFR_EXVL (1 << 11) +#define DEFR_EXUP (1 << 5) +#define DEFR_VCUP (1 << 4) +#define DEFR_DEFE (1 << 0) + +#define DAPCR 0x00024 +#define DAPCR_CODE (0x7773 << 16) +#define DAPCR_AP2E (1 << 4) +#define DAPCR_AP1E (1 << 0) + +#define DCPCR 0x00028 +#define DCPCR_CODE (0x7773 << 16) +#define DCPCR_CA2B (1 << 13) +#define DCPCR_CD2F (1 << 12) +#define DCPCR_DC2E (1 << 8) +#define DCPCR_CAB (1 << 5) +#define DCPCR_CDF (1 << 4) +#define DCPCR_DCE (1 << 0) + +#define DEFR2 0x00034 +#define D2EFR2 0x30034 +#define DEFR2_CODE (0x7775 << 16) +#define DEFR2_DEFE2G (1 << 0) + +#define DEFR3 0x00038 +#define D2EFR3 0x30038 +#define DEFR3_CODE (0x7776 << 16) +#define DEFR3_EVDA (1 << 14) +#define DEFR3_EVDM_1 (1 << 12) +#define DEFR3_EVDM_2 (2 << 12) +#define DEFR3_EVDM_3 (3 << 12) +#define DEFR3_VMSM2_EMA (1 << 6) +#define DEFR3_VMSM1_ENA (1 << 4) +#define DEFR3_DEFE3 (1 << 0) + +#define DEFR4 0x0003c +#define D2EFR4 0x3003c +#define DEFR4_CODE (0x7777 << 16) +#define DEFR4_LRUO (1 << 5) +#define DEFR4_SPCE (1 << 4) + +#define DVCSR 0x000d0 +#define DVCSR_VCnFB2_DSA0(n) (0 << ((n)*2+16)) +#define DVCSR_VCnFB2_DSA1(n) (1 << ((n)*2+16)) +#define DVCSR_VCnFB2_DSA2(n) (2 << ((n)*2+16)) +#define DVCSR_VCnFB2_INIT(n) (3 << ((n)*2+16)) +#define DVCSR_VCnFB2_MASK(n) (3 << ((n)*2+16)) +#define DVCSR_VCnFB_DSA0(n) (0 << ((n)*2)) +#define DVCSR_VCnFB_DSA1(n) (1 << ((n)*2)) +#define DVCSR_VCnFB_DSA2(n) (2 << ((n)*2)) +#define DVCSR_VCnFB_INIT(n) (3 << ((n)*2)) +#define DVCSR_VCnFB_MASK(n) (3 << ((n)*2)) + +#define DEFR5 0x000e0 +#define DEFR5_CODE (0x66 << 24) +#define DEFR5_YCRGB2_DIS (0 << 14) +#define DEFR5_YCRGB2_PRI1 (1 << 14) +#define DEFR5_YCRGB2_PRI2 (2 << 14) +#define DEFR5_YCRGB2_PRI3 (3 << 14) +#define DEFR5_YCRGB2_MASK (3 << 14) +#define DEFR5_YCRGB1_DIS (0 << 12) +#define DEFR5_YCRGB1_PRI1 (1 << 12) +#define DEFR5_YCRGB1_PRI2 (2 << 12) +#define DEFR5_YCRGB1_PRI3 (3 << 12) +#define DEFR5_YCRGB1_MASK (3 << 12) +#define DEFR5_DEFE5 (1 << 0) + +#define DDLTR 0x000e4 +#define DDLTR_CODE (0x7766 << 16) +#define DDLTR_DLAR2 (1 << 6) +#define DDLTR_DLAY2 (1 << 5) +#define DDLTR_DLAY1 (1 << 1) + +#define DEFR6 0x000e8 +#define DEFR6_CODE (0x7778 << 16) +#define DEFR6_ODPM22_D2SMR (0 << 10) +#define DEFR6_ODPM22_DISP (2 << 10) +#define DEFR6_ODPM22_CDE (3 << 10) +#define DEFR6_ODPM22_MASK (3 << 10) +#define DEFR6_ODPM12_DSMR (0 << 8) +#define DEFR6_ODPM12_DISP (2 << 8) +#define DEFR6_ODPM12_CDE (3 << 8) +#define DEFR6_ODPM12_MASK (3 << 8) +#define DEFR6_TCNE2 (1 << 6) +#define DEFR6_MLOS1 (1 << 2) +#define DEFR6_DEFAULT (DEFR6_CODE | DEFR6_TCNE2) + +/* ----------------------------------------------------------------------------- + * Display Timing Generation Registers + */ + +#define HDSR 0x00040 +#define HDER 0x00044 +#define VDSR 0x00048 +#define VDER 0x0004c +#define HCR 0x00050 +#define HSWR 0x00054 +#define VCR 0x00058 +#define VSPR 0x0005c +#define EQWR 0x00060 +#define SPWR 0x00064 +#define CLAMPSR 0x00070 +#define CLAMPWR 0x00074 +#define DESR 0x00078 +#define DEWR 0x0007c + +/* ----------------------------------------------------------------------------- + * Display Attribute Registers + */ + +#define CP1TR 0x00080 +#define CP2TR 0x00084 +#define CP3TR 0x00088 +#define CP4TR 0x0008c + +#define DOOR 0x00090 +#define DOOR_RGB(r, g, b) (((r) << 18) | ((g) << 10) | ((b) << 2)) +#define CDER 0x00094 +#define CDER_RGB(r, g, b) (((r) << 18) | ((g) << 10) | ((b) << 2)) +#define BPOR 0x00098 +#define BPOR_RGB(r, g, b) (((r) << 18) | ((g) << 10) | ((b) << 2)) + +#define RINTOFSR 0x0009c + +#define DSHPR 0x000c8 +#define DSHPR_CODE (0x7776 << 16) +#define DSHPR_PRIH (0xa << 4) +#define DSHPR_PRIL_BPP16 (0x8 << 0) +#define DSHPR_PRIL_BPP32 (0x9 << 0) + +/* ----------------------------------------------------------------------------- + * Display Plane Registers + */ + +#define PLANE_OFF 0x00100 + +#define PnMR 0x00100 /* plane 1 */ +#define PnMR_VISL_VIN0 (0 << 26) /* use Video Input 0 */ +#define PnMR_VISL_VIN1 (1 << 26) /* use Video Input 1 */ +#define PnMR_VISL_VIN2 (2 << 26) /* use Video Input 2 */ +#define PnMR_VISL_VIN3 (3 << 26) /* use Video Input 3 */ +#define PnMR_YCDF_YUYV (1 << 20) /* YUYV format */ +#define PnMR_TC_R (0 << 17) /* Tranparent color is PnTC1R */ +#define PnMR_TC_CP (1 << 17) /* Tranparent color is color palette */ +#define PnMR_WAE (1 << 16) /* Wrap around Enable */ +#define PnMR_SPIM_TP (0 << 12) /* Transparent Color */ +#define PnMR_SPIM_ALP (1 << 12) /* Alpha Blending */ +#define PnMR_SPIM_EOR (2 << 12) /* EOR */ +#define PnMR_SPIM_TP_OFF (1 << 14) /* No Transparent Color */ +#define PnMR_CPSL_CP1 (0 << 8) /* Color Palette selected 1 */ +#define PnMR_CPSL_CP2 (1 << 8) /* Color Palette selected 2 */ +#define PnMR_CPSL_CP3 (2 << 8) /* Color Palette selected 3 */ +#define PnMR_CPSL_CP4 (3 << 8) /* Color Palette selected 4 */ +#define PnMR_DC (1 << 7) /* Display Area Change */ +#define PnMR_BM_MD (0 << 4) /* Manual Display Change Mode */ +#define PnMR_BM_AR (1 << 4) /* Auto Rendering Mode */ +#define PnMR_BM_AD (2 << 4) /* Auto Display Change Mode */ +#define PnMR_BM_VC (3 << 4) /* Video Capture Mode */ +#define PnMR_DDDF_8BPP (0 << 0) /* 8bit */ +#define PnMR_DDDF_16BPP (1 << 0) /* 16bit or 32bit */ +#define PnMR_DDDF_ARGB (2 << 0) /* ARGB */ +#define PnMR_DDDF_YC (3 << 0) /* YC */ +#define PnMR_DDDF_MASK (3 << 0) + +#define PnMWR 0x00104 + +#define PnALPHAR 0x00108 +#define PnALPHAR_ABIT_1 (0 << 12) +#define PnALPHAR_ABIT_0 (1 << 12) +#define PnALPHAR_ABIT_X (2 << 12) + +#define PnDSXR 0x00110 +#define PnDSYR 0x00114 +#define PnDPXR 0x00118 +#define PnDPYR 0x0011c + +#define PnDSA0R 0x00120 +#define PnDSA1R 0x00124 +#define PnDSA2R 0x00128 +#define PnDSA_MASK 0xfffffff0 + +#define PnSPXR 0x00130 +#define PnSPYR 0x00134 +#define PnWASPR 0x00138 +#define PnWAMWR 0x0013c + +#define PnBTR 0x00140 + +#define PnTC1R 0x00144 +#define PnTC2R 0x00148 +#define PnTC3R 0x0014c +#define PnTC3R_CODE (0x66 << 24) + +#define PnMLR 0x00150 + +#define PnSWAPR 0x00180 +#define PnSWAPR_DIGN (1 << 4) +#define PnSWAPR_SPQW (1 << 3) +#define PnSWAPR_SPLW (1 << 2) +#define PnSWAPR_SPWD (1 << 1) +#define PnSWAPR_SPBY (1 << 0) + +#define PnDDCR 0x00184 +#define PnDDCR_CODE (0x7775 << 16) +#define PnDDCR_LRGB1 (1 << 11) +#define PnDDCR_LRGB0 (1 << 10) + +#define PnDDCR2 0x00188 +#define PnDDCR2_CODE (0x7776 << 16) +#define PnDDCR2_NV21 (1 << 5) +#define PnDDCR2_Y420 (1 << 4) +#define PnDDCR2_DIVU (1 << 1) +#define PnDDCR2_DIVY (1 << 0) + +#define PnDDCR4 0x00190 +#define PnDDCR4_CODE (0x7766 << 16) +#define PnDDCR4_SDFS_RGB (0 << 4) +#define PnDDCR4_SDFS_YC (5 << 4) +#define PnDDCR4_SDFS_MASK (7 << 4) +#define PnDDCR4_EDF_NONE (0 << 0) +#define PnDDCR4_EDF_ARGB8888 (1 << 0) +#define PnDDCR4_EDF_RGB888 (2 << 0) +#define PnDDCR4_EDF_RGB666 (3 << 0) +#define PnDDCR4_EDF_MASK (7 << 0) + +#define APnMR 0x0a100 +#define APnMR_WAE (1 << 16) /* Wrap around Enable */ +#define APnMR_DC (1 << 7) /* Display Area Change */ +#define APnMR_BM_MD (0 << 4) /* Manual Display Change Mode */ +#define APnMR_BM_AD (2 << 4) /* Auto Display Change Mode */ + +#define APnMWR 0x0a104 +#define APnDSA0R 0x0a120 +#define APnDSA1R 0x0a124 +#define APnDSA2R 0x0a128 +#define APnMLR 0x0a150 + +/* ----------------------------------------------------------------------------- + * Display Capture Registers + */ + +#define DCMWR 0x0c104 +#define DC2MWR 0x0c204 +#define DCSAR 0x0c120 +#define DC2SAR 0x0c220 +#define DCMLR 0x0c150 +#define DC2MLR 0x0c250 + +/* ----------------------------------------------------------------------------- + * Color Palette Registers + */ + +#define CP1_000R 0x01000 +#define CP1_255R 0x013fc +#define CP2_000R 0x02000 +#define CP2_255R 0x023fc +#define CP3_000R 0x03000 +#define CP3_255R 0x033fc +#define CP4_000R 0x04000 +#define CP4_255R 0x043fc + +/* ----------------------------------------------------------------------------- + * External Synchronization Control Registers + */ + +#define ESCR 0x10000 +#define ESCR2 0x31000 +#define ESCR_DCLKOINV (1 << 25) +#define ESCR_DCLKSEL_DCLKIN (0 << 20) +#define ESCR_DCLKSEL_CLKS (1 << 20) +#define ESCR_DCLKSEL_MASK (1 << 20) +#define ESCR_DCLKDIS (1 << 16) +#define ESCR_SYNCSEL_OFF (0 << 8) +#define ESCR_SYNCSEL_EXVSYNC (2 << 8) +#define ESCR_SYNCSEL_EXHSYNC (3 << 8) +#define ESCR_FRQSEL_MASK (0x3f << 0) + +#define OTAR 0x10004 +#define OTAR2 0x31004 + +/* ----------------------------------------------------------------------------- + * Dual Display Output Control Registers + */ + +#define DORCR 0x11000 +#define DORCR_PG2T (1 << 30) +#define DORCR_DK2S (1 << 28) +#define DORCR_PG2D_DS1 (0 << 24) +#define DORCR_PG2D_DS2 (1 << 24) +#define DORCR_PG2D_FIX0 (2 << 24) +#define DORCR_PG2D_DOOR (3 << 24) +#define DORCR_PG2D_MASK (3 << 24) +#define DORCR_DR1D (1 << 21) +#define DORCR_PG1D_DS1 (0 << 16) +#define DORCR_PG1D_DS2 (1 << 16) +#define DORCR_PG1D_FIX0 (2 << 16) +#define DORCR_PG1D_DOOR (3 << 16) +#define DORCR_PG1D_MASK (3 << 16) +#define DORCR_RGPV (1 << 4) +#define DORCR_DPRS (1 << 0) + +#define DPTSR 0x11004 +#define DPTSR_PnDK(n) (1 << ((n) + 16)) +#define DPTSR_PnTS(n) (1 << (n)) + +#define DAPTSR 0x11008 +#define DAPTSR_APnDK(n) (1 << ((n) + 16)) +#define DAPTSR_APnTS(n) (1 << (n)) + +#define DS1PR 0x11020 +#define DS2PR 0x11024 + +/* ----------------------------------------------------------------------------- + * YC-RGB Conversion Coefficient Registers + */ + +#define YNCR 0x11080 +#define YNOR 0x11084 +#define CRNOR 0x11088 +#define CBNOR 0x1108c +#define RCRCR 0x11090 +#define GCRCR 0x11094 +#define GCBCR 0x11098 +#define BCBCR 0x1109c + +#endif /* __RCAR_DU_REGS_H__ */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_vga.c b/drivers/gpu/drm/rcar-du/rcar_du_vga.c new file mode 100644 index 000000000000..327289ec380d --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_vga.c @@ -0,0 +1,149 @@ +/* + * rcar_du_vga.c -- R-Car Display Unit VGA DAC and Connector + * + * 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 "rcar_du_drv.h" +#include "rcar_du_kms.h" +#include "rcar_du_vga.h" + +/* ----------------------------------------------------------------------------- + * Connector + */ + +static int rcar_du_vga_connector_get_modes(struct drm_connector *connector) +{ + return 0; +} + +static int rcar_du_vga_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static const struct drm_connector_helper_funcs connector_helper_funcs = { + .get_modes = rcar_du_vga_connector_get_modes, + .mode_valid = rcar_du_vga_connector_mode_valid, + .best_encoder = rcar_du_connector_best_encoder, +}; + +static void rcar_du_vga_connector_destroy(struct drm_connector *connector) +{ + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); +} + +static enum drm_connector_status +rcar_du_vga_connector_detect(struct drm_connector *connector, bool force) +{ + return connector_status_unknown; +} + +static const struct drm_connector_funcs connector_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = rcar_du_vga_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = rcar_du_vga_connector_destroy, +}; + +static int rcar_du_vga_connector_init(struct rcar_du_device *rcdu, + struct rcar_du_encoder *renc) +{ + struct rcar_du_connector *rcon; + struct drm_connector *connector; + int ret; + + rcon = devm_kzalloc(rcdu->dev, sizeof(*rcon), GFP_KERNEL); + if (rcon == NULL) + return -ENOMEM; + + connector = &rcon->connector; + connector->display_info.width_mm = 0; + connector->display_info.height_mm = 0; + + ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs, + DRM_MODE_CONNECTOR_VGA); + if (ret < 0) + return ret; + + drm_connector_helper_add(connector, &connector_helper_funcs); + ret = drm_sysfs_connector_add(connector); + if (ret < 0) + return ret; + + drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF); + drm_object_property_set_value(&connector->base, + rcdu->ddev->mode_config.dpms_property, DRM_MODE_DPMS_OFF); + + ret = drm_mode_connector_attach_encoder(connector, &renc->encoder); + if (ret < 0) + return ret; + + connector->encoder = &renc->encoder; + rcon->encoder = renc; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Encoder + */ + +static void rcar_du_vga_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +} + +static bool rcar_du_vga_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static const struct drm_encoder_helper_funcs encoder_helper_funcs = { + .dpms = rcar_du_vga_encoder_dpms, + .mode_fixup = rcar_du_vga_encoder_mode_fixup, + .prepare = rcar_du_encoder_mode_prepare, + .commit = rcar_du_encoder_mode_commit, + .mode_set = rcar_du_encoder_mode_set, +}; + +static const struct drm_encoder_funcs encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +int rcar_du_vga_init(struct rcar_du_device *rcdu, + const struct rcar_du_encoder_vga_data *data, + unsigned int output) +{ + struct rcar_du_encoder *renc; + int ret; + + renc = devm_kzalloc(rcdu->dev, sizeof(*renc), GFP_KERNEL); + if (renc == NULL) + return -ENOMEM; + + renc->output = output; + + ret = drm_encoder_init(rcdu->ddev, &renc->encoder, &encoder_funcs, + DRM_MODE_ENCODER_DAC); + if (ret < 0) + return ret; + + drm_encoder_helper_add(&renc->encoder, &encoder_helper_funcs); + + return rcar_du_vga_connector_init(rcdu, renc); +} diff --git a/drivers/gpu/drm/rcar-du/rcar_du_vga.h b/drivers/gpu/drm/rcar-du/rcar_du_vga.h new file mode 100644 index 000000000000..66b4d2d7190d --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_vga.h @@ -0,0 +1,24 @@ +/* + * rcar_du_vga.h -- R-Car Display Unit VGA DAC and Connector + * + * 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 __RCAR_DU_VGA_H__ +#define __RCAR_DU_VGA_H__ + +struct rcar_du_device; +struct rcar_du_encoder_vga_data; + +int rcar_du_vga_init(struct rcar_du_device *rcdu, + const struct rcar_du_encoder_vga_data *data, + unsigned int output); + +#endif /* __RCAR_DU_VGA_H__ */ diff --git a/include/linux/platform_data/rcar-du.h b/include/linux/platform_data/rcar-du.h new file mode 100644 index 000000000000..80587fdbba3e --- /dev/null +++ b/include/linux/platform_data/rcar-du.h @@ -0,0 +1,54 @@ +/* + * rcar_du.h -- R-Car Display Unit DRM 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 __RCAR_DU_H__ +#define __RCAR_DU_H__ + +#include + +enum rcar_du_encoder_type { + RCAR_DU_ENCODER_UNUSED = 0, + RCAR_DU_ENCODER_VGA, + RCAR_DU_ENCODER_LVDS, +}; + +struct rcar_du_panel_data { + unsigned int width_mm; /* Panel width in mm */ + unsigned int height_mm; /* Panel height in mm */ + struct drm_mode_modeinfo mode; +}; + +struct rcar_du_encoder_lvds_data { + struct rcar_du_panel_data panel; +}; + +struct rcar_du_encoder_vga_data { + /* TODO: Add DDC information for EDID retrieval */ +}; + +struct rcar_du_encoder_data { + enum rcar_du_encoder_type encoder; + unsigned int output; + + union { + struct rcar_du_encoder_lvds_data lvds; + struct rcar_du_encoder_vga_data vga; + } u; +}; + +struct rcar_du_platform_data { + struct rcar_du_encoder_data *encoders; + unsigned int num_encoders; +}; + +#endif /* __RCAR_DU_H__ */ -- cgit v1.2.3 From 0ddf03c95bbb4f4ed57281fa7b781472950df749 Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Wed, 5 Jun 2013 15:13:26 +0200 Subject: mmc: esdhc-imx: parse max-frequency from devicetree In order to make it possible to reduce the SD bus frequency, parse the optional "max-frequency" attribute as documented in devicetree/bindings/mmc/mmc.txt Signed-off-by: Lucas Stach Acked-by: Shawn Guo Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-esdhc-imx.c | 18 +++++++++++++++++- include/linux/platform_data/mmc-esdhc-imx.h | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) (limited to 'include/linux/platform_data') diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index eb1310ca021e..1dd5ba858754 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -384,6 +384,20 @@ static void esdhc_writeb_le(struct sdhci_host *host, u8 val, int reg) } } +static unsigned int esdhc_pltfm_get_max_clock(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct pltfm_imx_data *imx_data = pltfm_host->priv; + struct esdhc_platform_data *boarddata = &imx_data->boarddata; + + u32 f_host = clk_get_rate(pltfm_host->clk); + + if (boarddata->f_max && (boarddata->f_max < f_host)) + return boarddata->f_max; + else + return f_host; +} + static unsigned int esdhc_pltfm_get_min_clock(struct sdhci_host *host) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -447,7 +461,7 @@ static const struct sdhci_ops sdhci_esdhc_ops = { .write_w = esdhc_writew_le, .write_b = esdhc_writeb_le, .set_clock = esdhc_pltfm_set_clock, - .get_max_clock = sdhci_pltfm_clk_get_max_clock, + .get_max_clock = esdhc_pltfm_get_max_clock, .get_min_clock = esdhc_pltfm_get_min_clock, .get_ro = esdhc_pltfm_get_ro, .platform_bus_width = esdhc_pltfm_bus_width, @@ -490,6 +504,8 @@ sdhci_esdhc_imx_probe_dt(struct platform_device *pdev, of_property_read_u32(np, "bus-width", &boarddata->max_bus_width); + of_property_read_u32(np, "max-frequency", &boarddata->f_max); + return 0; } #else diff --git a/include/linux/platform_data/mmc-esdhc-imx.h b/include/linux/platform_data/mmc-esdhc-imx.h index b4a0521ce411..d44912d81578 100644 --- a/include/linux/platform_data/mmc-esdhc-imx.h +++ b/include/linux/platform_data/mmc-esdhc-imx.h @@ -40,5 +40,6 @@ struct esdhc_platform_data { enum wp_types wp_type; enum cd_types cd_type; int max_bus_width; + unsigned int f_max; }; #endif /* __ASM_ARCH_IMX_ESDHC_H */ -- cgit v1.2.3 From 594fbe713bf60073ed884dc317a74dd5b327aba7 Mon Sep 17 00:00:00 2001 From: Arnaud Ebalard Date: Thu, 20 Jun 2013 22:21:04 +0200 Subject: Add support for GMT G762/G763 PWM fan controllers GMT G762/763 fan speed PWM controller is connected directly to a fan and performs closed-loop or open-loop control of the fan speed. Two modes - PWM or DC - are supported by the chip. Introduced driver provides various knobs to control the operations of the chip (via sysfs interface). Specific characteristics of the system can be passed either using board init code or via DT. Documentation for both the driver and DT bindings are also provided. Signed-off-by: Arnaud Ebalard Tested-by: Simon Guinot Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/g762.txt | 47 + Documentation/hwmon/g762 | 65 ++ drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/g762.c | 1149 ++++++++++++++++++++++ include/linux/platform_data/g762.h | 37 + 6 files changed, 1309 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/g762.txt create mode 100644 Documentation/hwmon/g762 create mode 100644 drivers/hwmon/g762.c create mode 100644 include/linux/platform_data/g762.h (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/hwmon/g762.txt b/Documentation/devicetree/bindings/hwmon/g762.txt new file mode 100644 index 000000000000..25cc6d8ee575 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/g762.txt @@ -0,0 +1,47 @@ +GMT G762/G763 PWM Fan controller + +Required node properties: + + - "compatible": must be either "gmt,g762" or "gmt,g763" + - "reg": I2C bus address of the device + - "clocks": a fixed clock providing input clock frequency + on CLK pin of the chip. + +Optional properties: + + - "fan_startv": fan startup voltage. Accepted values are 0, 1, 2 and 3. + The higher the more. + + - "pwm_polarity": pwm polarity. Accepted values are 0 (positive duty) + and 1 (negative duty). + + - "fan_gear_mode": fan gear mode. Supported values are 0, 1 and 2. + +If an optional property is not set in .dts file, then current value is kept +unmodified (e.g. u-boot installed value). + +Additional information on operational parameters for the device is available +in Documentation/hwmon/g762. A detailed datasheet for the device is available +at http://natisbad.org/NAS/refs/GMT_EDS-762_763-080710-0.2.pdf. + +Example g762 node: + + clocks { + #address-cells = <1>; + #size-cells = <0>; + + g762_clk: fixedclk { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <8192>; + } + } + + g762: g762@3e { + compatible = "gmt,g762"; + reg = <0x3e>; + clocks = <&g762_clk> + fan_gear_mode = <0>; /* chip default */ + fan_startv = <1>; /* chip default */ + pwm_polarity = <0>; /* chip default */ + }; diff --git a/Documentation/hwmon/g762 b/Documentation/hwmon/g762 new file mode 100644 index 000000000000..923db9c5b5bc --- /dev/null +++ b/Documentation/hwmon/g762 @@ -0,0 +1,65 @@ +Kernel driver g762 +================== + +The GMT G762 Fan Speed PWM Controller is connected directly to a fan +and performs closed-loop or open-loop control of the fan speed. Two +modes - PWM or DC - are supported by the device. + +For additional information, a detailed datasheet is available at +http://natisbad.org/NAS/ref/GMT_EDS-762_763-080710-0.2.pdf. sysfs +bindings are described in Documentation/hwmon/sysfs-interface. + +The following entries are available to the user in a subdirectory of +/sys/bus/i2c/drivers/g762/ to control the operation of the device. +This can be done manually using the following entries but is usually +done via a userland daemon like fancontrol. + +Note that those entries do not provide ways to setup the specific +hardware characteristics of the system (reference clock, pulses per +fan revolution, ...); Those can be modified via devicetree bindings +documented in Documentation/devicetree/bindings/hwmon/g762.txt or +using a specific platform_data structure in board initialization +file (see include/linux/platform_data/g762.h). + + fan1_target: set desired fan speed. This only makes sense in closed-loop + fan speed control (i.e. when pwm1_enable is set to 2). + + fan1_input: provide current fan rotation value in RPM as reported by + the fan to the device. + + fan1_div: fan clock divisor. Supported value are 1, 2, 4 and 8. + + fan1_pulses: number of pulses per fan revolution. Supported values + are 2 and 4. + + fan1_fault: reports fan failure, i.e. no transition on fan gear pin for + about 0.7s (if the fan is not voluntarily set off). + + fan1_alarm: in closed-loop control mode, if fan RPM value is 25% out + of the programmed value for over 6 seconds 'fan1_alarm' is + set to 1. + + pwm1_enable: set current fan speed control mode i.e. 1 for manual fan + speed control (open-loop) via pwm1 described below, 2 for + automatic fan speed control (closed-loop) via fan1_target + above. + + pwm1_mode: set or get fan driving mode: 1 for PWM mode, 0 for DC mode. + + pwm1: get or set PWM fan control value in open-loop mode. This is an + integer value between 0 and 255. 0 stops the fan, 255 makes + it run at full speed. + +Both in PWM mode ('pwm1_mode' set to 1) and DC mode ('pwm1_mode' set to 0), +when current fan speed control mode is open-loop ('pwm1_enable' set to 1), +the fan speed is programmed by setting a value between 0 and 255 via 'pwm1' +entry (0 stops the fan, 255 makes it run at full speed). In closed-loop mode +('pwm1_enable' set to 2), the expected rotation speed in RPM can be passed to +the chip via 'fan1_target'. In closed-loop mode, the target speed is compared +with current speed (available via 'fan1_input') by the device and a feedback +is performed to match that target value. The fan speed value is computed +based on the parameters associated with the physical characteristics of the +system: a reference clock source frequency, a number of pulses per fan +revolution, etc. + +Note that the driver will update its values at most once per second. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 683c769a8fca..e989f7fd645b 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -461,6 +461,16 @@ config SENSORS_G760A This driver can also be built as a module. If so, the module will be called g760a. +config SENSORS_G762 + tristate "GMT G762 and G763" + depends on I2C + help + If you say yes here you get support for Global Mixed-mode + Technology Inc G762 and G763 fan speed PWM controller chips. + + This driver can also be built as a module. If so, the module + will be called g762. + config SENSORS_GL518SM tristate "Genesys Logic GL518SM" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index d17d3e64f9f4..4f0fb5235f42 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -60,6 +60,7 @@ obj-$(CONFIG_SENSORS_F75375S) += f75375s.o obj-$(CONFIG_SENSORS_FAM15H_POWER) += fam15h_power.o obj-$(CONFIG_SENSORS_FSCHMD) += fschmd.o obj-$(CONFIG_SENSORS_G760A) += g760a.o +obj-$(CONFIG_SENSORS_G762) += g762.o obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o diff --git a/drivers/hwmon/g762.c b/drivers/hwmon/g762.c new file mode 100644 index 000000000000..73adf01b0ef2 --- /dev/null +++ b/drivers/hwmon/g762.c @@ -0,0 +1,1149 @@ +/* + * g762 - Driver for the Global Mixed-mode Technology Inc. fan speed + * PWM controller chips from G762 family, i.e. G762 and G763 + * + * Copyright (C) 2013, Arnaud EBALARD + * + * This work is based on a basic version for 2.6.31 kernel developed + * by Olivier Mouchet for LaCie. Updates and correction have been + * performed to run on recent kernels. Additional features, like the + * ability to configure various characteristics via .dts file or + * board init file have been added. Detailed datasheet on which this + * development is based is available here: + * + * http://natisbad.org/NAS/refs/GMT_EDS-762_763-080710-0.2.pdf + * + * Headers from previous developments have been kept below: + * + * Copyright (c) 2009 LaCie + * + * Author: Olivier Mouchet + * + * based on g760a code written by Herbert Valerio Riedel + * Copyright (C) 2007 Herbert Valerio Riedel + * + * g762: minimal datasheet available at: + * http://www.gmt.com.tw/product/datasheet/EDS-762_3.pdf + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "g762" + +static const struct i2c_device_id g762_id[] = { + { "g762", 0 }, + { "g763", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, g762_id); + +enum g762_regs { + G762_REG_SET_CNT = 0x00, + G762_REG_ACT_CNT = 0x01, + G762_REG_FAN_STA = 0x02, + G762_REG_SET_OUT = 0x03, + G762_REG_FAN_CMD1 = 0x04, + G762_REG_FAN_CMD2 = 0x05, +}; + +/* Config register bits */ +#define G762_REG_FAN_CMD1_DET_FAN_FAIL 0x80 /* enable fan_fail signal */ +#define G762_REG_FAN_CMD1_DET_FAN_OOC 0x40 /* enable fan_out_of_control */ +#define G762_REG_FAN_CMD1_OUT_MODE 0x20 /* out mode: PWM or DC */ +#define G762_REG_FAN_CMD1_FAN_MODE 0x10 /* fan mode: closed/open-loop */ +#define G762_REG_FAN_CMD1_CLK_DIV_ID1 0x08 /* clock divisor value */ +#define G762_REG_FAN_CMD1_CLK_DIV_ID0 0x04 +#define G762_REG_FAN_CMD1_PWM_POLARITY 0x02 /* PWM polarity */ +#define G762_REG_FAN_CMD1_PULSE_PER_REV 0x01 /* pulse per fan revolution */ + +#define G762_REG_FAN_CMD2_GEAR_MODE_1 0x08 /* fan gear mode */ +#define G762_REG_FAN_CMD2_GEAR_MODE_0 0x04 +#define G762_REG_FAN_CMD2_FAN_STARTV_1 0x02 /* fan startup voltage */ +#define G762_REG_FAN_CMD2_FAN_STARTV_0 0x01 + +#define G762_REG_FAN_STA_FAIL 0x02 /* fan fail */ +#define G762_REG_FAN_STA_OOC 0x01 /* fan out of control */ + +/* Config register values */ +#define G762_OUT_MODE_PWM 1 +#define G762_OUT_MODE_DC 0 + +#define G762_FAN_MODE_CLOSED_LOOP 2 +#define G762_FAN_MODE_OPEN_LOOP 1 + +#define G762_PWM_POLARITY_NEGATIVE 1 +#define G762_PWM_POLARITY_POSITIVE 0 + +/* Register data is read (and cached) at most once per second. */ +#define G762_UPDATE_INTERVAL HZ + +/* + * Extract pulse count per fan revolution value (2 or 4) from given + * FAN_CMD1 register value. + */ +#define G762_PULSE_FROM_REG(reg) \ + ((((reg) & G762_REG_FAN_CMD1_PULSE_PER_REV) + 1) << 1) + +/* + * Extract fan clock divisor (1, 2, 4 or 8) from given FAN_CMD1 + * register value. + */ +#define G762_CLKDIV_FROM_REG(reg) \ + (1 << (((reg) & (G762_REG_FAN_CMD1_CLK_DIV_ID0 | \ + G762_REG_FAN_CMD1_CLK_DIV_ID1)) >> 2)) + +/* + * Extract fan gear mode multiplier value (0, 2 or 4) from given + * FAN_CMD2 register value. + */ +#define G762_GEARMULT_FROM_REG(reg) \ + (1 << (((reg) & (G762_REG_FAN_CMD2_GEAR_MODE_0 | \ + G762_REG_FAN_CMD2_GEAR_MODE_1)) >> 2)) + +struct g762_data { + struct i2c_client *client; + struct device *hwmon_dev; + struct clk *clk; + + /* update mutex */ + struct mutex update_lock; + + /* board specific parameters. */ + u32 clk_freq; + + /* g762 register cache */ + bool valid; + unsigned long last_updated; /* in jiffies */ + + u8 set_cnt; /* controls fan rotation speed in closed-loop mode */ + u8 act_cnt; /* provides access to current fan RPM value */ + u8 fan_sta; /* bit 0: set when actual fan speed is more than + * 25% outside requested fan speed + * bit 1: set when no transition occurs on fan + * pin for 0.7s + */ + u8 set_out; /* controls fan rotation speed in open-loop mode */ + u8 fan_cmd1; /* 0: FG_PLS_ID0 FG pulses count per revolution + * 0: 2 counts per revolution + * 1: 4 counts per revolution + * 1: PWM_POLARITY 1: negative_duty + * 0: positive_duty + * 2,3: [FG_CLOCK_ID0, FG_CLK_ID1] + * 00: Divide fan clock by 1 + * 01: Divide fan clock by 2 + * 10: Divide fan clock by 4 + * 11: Divide fan clock by 8 + * 4: FAN_MODE 1:closed-loop, 0:open-loop + * 5: OUT_MODE 1:PWM, 0:DC + * 6: DET_FAN_OOC enable "fan ooc" status + * 7: DET_FAN_FAIL enable "fan fail" status + */ + u8 fan_cmd2; /* 0,1: FAN_STARTV 0,1,2,3 -> 0,32,64,96 dac_code + * 2,3: FG_GEAR_MODE + * 00: multiplier = 1 + * 01: multiplier = 2 + * 10: multiplier = 4 + * 4: Mask ALERT# (g763 only) + */ +}; + +/* + * Convert count value from fan controller register (FAN_SET_CNT) into fan + * speed RPM value. Note that the datasheet documents a basic formula; + * influence of additional parameters (fan clock divisor, fan gear mode) + * have been infered from examples in the datasheet and tests. + */ +static inline unsigned int rpm_from_cnt(u8 cnt, u32 clk_freq, u16 p, + u8 clk_div, u8 gear_mult) +{ + if (cnt == 0xff) /* setting cnt to 255 stops the fan */ + return 0; + + return (clk_freq * 30 * gear_mult) / ((cnt ? cnt : 1) * p * clk_div); +} + +/* + * Convert fan RPM value from sysfs into count value for fan controller + * register (FAN_SET_CNT). + */ +static inline unsigned char cnt_from_rpm(u32 rpm, u32 clk_freq, u16 p, + u8 clk_div, u8 gear_mult) +{ + if (!rpm) /* to stop the fan, set cnt to 255 */ + return 0xff; + + return clamp_val(((clk_freq * 30 * gear_mult) / (rpm * p * clk_div)), + 0, 255); +} + +/* helper to grab and cache data, at most one time per second */ +static struct g762_data *g762_update_client(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct g762_data *data = i2c_get_clientdata(client); + int ret = 0; + + mutex_lock(&data->update_lock); + if (time_before(jiffies, data->last_updated + G762_UPDATE_INTERVAL) && + likely(data->valid)) + goto out; + + ret = i2c_smbus_read_byte_data(client, G762_REG_SET_CNT); + if (ret < 0) + goto out; + data->set_cnt = ret; + + ret = i2c_smbus_read_byte_data(client, G762_REG_ACT_CNT); + if (ret < 0) + goto out; + data->act_cnt = ret; + + ret = i2c_smbus_read_byte_data(client, G762_REG_FAN_STA); + if (ret < 0) + goto out; + data->fan_sta = ret; + + ret = i2c_smbus_read_byte_data(client, G762_REG_SET_OUT); + if (ret < 0) + goto out; + data->set_out = ret; + + ret = i2c_smbus_read_byte_data(client, G762_REG_FAN_CMD1); + if (ret < 0) + goto out; + data->fan_cmd1 = ret; + + ret = i2c_smbus_read_byte_data(client, G762_REG_FAN_CMD2); + if (ret < 0) + goto out; + data->fan_cmd2 = ret; + + data->last_updated = jiffies; + data->valid = true; + out: + mutex_unlock(&data->update_lock); + + if (ret < 0) /* upon error, encode it in return value */ + data = ERR_PTR(ret); + + return data; +} + +/* helpers for writing hardware parameters */ + +/* + * Set input clock frequency received on CLK pin of the chip. Accepted values + * are between 0 and 0xffffff. If zero is given, then default frequency + * (32,768Hz) is used. Note that clock frequency is a characteristic of the + * system but an internal parameter, i.e. value is not passed to the device. + */ +static int do_set_clk_freq(struct device *dev, unsigned long val) +{ + struct i2c_client *client = to_i2c_client(dev); + struct g762_data *data = i2c_get_clientdata(client); + + if (val > 0xffffff) + return -EINVAL; + if (!val) + val = 32768; + + data->clk_freq = val; + + return 0; +} + +/* Set pwm mode. Accepts either 0 (PWM mode) or 1 (DC mode) */ +static int do_set_pwm_mode(struct device *dev, unsigned long val) +{ + struct i2c_client *client = to_i2c_client(dev); + struct g762_data *data = g762_update_client(dev); + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + switch (val) { + case G762_OUT_MODE_PWM: + data->fan_cmd1 |= G762_REG_FAN_CMD1_OUT_MODE; + break; + case G762_OUT_MODE_DC: + data->fan_cmd1 &= ~G762_REG_FAN_CMD1_OUT_MODE; + break; + default: + ret = -EINVAL; + goto out; + } + ret = i2c_smbus_write_byte_data(client, G762_REG_FAN_CMD1, + data->fan_cmd1); + data->valid = false; + out: + mutex_unlock(&data->update_lock); + + return ret; +} + +/* Set fan clock divisor. Accepts either 1, 2, 4 or 8. */ +static int do_set_fan_div(struct device *dev, unsigned long val) +{ + struct i2c_client *client = to_i2c_client(dev); + struct g762_data *data = g762_update_client(dev); + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + switch (val) { + case 1: + data->fan_cmd1 &= ~G762_REG_FAN_CMD1_CLK_DIV_ID0; + data->fan_cmd1 &= ~G762_REG_FAN_CMD1_CLK_DIV_ID1; + break; + case 2: + data->fan_cmd1 |= G762_REG_FAN_CMD1_CLK_DIV_ID0; + data->fan_cmd1 &= ~G762_REG_FAN_CMD1_CLK_DIV_ID1; + break; + case 4: + data->fan_cmd1 &= ~G762_REG_FAN_CMD1_CLK_DIV_ID0; + data->fan_cmd1 |= G762_REG_FAN_CMD1_CLK_DIV_ID1; + break; + case 8: + data->fan_cmd1 |= G762_REG_FAN_CMD1_CLK_DIV_ID0; + data->fan_cmd1 |= G762_REG_FAN_CMD1_CLK_DIV_ID1; + break; + default: + ret = -EINVAL; + goto out; + } + ret = i2c_smbus_write_byte_data(client, G762_REG_FAN_CMD1, + data->fan_cmd1); + data->valid = false; + out: + mutex_unlock(&data->update_lock); + + return ret; +} + +/* Set fan gear mode. Accepts either 0, 1 or 2. */ +static int do_set_fan_gear_mode(struct device *dev, unsigned long val) +{ + struct i2c_client *client = to_i2c_client(dev); + struct g762_data *data = g762_update_client(dev); + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + switch (val) { + case 0: + data->fan_cmd2 &= ~G762_REG_FAN_CMD2_GEAR_MODE_0; + data->fan_cmd2 &= ~G762_REG_FAN_CMD2_GEAR_MODE_1; + break; + case 1: + data->fan_cmd2 |= G762_REG_FAN_CMD2_GEAR_MODE_0; + data->fan_cmd2 &= ~G762_REG_FAN_CMD2_GEAR_MODE_1; + break; + case 2: + data->fan_cmd2 &= ~G762_REG_FAN_CMD2_GEAR_MODE_0; + data->fan_cmd2 |= G762_REG_FAN_CMD2_GEAR_MODE_1; + break; + default: + ret = -EINVAL; + goto out; + } + ret = i2c_smbus_write_byte_data(client, G762_REG_FAN_CMD2, + data->fan_cmd2); + data->valid = false; + out: + mutex_unlock(&data->update_lock); + + return ret; +} + +/* Set number of fan pulses per revolution. Accepts either 2 or 4. */ +static int do_set_fan_pulses(struct device *dev, unsigned long val) +{ + struct i2c_client *client = to_i2c_client(dev); + struct g762_data *data = g762_update_client(dev); + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + switch (val) { + case 2: + data->fan_cmd1 &= ~G762_REG_FAN_CMD1_PULSE_PER_REV; + break; + case 4: + data->fan_cmd1 |= G762_REG_FAN_CMD1_PULSE_PER_REV; + break; + default: + ret = -EINVAL; + goto out; + } + ret = i2c_smbus_write_byte_data(client, G762_REG_FAN_CMD1, + data->fan_cmd1); + data->valid = false; + out: + mutex_unlock(&data->update_lock); + + return ret; +} + +/* Set fan mode. Accepts either 1 (open-loop) or 2 (closed-loop). */ +static int do_set_pwm_enable(struct device *dev, unsigned long val) +{ + struct i2c_client *client = to_i2c_client(dev); + struct g762_data *data = g762_update_client(dev); + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + switch (val) { + case G762_FAN_MODE_CLOSED_LOOP: + data->fan_cmd1 |= G762_REG_FAN_CMD1_FAN_MODE; + break; + case G762_FAN_MODE_OPEN_LOOP: + data->fan_cmd1 &= ~G762_REG_FAN_CMD1_FAN_MODE; + /* + * BUG FIX: if SET_CNT register value is 255 then, for some + * unknown reason, fan will not rotate as expected, no matter + * the value of SET_OUT (to be specific, this seems to happen + * only in PWM mode). To workaround this bug, we give SET_CNT + * value of 254 if it is 255 when switching to open-loop. + */ + if (data->set_cnt == 0xff) + i2c_smbus_write_byte_data(client, G762_REG_SET_CNT, + 254); + break; + default: + ret = -EINVAL; + goto out; + } + + ret = i2c_smbus_write_byte_data(client, G762_REG_FAN_CMD1, + data->fan_cmd1); + data->valid = false; + out: + mutex_unlock(&data->update_lock); + + return ret; +} + +/* Set PWM polarity. Accepts either 0 (positive duty) or 1 (negative duty) */ +static int do_set_pwm_polarity(struct device *dev, unsigned long val) +{ + struct i2c_client *client = to_i2c_client(dev); + struct g762_data *data = g762_update_client(dev); + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + switch (val) { + case G762_PWM_POLARITY_POSITIVE: + data->fan_cmd1 &= ~G762_REG_FAN_CMD1_PWM_POLARITY; + break; + case G762_PWM_POLARITY_NEGATIVE: + data->fan_cmd1 |= G762_REG_FAN_CMD1_PWM_POLARITY; + break; + default: + ret = -EINVAL; + goto out; + } + ret = i2c_smbus_write_byte_data(client, G762_REG_FAN_CMD1, + data->fan_cmd1); + data->valid = false; + out: + mutex_unlock(&data->update_lock); + + return ret; +} + +/* + * Set pwm value. Accepts values between 0 (stops the fan) and + * 255 (full speed). This only makes sense in open-loop mode. + */ +static int do_set_pwm(struct device *dev, unsigned long val) +{ + struct i2c_client *client = to_i2c_client(dev); + struct g762_data *data = i2c_get_clientdata(client); + int ret; + + if (val > 255) + return -EINVAL; + + mutex_lock(&data->update_lock); + ret = i2c_smbus_write_byte_data(client, G762_REG_SET_OUT, val); + data->valid = false; + mutex_unlock(&data->update_lock); + + return ret; +} + +/* + * Set fan RPM value. Can be called both in closed and open-loop mode + * but effect will only be seen after closed-loop mode is configured. + */ +static int do_set_fan_target(struct device *dev, unsigned long val) +{ + struct i2c_client *client = to_i2c_client(dev); + struct g762_data *data = g762_update_client(dev); + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + data->set_cnt = cnt_from_rpm(val, data->clk_freq, + G762_PULSE_FROM_REG(data->fan_cmd1), + G762_CLKDIV_FROM_REG(data->fan_cmd1), + G762_GEARMULT_FROM_REG(data->fan_cmd2)); + ret = i2c_smbus_write_byte_data(client, G762_REG_SET_CNT, + data->set_cnt); + data->valid = false; + mutex_unlock(&data->update_lock); + + return ret; +} + +/* Set fan startup voltage. Accepted values are either 0, 1, 2 or 3. */ +static int do_set_fan_startv(struct device *dev, unsigned long val) +{ + struct i2c_client *client = to_i2c_client(dev); + struct g762_data *data = g762_update_client(dev); + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + switch (val) { + case 0: + data->fan_cmd2 &= ~G762_REG_FAN_CMD2_FAN_STARTV_0; + data->fan_cmd2 &= ~G762_REG_FAN_CMD2_FAN_STARTV_1; + break; + case 1: + data->fan_cmd2 |= G762_REG_FAN_CMD2_FAN_STARTV_0; + data->fan_cmd2 &= ~G762_REG_FAN_CMD2_FAN_STARTV_1; + break; + case 2: + data->fan_cmd2 &= ~G762_REG_FAN_CMD2_FAN_STARTV_0; + data->fan_cmd2 |= G762_REG_FAN_CMD2_FAN_STARTV_1; + break; + case 3: + data->fan_cmd2 |= G762_REG_FAN_CMD2_FAN_STARTV_0; + data->fan_cmd2 |= G762_REG_FAN_CMD2_FAN_STARTV_1; + break; + default: + ret = -EINVAL; + goto out; + } + ret = i2c_smbus_write_byte_data(client, G762_REG_FAN_CMD2, + data->fan_cmd2); + data->valid = false; + out: + mutex_unlock(&data->update_lock); + + return ret; +} + +/* + * Helper to import hardware characteristics from .dts file and push + * those to the chip. + */ + +#ifdef CONFIG_OF +static struct of_device_id g762_dt_match[] = { + { .compatible = "gmt,g762" }, + { .compatible = "gmt,g763" }, + { }, +}; + +/* + * Grab clock (a required property), enable it, get (fixed) clock frequency + * and store it. Note: upon success, clock has been prepared and enabled; it + * must later be unprepared and disabled (e.g. during module unloading) by a + * call to g762_of_clock_disable(). Note that a reference to clock is kept + * in our private data structure to be used in this function. + */ +static int g762_of_clock_enable(struct i2c_client *client) +{ + struct g762_data *data; + unsigned long clk_freq; + struct clk *clk; + int ret; + + if (!client->dev.of_node) + return 0; + + clk = of_clk_get(client->dev.of_node, 0); + if (IS_ERR(clk)) { + dev_err(&client->dev, "failed to get clock\n"); + return PTR_ERR(clk); + } + + ret = clk_prepare_enable(clk); + if (ret) { + dev_err(&client->dev, "failed to enable clock\n"); + goto clk_put; + } + + clk_freq = clk_get_rate(clk); + ret = do_set_clk_freq(&client->dev, clk_freq); + if (ret) { + dev_err(&client->dev, "invalid clock freq %lu\n", clk_freq); + goto clk_unprep; + } + + data = i2c_get_clientdata(client); + data->clk = clk; + + return 0; + + clk_unprep: + clk_disable_unprepare(clk); + + clk_put: + clk_put(clk); + + return ret; +} + +static void g762_of_clock_disable(struct i2c_client *client) +{ + struct g762_data *data = i2c_get_clientdata(client); + + if (!data->clk) + return; + + clk_disable_unprepare(data->clk); + clk_put(data->clk); +} + +static int g762_of_prop_import_one(struct i2c_client *client, + const char *pname, + int (*psetter)(struct device *dev, + unsigned long val)) +{ + const __be32 *prop; + int len, ret; + u32 pval; + + prop = of_get_property(client->dev.of_node, pname, &len); + if (!prop || len != sizeof(u32)) + return 0; + + pval = be32_to_cpu(prop[0]); + dev_dbg(&client->dev, "found %s (%d)\n", pname, pval); + ret = (*psetter)(&client->dev, pval); + if (ret) + dev_err(&client->dev, "unable to set %s (%d)\n", pname, pval); + + return ret; +} + +static int g762_of_prop_import(struct i2c_client *client) +{ + int ret; + + if (!client->dev.of_node) + return 0; + + ret = g762_of_prop_import_one(client, "fan_gear_mode", + do_set_fan_gear_mode); + if (ret) + return ret; + + ret = g762_of_prop_import_one(client, "pwm_polarity", + do_set_pwm_polarity); + if (ret) + return ret; + + return g762_of_prop_import_one(client, "fan_startv", + do_set_fan_startv); +} + +#else +static int g762_of_prop_import(struct i2c_client *client) +{ + return 0; +} + +static int g762_of_clock_enable(struct i2c_client *client) +{ + return 0; +} + +static void g762_of_clock_disable(struct i2c_client *client) { } +#endif + +/* + * Helper to import hardware characteristics from .dts file and push + * those to the chip. + */ + +static int g762_pdata_prop_import(struct i2c_client *client) +{ + struct g762_platform_data *pdata = client->dev.platform_data; + int ret; + + if (!pdata) + return 0; + + ret = do_set_fan_gear_mode(&client->dev, pdata->fan_gear_mode); + if (ret) + return ret; + + ret = do_set_pwm_polarity(&client->dev, pdata->pwm_polarity); + if (ret) + return ret; + + ret = do_set_fan_startv(&client->dev, pdata->fan_startv); + if (ret) + return ret; + + return do_set_clk_freq(&client->dev, pdata->clk_freq); +} + +/* + * sysfs attributes + */ + +/* + * Read function for fan1_input sysfs file. Return current fan RPM value, or + * 0 if fan is out of control. + */ +static ssize_t get_fan_rpm(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct g762_data *data = g762_update_client(dev); + unsigned int rpm = 0; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + /* reverse logic: fan out of control reporting is enabled low */ + if (data->fan_sta & G762_REG_FAN_STA_OOC) { + rpm = rpm_from_cnt(data->act_cnt, data->clk_freq, + G762_PULSE_FROM_REG(data->fan_cmd1), + G762_CLKDIV_FROM_REG(data->fan_cmd1), + G762_GEARMULT_FROM_REG(data->fan_cmd2)); + } + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%u\n", rpm); +} + +/* + * Read and write functions for pwm1_mode sysfs file. Get and set fan speed + * control mode i.e. PWM (1) or DC (0). + */ +static ssize_t get_pwm_mode(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct g762_data *data = g762_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", + !!(data->fan_cmd1 & G762_REG_FAN_CMD1_OUT_MODE)); +} + +static ssize_t set_pwm_mode(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + ret = do_set_pwm_mode(dev, val); + if (ret < 0) + return ret; + + return count; +} + +/* + * Read and write functions for fan1_div sysfs file. Get and set fan + * controller prescaler value + */ +static ssize_t get_fan_div(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct g762_data *data = g762_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", G762_CLKDIV_FROM_REG(data->fan_cmd1)); +} + +static ssize_t set_fan_div(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + ret = do_set_fan_div(dev, val); + if (ret < 0) + return ret; + + return count; +} + +/* + * Read and write functions for fan1_pulses sysfs file. Get and set number + * of tachometer pulses per fan revolution. + */ +static ssize_t get_fan_pulses(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct g762_data *data = g762_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", G762_PULSE_FROM_REG(data->fan_cmd1)); +} + +static ssize_t set_fan_pulses(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + ret = do_set_fan_pulses(dev, val); + if (ret < 0) + return ret; + + return count; +} + +/* + * Read and write functions for pwm1_enable. Get and set fan speed control mode + * (i.e. closed or open-loop). + * + * Following documentation about hwmon's sysfs interface, a pwm1_enable node + * should accept followings: + * + * 0 : no fan speed control (i.e. fan at full speed) + * 1 : manual fan speed control enabled (use pwm[1-*]) (open-loop) + * 2+: automatic fan speed control enabled (use fan[1-*]_target) (closed-loop) + * + * but we do not accept 0 as this mode is not natively supported by the chip + * and it is not emulated by g762 driver. -EINVAL is returned in this case. + */ +static ssize_t get_pwm_enable(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct g762_data *data = g762_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", + (!!(data->fan_cmd1 & G762_REG_FAN_CMD1_FAN_MODE)) + 1); +} + +static ssize_t set_pwm_enable(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + ret = do_set_pwm_enable(dev, val); + if (ret < 0) + return ret; + + return count; +} + +/* + * Read and write functions for pwm1 sysfs file. Get and set pwm value + * (which affects fan speed) in open-loop mode. 0 stops the fan and 255 + * makes it run at full speed. + */ +static ssize_t get_pwm(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct g762_data *data = g762_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->set_out); +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + ret = do_set_pwm(dev, val); + if (ret < 0) + return ret; + + return count; +} + +/* + * Read and write function for fan1_target sysfs file. Get/set the fan speed in + * closed-loop mode. Speed is given as a RPM value; then the chip will regulate + * the fan speed using pulses from fan tachometer. + * + * Refer to rpm_from_cnt() implementation above to get info about count number + * calculation. + * + * Also note that due to rounding errors it is possible that you don't read + * back exactly the value you have set. + */ +static ssize_t get_fan_target(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct g762_data *data = g762_update_client(dev); + unsigned int rpm; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + rpm = rpm_from_cnt(data->set_cnt, data->clk_freq, + G762_PULSE_FROM_REG(data->fan_cmd1), + G762_CLKDIV_FROM_REG(data->fan_cmd1), + G762_GEARMULT_FROM_REG(data->fan_cmd2)); + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%u\n", rpm); +} + +static ssize_t set_fan_target(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + ret = do_set_fan_target(dev, val); + if (ret < 0) + return ret; + + return count; +} + +/* read function for fan1_fault sysfs file. */ +static ssize_t get_fan_failure(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct g762_data *data = g762_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%u\n", !!(data->fan_sta & G762_REG_FAN_STA_FAIL)); +} + +/* + * read function for fan1_alarm sysfs file. Note that OOC condition is + * enabled low + */ +static ssize_t get_fan_ooc(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct g762_data *data = g762_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%u\n", !(data->fan_sta & G762_REG_FAN_STA_OOC)); +} + +static DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm, set_pwm); +static DEVICE_ATTR(pwm1_mode, S_IWUSR | S_IRUGO, get_pwm_mode, set_pwm_mode); +static DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, + get_pwm_enable, set_pwm_enable); +static DEVICE_ATTR(fan1_input, S_IRUGO, get_fan_rpm, NULL); +static DEVICE_ATTR(fan1_alarm, S_IRUGO, get_fan_ooc, NULL); +static DEVICE_ATTR(fan1_fault, S_IRUGO, get_fan_failure, NULL); +static DEVICE_ATTR(fan1_target, S_IWUSR | S_IRUGO, + get_fan_target, set_fan_target); +static DEVICE_ATTR(fan1_div, S_IWUSR | S_IRUGO, get_fan_div, set_fan_div); +static DEVICE_ATTR(fan1_pulses, S_IWUSR | S_IRUGO, + get_fan_pulses, set_fan_pulses); + +/* Driver data */ +static struct attribute *g762_attributes[] = { + &dev_attr_fan1_input.attr, + &dev_attr_fan1_alarm.attr, + &dev_attr_fan1_fault.attr, + &dev_attr_fan1_target.attr, + &dev_attr_fan1_div.attr, + &dev_attr_fan1_pulses.attr, + &dev_attr_pwm1.attr, + &dev_attr_pwm1_mode.attr, + &dev_attr_pwm1_enable.attr, + NULL +}; + +static const struct attribute_group g762_group = { + .attrs = g762_attributes, +}; + +/* + * Enable both fan failure detection and fan out of control protection. The + * function does not protect change/access to data structure; it must thus + * only be called during initialization. + */ +static inline int g762_fan_init(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct g762_data *data = g762_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + data->fan_cmd1 |= G762_REG_FAN_CMD1_DET_FAN_FAIL; + data->fan_cmd1 |= G762_REG_FAN_CMD1_DET_FAN_OOC; + data->valid = false; + + return i2c_smbus_write_byte_data(client, G762_REG_FAN_CMD1, + data->fan_cmd1); +} + +static int g762_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct g762_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + data = devm_kzalloc(&client->dev, sizeof(struct g762_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + data->client = client; + mutex_init(&data->update_lock); + + /* Enable fan failure detection and fan out of control protection */ + ret = g762_fan_init(&client->dev); + if (ret) + return ret; + + /* Get configuration via DT ... */ + ret = g762_of_clock_enable(client); + if (ret) + return ret; + ret = g762_of_prop_import(client); + if (ret) + goto clock_dis; + /* ... or platform_data */ + ret = g762_pdata_prop_import(client); + if (ret) + goto clock_dis; + + /* Register sysfs hooks */ + ret = sysfs_create_group(&client->dev.kobj, &g762_group); + if (ret) + goto clock_dis; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + goto sysfs_rem; + } + + return 0; + + sysfs_rem: + sysfs_remove_group(&client->dev.kobj, &g762_group); + + clock_dis: + g762_of_clock_disable(client); + + return ret; +} + +static int g762_remove(struct i2c_client *client) +{ + struct g762_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &g762_group); + g762_of_clock_disable(client); + + return 0; +} + +static struct i2c_driver g762_driver = { + .driver = { + .name = DRVNAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(g762_dt_match), + }, + .probe = g762_probe, + .remove = g762_remove, + .id_table = g762_id, +}; + +module_i2c_driver(g762_driver); + +MODULE_AUTHOR("Arnaud EBALARD "); +MODULE_DESCRIPTION("GMT G762/G763 driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/platform_data/g762.h b/include/linux/platform_data/g762.h new file mode 100644 index 000000000000..d3c51283764d --- /dev/null +++ b/include/linux/platform_data/g762.h @@ -0,0 +1,37 @@ +/* + * Platform data structure for g762 fan controller driver + * + * Copyright (C) 2013, Arnaud EBALARD + * + * 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_PLATFORM_DATA_G762_H__ +#define __LINUX_PLATFORM_DATA_G762_H__ + +/* + * Following structure can be used to set g762 driver platform specific data + * during board init. Note that passing a sparse structure is possible but + * will result in non-specified attributes to be set to default value, hence + * overloading those installed during boot (e.g. by u-boot). + */ + +struct g762_platform_data { + u32 fan_startv; + u32 fan_gear_mode; + u32 pwm_polarity; + u32 clk_freq; +}; + +#endif /* __LINUX_PLATFORM_DATA_G762_H__ */ -- cgit v1.2.3 From 3b81a6809480f3fc7d9d06562704c8df18ecec00 Mon Sep 17 00:00:00 2001 From: Franky Lin Date: Wed, 26 Jun 2013 14:20:18 +0200 Subject: brcmfmac: add broken scatter-gather DMA support DMA engine of some old SDIO host controllers require block size alignment for data length of each scatterlist item. This patch introduces an intermediate buffer list to support this kind of platform. It decreases the throughput because of an extra memcpy in critical data path. So don't turn this on unless it's necessary. 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 | 78 ++++++++++++++++++++---- include/linux/platform_data/brcmfmac-sdio.h | 5 ++ 2 files changed, 72 insertions(+), 11 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 70cd0e9cd52b..e3f3c48f86d4 100644 --- a/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c +++ b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c @@ -331,10 +331,11 @@ static int brcmf_sdio_buffrw(struct brcmf_sdio_dev *sdiodev, uint fn, bool write, u32 addr, struct sk_buff_head *pktlist) { unsigned int req_sz, func_blk_sz, sg_cnt, sg_data_sz, pkt_offset; - unsigned int max_blks, max_req_sz; + unsigned int max_blks, max_req_sz, orig_offset, dst_offset; unsigned short max_seg_sz, seg_sz; - unsigned char *pkt_data; - struct sk_buff *pkt_next = NULL; + unsigned char *pkt_data, *orig_data, *dst_data; + struct sk_buff *pkt_next = NULL, *local_pkt_next; + struct sk_buff_head local_list, *target_list; struct mmc_request mmc_req; struct mmc_command mmc_cmd; struct mmc_data mmc_dat; @@ -371,6 +372,32 @@ static int brcmf_sdio_buffrw(struct brcmf_sdio_dev *sdiodev, uint fn, req_sz); } + target_list = pktlist; + /* for host with broken sg support, prepare a page aligned list */ + __skb_queue_head_init(&local_list); + if (sdiodev->pdata && sdiodev->pdata->broken_sg_support && !write) { + req_sz = 0; + skb_queue_walk(pktlist, pkt_next) + req_sz += pkt_next->len; + req_sz = ALIGN(req_sz, sdiodev->func[fn]->cur_blksize); + while (req_sz > PAGE_SIZE) { + pkt_next = brcmu_pkt_buf_get_skb(PAGE_SIZE); + if (pkt_next == NULL) { + ret = -ENOMEM; + goto exit; + } + __skb_queue_tail(&local_list, pkt_next); + req_sz -= PAGE_SIZE; + } + pkt_next = brcmu_pkt_buf_get_skb(req_sz); + if (pkt_next == NULL) { + ret = -ENOMEM; + goto exit; + } + __skb_queue_tail(&local_list, pkt_next); + target_list = &local_list; + } + host = sdiodev->func[fn]->card->host; func_blk_sz = sdiodev->func[fn]->cur_blksize; /* Blocks per command is limited by host count, host transfer @@ -380,13 +407,15 @@ static int brcmf_sdio_buffrw(struct brcmf_sdio_dev *sdiodev, uint fn, max_req_sz = min_t(unsigned int, host->max_req_size, max_blks * func_blk_sz); max_seg_sz = min_t(unsigned short, host->max_segs, SG_MAX_SINGLE_ALLOC); - max_seg_sz = min_t(unsigned short, max_seg_sz, pktlist->qlen); - seg_sz = pktlist->qlen; + max_seg_sz = min_t(unsigned short, max_seg_sz, target_list->qlen); + seg_sz = target_list->qlen; pkt_offset = 0; - pkt_next = pktlist->next; + pkt_next = target_list->next; - if (sg_alloc_table(&st, max_seg_sz, GFP_KERNEL)) - return -ENOMEM; + if (sg_alloc_table(&st, max_seg_sz, GFP_KERNEL)) { + ret = -ENOMEM; + goto exit; + } while (seg_sz) { req_sz = 0; @@ -396,7 +425,7 @@ static int brcmf_sdio_buffrw(struct brcmf_sdio_dev *sdiodev, uint fn, memset(&mmc_dat, 0, sizeof(struct mmc_data)); sgl = st.sgl; /* prep sg table */ - while (pkt_next != (struct sk_buff *)pktlist) { + while (pkt_next != (struct sk_buff *)target_list) { pkt_data = pkt_next->data + pkt_offset; sg_data_sz = pkt_next->len - pkt_offset; if (sg_data_sz > host->max_seg_size) @@ -423,8 +452,8 @@ static int brcmf_sdio_buffrw(struct brcmf_sdio_dev *sdiodev, uint fn, if (req_sz % func_blk_sz != 0) { brcmf_err("sg request length %u is not %u aligned\n", req_sz, func_blk_sz); - sg_free_table(&st); - return -ENOTBLK; + ret = -ENOTBLK; + goto exit; } mmc_dat.sg = st.sgl; mmc_dat.sg_len = sg_cnt; @@ -457,7 +486,34 @@ static int brcmf_sdio_buffrw(struct brcmf_sdio_dev *sdiodev, uint fn, } } + if (sdiodev->pdata && sdiodev->pdata->broken_sg_support && !write) { + local_pkt_next = local_list.next; + orig_offset = 0; + skb_queue_walk(pktlist, pkt_next) { + dst_offset = 0; + do { + req_sz = local_pkt_next->len - orig_offset; + req_sz = min_t(uint, pkt_next->len - dst_offset, + req_sz); + orig_data = local_pkt_next->data + orig_offset; + dst_data = pkt_next->data + dst_offset; + memcpy(dst_data, orig_data, req_sz); + orig_offset += req_sz; + dst_offset += req_sz; + if (orig_offset == local_pkt_next->len) { + orig_offset = 0; + local_pkt_next = local_pkt_next->next; + } + if (dst_offset == pkt_next->len) + break; + } while (!skb_queue_empty(&local_list)); + } + } + +exit: sg_free_table(&st); + while ((pkt_next = __skb_dequeue(&local_list)) != NULL) + brcmu_pkt_buf_free_skb(pkt_next); return ret; } diff --git a/include/linux/platform_data/brcmfmac-sdio.h b/include/linux/platform_data/brcmfmac-sdio.h index 1ade657d5fc1..b7174998c24a 100644 --- a/include/linux/platform_data/brcmfmac-sdio.h +++ b/include/linux/platform_data/brcmfmac-sdio.h @@ -90,6 +90,10 @@ void __init brcmfmac_init_pdata(void) * oob_irq_nr, oob_irq_flags: the OOB interrupt information. The values are * used for registering the irq using request_irq function. * + * broken_sg_support: flag for broken sg list support of SDIO host controller. + * Set this to true if the SDIO host controller has higher align requirement + * than 32 bytes for each scatterlist item. + * * 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. @@ -116,6 +120,7 @@ struct brcmfmac_sdio_platform_data { bool oob_irq_supported; unsigned int oob_irq_nr; unsigned long oob_irq_flags; + bool broken_sg_support; void (*power_on)(void); void (*power_off)(void); void (*reset)(void); -- cgit v1.2.3 From 17fb1563d69b63fe7a79570fe870cf7e530cd2cd Mon Sep 17 00:00:00 2001 From: Ferruh Yigit Date: Sun, 30 Jun 2013 18:49:02 -0700 Subject: Input: cyttsp4 - add core driver for Cypress TMA4XX touchscreen devices Cypress TrueTouch(tm) Standard Product controllers, Generetion4 devices, Core driver. Core driver is interface between host and TTSP controller and processes data sent by controller. Responsibilities of module are IRQ handling, reading system information registers and sending multi-touch protocol type B events. Signed-off-by: Ferruh Yigit Acked-by: Greg Kroah-Hartman Acked-by: Javier Martinez Canillas Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/Kconfig | 12 + drivers/input/touchscreen/Makefile | 1 + drivers/input/touchscreen/cyttsp4_core.c | 2169 ++++++++++++++++++++++++++++++ drivers/input/touchscreen/cyttsp4_core.h | 472 +++++++ include/linux/platform_data/cyttsp4.h | 76 ++ 5 files changed, 2730 insertions(+) create mode 100644 drivers/input/touchscreen/cyttsp4_core.c create mode 100644 drivers/input/touchscreen/cyttsp4_core.h create mode 100644 include/linux/platform_data/cyttsp4.h (limited to 'include/linux/platform_data') diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index f9a5fd89bc02..66df8df88140 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -167,6 +167,18 @@ config TOUCHSCREEN_CYTTSP_SPI To compile this driver as a module, choose M here: the module will be called cyttsp_spi. +config TOUCHSCREEN_CYTTSP4_CORE + tristate "Cypress TrueTouch Gen4 Touchscreen Driver" + help + Core driver for Cypress TrueTouch(tm) Standard Product + Generation4 touchscreen controllers. + + Say Y here if you have a Cypress Gen4 touchscreen. + + If unsure, say N. + + To compile this driver as a module, choose M here. + config TOUCHSCREEN_DA9034 tristate "Touchscreen support for Dialog Semiconductor DA9034" depends on PMIC_DA903X diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 2b378b12ee6b..f44500674c32 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_TOUCHSCREEN_CY8CTMG110) += cy8ctmg110_ts.o obj-$(CONFIG_TOUCHSCREEN_CYTTSP_CORE) += cyttsp_core.o obj-$(CONFIG_TOUCHSCREEN_CYTTSP_I2C) += cyttsp_i2c.o cyttsp_i2c_common.o obj-$(CONFIG_TOUCHSCREEN_CYTTSP_SPI) += cyttsp_spi.o +obj-$(CONFIG_TOUCHSCREEN_CYTTSP4_CORE) += cyttsp4_core.o obj-$(CONFIG_TOUCHSCREEN_DA9034) += da9034-ts.o obj-$(CONFIG_TOUCHSCREEN_DA9052) += da9052_tsi.o obj-$(CONFIG_TOUCHSCREEN_DYNAPRO) += dynapro.o diff --git a/drivers/input/touchscreen/cyttsp4_core.c b/drivers/input/touchscreen/cyttsp4_core.c new file mode 100644 index 000000000000..963da052c15e --- /dev/null +++ b/drivers/input/touchscreen/cyttsp4_core.c @@ -0,0 +1,2169 @@ +/* + * cyttsp4_core.c + * Cypress TrueTouch(TM) Standard Product V4 Core driver module. + * For use with Cypress Txx4xx parts. + * Supported parts include: + * TMA4XX + * TMA1036 + * + * Copyright (C) 2012 Cypress Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Cypress Semiconductor at www.cypress.com + * + */ + +#include "cyttsp4_core.h" +#include +#include +#include +#include +#include +#include +#include + +/* Timeout in ms. */ +#define CY_CORE_REQUEST_EXCLUSIVE_TIMEOUT 500 +#define CY_CORE_SLEEP_REQUEST_EXCLUSIVE_TIMEOUT 5000 +#define CY_CORE_MODE_CHANGE_TIMEOUT 1000 +#define CY_CORE_RESET_AND_WAIT_TIMEOUT 500 +#define CY_CORE_WAKEUP_TIMEOUT 500 + +#define CY_CORE_STARTUP_RETRY_COUNT 3 + +static const u8 ldr_exit[] = { + 0xFF, 0x01, 0x3B, 0x00, 0x00, 0x4F, 0x6D, 0x17 +}; + +static const u8 ldr_err_app[] = { + 0x01, 0x02, 0x00, 0x00, 0x55, 0xDD, 0x17 +}; + +static inline size_t merge_bytes(u8 high, u8 low) +{ + return (high << 8) + low; +} + +#ifdef VERBOSE_DEBUG +static void cyttsp4_pr_buf(struct device *dev, u8 *pr_buf, u8 *dptr, int size, + const char *data_name) +{ + int i, k; + const char fmt[] = "%02X "; + int max; + + if (!size) + return; + + max = (CY_MAX_PRBUF_SIZE - 1) - sizeof(CY_PR_TRUNCATED); + + pr_buf[0] = 0; + for (i = k = 0; i < size && k < max; i++, k += 3) + scnprintf(pr_buf + k, CY_MAX_PRBUF_SIZE, fmt, dptr[i]); + + dev_vdbg(dev, "%s: %s[0..%d]=%s%s\n", __func__, data_name, size - 1, + pr_buf, size <= max ? "" : CY_PR_TRUNCATED); +} +#else +#define cyttsp4_pr_buf(dev, pr_buf, dptr, size, data_name) do { } while (0) +#endif + +static int cyttsp4_load_status_regs(struct cyttsp4 *cd) +{ + struct cyttsp4_sysinfo *si = &cd->sysinfo; + struct device *dev = cd->dev; + int rc; + + rc = cyttsp4_adap_read(cd, CY_REG_BASE, si->si_ofs.mode_size, + si->xy_mode); + if (rc < 0) + dev_err(dev, "%s: fail read mode regs r=%d\n", + __func__, rc); + else + cyttsp4_pr_buf(dev, cd->pr_buf, si->xy_mode, + si->si_ofs.mode_size, "xy_mode"); + + return rc; +} + +static int cyttsp4_handshake(struct cyttsp4 *cd, u8 mode) +{ + u8 cmd = mode ^ CY_HST_TOGGLE; + int rc; + + /* + * Mode change issued, handshaking now will cause endless mode change + * requests, for sync mode modechange will do same with handshake + * */ + if (mode & CY_HST_MODE_CHANGE) + return 0; + + rc = cyttsp4_adap_write(cd, CY_REG_BASE, sizeof(cmd), &cmd); + if (rc < 0) + dev_err(cd->dev, "%s: bus write fail on handshake (ret=%d)\n", + __func__, rc); + + return rc; +} + +static int cyttsp4_hw_soft_reset(struct cyttsp4 *cd) +{ + u8 cmd = CY_HST_RESET; + int rc = cyttsp4_adap_write(cd, CY_REG_BASE, sizeof(cmd), &cmd); + if (rc < 0) { + dev_err(cd->dev, "%s: FAILED to execute SOFT reset\n", + __func__); + return rc; + } + return 0; +} + +static int cyttsp4_hw_hard_reset(struct cyttsp4 *cd) +{ + if (cd->cpdata->xres) { + cd->cpdata->xres(cd->cpdata, cd->dev); + dev_dbg(cd->dev, "%s: execute HARD reset\n", __func__); + return 0; + } + dev_err(cd->dev, "%s: FAILED to execute HARD reset\n", __func__); + return -ENOSYS; +} + +static int cyttsp4_hw_reset(struct cyttsp4 *cd) +{ + int rc = cyttsp4_hw_hard_reset(cd); + if (rc == -ENOSYS) + rc = cyttsp4_hw_soft_reset(cd); + return rc; +} + +/* + * Gets number of bits for a touch filed as parameter, + * sets maximum value for field which is used as bit mask + * and returns number of bytes required for that field + */ +static int cyttsp4_bits_2_bytes(unsigned int nbits, size_t *max) +{ + *max = 1 << nbits; + return (nbits + 7) / 8; +} + +static int cyttsp4_si_data_offsets(struct cyttsp4 *cd) +{ + struct cyttsp4_sysinfo *si = &cd->sysinfo; + int rc = cyttsp4_adap_read(cd, CY_REG_BASE, sizeof(si->si_data), + &si->si_data); + if (rc < 0) { + dev_err(cd->dev, "%s: fail read sysinfo data offsets r=%d\n", + __func__, rc); + return rc; + } + + /* Print sysinfo data offsets */ + cyttsp4_pr_buf(cd->dev, cd->pr_buf, (u8 *)&si->si_data, + sizeof(si->si_data), "sysinfo_data_offsets"); + + /* convert sysinfo data offset bytes into integers */ + + si->si_ofs.map_sz = merge_bytes(si->si_data.map_szh, + si->si_data.map_szl); + si->si_ofs.map_sz = merge_bytes(si->si_data.map_szh, + si->si_data.map_szl); + si->si_ofs.cydata_ofs = merge_bytes(si->si_data.cydata_ofsh, + si->si_data.cydata_ofsl); + si->si_ofs.test_ofs = merge_bytes(si->si_data.test_ofsh, + si->si_data.test_ofsl); + si->si_ofs.pcfg_ofs = merge_bytes(si->si_data.pcfg_ofsh, + si->si_data.pcfg_ofsl); + si->si_ofs.opcfg_ofs = merge_bytes(si->si_data.opcfg_ofsh, + si->si_data.opcfg_ofsl); + si->si_ofs.ddata_ofs = merge_bytes(si->si_data.ddata_ofsh, + si->si_data.ddata_ofsl); + si->si_ofs.mdata_ofs = merge_bytes(si->si_data.mdata_ofsh, + si->si_data.mdata_ofsl); + return rc; +} + +static int cyttsp4_si_get_cydata(struct cyttsp4 *cd) +{ + struct cyttsp4_sysinfo *si = &cd->sysinfo; + int read_offset; + int mfgid_sz, calc_mfgid_sz; + void *p; + int rc; + + si->si_ofs.cydata_size = si->si_ofs.test_ofs - si->si_ofs.cydata_ofs; + dev_dbg(cd->dev, "%s: cydata size: %Zd\n", __func__, + si->si_ofs.cydata_size); + + p = krealloc(si->si_ptrs.cydata, si->si_ofs.cydata_size, GFP_KERNEL); + if (p == NULL) { + dev_err(cd->dev, "%s: fail alloc cydata memory\n", __func__); + return -ENOMEM; + } + si->si_ptrs.cydata = p; + + read_offset = si->si_ofs.cydata_ofs; + + /* Read the CYDA registers up to MFGID field */ + rc = cyttsp4_adap_read(cd, read_offset, + offsetof(struct cyttsp4_cydata, mfgid_sz) + + sizeof(si->si_ptrs.cydata->mfgid_sz), + si->si_ptrs.cydata); + if (rc < 0) { + dev_err(cd->dev, "%s: fail read cydata r=%d\n", + __func__, rc); + return rc; + } + + /* Check MFGID size */ + mfgid_sz = si->si_ptrs.cydata->mfgid_sz; + calc_mfgid_sz = si->si_ofs.cydata_size - sizeof(struct cyttsp4_cydata); + if (mfgid_sz != calc_mfgid_sz) { + dev_err(cd->dev, "%s: mismatch in MFGID size, reported:%d calculated:%d\n", + __func__, mfgid_sz, calc_mfgid_sz); + return -EINVAL; + } + + read_offset += offsetof(struct cyttsp4_cydata, mfgid_sz) + + sizeof(si->si_ptrs.cydata->mfgid_sz); + + /* Read the CYDA registers for MFGID field */ + rc = cyttsp4_adap_read(cd, read_offset, si->si_ptrs.cydata->mfgid_sz, + si->si_ptrs.cydata->mfg_id); + if (rc < 0) { + dev_err(cd->dev, "%s: fail read cydata r=%d\n", + __func__, rc); + return rc; + } + + read_offset += si->si_ptrs.cydata->mfgid_sz; + + /* Read the rest of the CYDA registers */ + rc = cyttsp4_adap_read(cd, read_offset, + sizeof(struct cyttsp4_cydata) + - offsetof(struct cyttsp4_cydata, cyito_idh), + &si->si_ptrs.cydata->cyito_idh); + if (rc < 0) { + dev_err(cd->dev, "%s: fail read cydata r=%d\n", + __func__, rc); + return rc; + } + + cyttsp4_pr_buf(cd->dev, cd->pr_buf, (u8 *)si->si_ptrs.cydata, + si->si_ofs.cydata_size, "sysinfo_cydata"); + return rc; +} + +static int cyttsp4_si_get_test_data(struct cyttsp4 *cd) +{ + struct cyttsp4_sysinfo *si = &cd->sysinfo; + void *p; + int rc; + + si->si_ofs.test_size = si->si_ofs.pcfg_ofs - si->si_ofs.test_ofs; + + p = krealloc(si->si_ptrs.test, si->si_ofs.test_size, GFP_KERNEL); + if (p == NULL) { + dev_err(cd->dev, "%s: fail alloc test memory\n", __func__); + return -ENOMEM; + } + si->si_ptrs.test = p; + + rc = cyttsp4_adap_read(cd, si->si_ofs.test_ofs, si->si_ofs.test_size, + si->si_ptrs.test); + if (rc < 0) { + dev_err(cd->dev, "%s: fail read test data r=%d\n", + __func__, rc); + return rc; + } + + cyttsp4_pr_buf(cd->dev, cd->pr_buf, + (u8 *)si->si_ptrs.test, si->si_ofs.test_size, + "sysinfo_test_data"); + if (si->si_ptrs.test->post_codel & + CY_POST_CODEL_WDG_RST) + dev_info(cd->dev, "%s: %s codel=%02X\n", + __func__, "Reset was a WATCHDOG RESET", + si->si_ptrs.test->post_codel); + + if (!(si->si_ptrs.test->post_codel & + CY_POST_CODEL_CFG_DATA_CRC_FAIL)) + dev_info(cd->dev, "%s: %s codel=%02X\n", __func__, + "Config Data CRC FAIL", + si->si_ptrs.test->post_codel); + + if (!(si->si_ptrs.test->post_codel & + CY_POST_CODEL_PANEL_TEST_FAIL)) + dev_info(cd->dev, "%s: %s codel=%02X\n", + __func__, "PANEL TEST FAIL", + si->si_ptrs.test->post_codel); + + dev_info(cd->dev, "%s: SCANNING is %s codel=%02X\n", + __func__, si->si_ptrs.test->post_codel & 0x08 ? + "ENABLED" : "DISABLED", + si->si_ptrs.test->post_codel); + return rc; +} + +static int cyttsp4_si_get_pcfg_data(struct cyttsp4 *cd) +{ + struct cyttsp4_sysinfo *si = &cd->sysinfo; + void *p; + int rc; + + si->si_ofs.pcfg_size = si->si_ofs.opcfg_ofs - si->si_ofs.pcfg_ofs; + + p = krealloc(si->si_ptrs.pcfg, si->si_ofs.pcfg_size, GFP_KERNEL); + if (p == NULL) { + rc = -ENOMEM; + dev_err(cd->dev, "%s: fail alloc pcfg memory r=%d\n", + __func__, rc); + return rc; + } + si->si_ptrs.pcfg = p; + + rc = cyttsp4_adap_read(cd, si->si_ofs.pcfg_ofs, si->si_ofs.pcfg_size, + si->si_ptrs.pcfg); + if (rc < 0) { + dev_err(cd->dev, "%s: fail read pcfg data r=%d\n", + __func__, rc); + return rc; + } + + si->si_ofs.max_x = merge_bytes((si->si_ptrs.pcfg->res_xh + & CY_PCFG_RESOLUTION_X_MASK), si->si_ptrs.pcfg->res_xl); + si->si_ofs.x_origin = !!(si->si_ptrs.pcfg->res_xh + & CY_PCFG_ORIGIN_X_MASK); + si->si_ofs.max_y = merge_bytes((si->si_ptrs.pcfg->res_yh + & CY_PCFG_RESOLUTION_Y_MASK), si->si_ptrs.pcfg->res_yl); + si->si_ofs.y_origin = !!(si->si_ptrs.pcfg->res_yh + & CY_PCFG_ORIGIN_Y_MASK); + si->si_ofs.max_p = merge_bytes(si->si_ptrs.pcfg->max_zh, + si->si_ptrs.pcfg->max_zl); + + cyttsp4_pr_buf(cd->dev, cd->pr_buf, + (u8 *)si->si_ptrs.pcfg, + si->si_ofs.pcfg_size, "sysinfo_pcfg_data"); + return rc; +} + +static int cyttsp4_si_get_opcfg_data(struct cyttsp4 *cd) +{ + struct cyttsp4_sysinfo *si = &cd->sysinfo; + struct cyttsp4_tch_abs_params *tch; + struct cyttsp4_tch_rec_params *tch_old, *tch_new; + enum cyttsp4_tch_abs abs; + int i; + void *p; + int rc; + + si->si_ofs.opcfg_size = si->si_ofs.ddata_ofs - si->si_ofs.opcfg_ofs; + + p = krealloc(si->si_ptrs.opcfg, si->si_ofs.opcfg_size, GFP_KERNEL); + if (p == NULL) { + dev_err(cd->dev, "%s: fail alloc opcfg memory\n", __func__); + rc = -ENOMEM; + goto cyttsp4_si_get_opcfg_data_exit; + } + si->si_ptrs.opcfg = p; + + rc = cyttsp4_adap_read(cd, si->si_ofs.opcfg_ofs, si->si_ofs.opcfg_size, + si->si_ptrs.opcfg); + if (rc < 0) { + dev_err(cd->dev, "%s: fail read opcfg data r=%d\n", + __func__, rc); + goto cyttsp4_si_get_opcfg_data_exit; + } + si->si_ofs.cmd_ofs = si->si_ptrs.opcfg->cmd_ofs; + si->si_ofs.rep_ofs = si->si_ptrs.opcfg->rep_ofs; + si->si_ofs.rep_sz = (si->si_ptrs.opcfg->rep_szh * 256) + + si->si_ptrs.opcfg->rep_szl; + si->si_ofs.num_btns = si->si_ptrs.opcfg->num_btns; + si->si_ofs.num_btn_regs = (si->si_ofs.num_btns + + CY_NUM_BTN_PER_REG - 1) / CY_NUM_BTN_PER_REG; + si->si_ofs.tt_stat_ofs = si->si_ptrs.opcfg->tt_stat_ofs; + si->si_ofs.obj_cfg0 = si->si_ptrs.opcfg->obj_cfg0; + si->si_ofs.max_tchs = si->si_ptrs.opcfg->max_tchs & + CY_BYTE_OFS_MASK; + si->si_ofs.tch_rec_size = si->si_ptrs.opcfg->tch_rec_size & + CY_BYTE_OFS_MASK; + + /* Get the old touch fields */ + for (abs = CY_TCH_X; abs < CY_NUM_TCH_FIELDS; abs++) { + tch = &si->si_ofs.tch_abs[abs]; + tch_old = &si->si_ptrs.opcfg->tch_rec_old[abs]; + + tch->ofs = tch_old->loc & CY_BYTE_OFS_MASK; + tch->size = cyttsp4_bits_2_bytes(tch_old->size, + &tch->max); + tch->bofs = (tch_old->loc & CY_BOFS_MASK) >> CY_BOFS_SHIFT; + } + + /* button fields */ + si->si_ofs.btn_rec_size = si->si_ptrs.opcfg->btn_rec_size; + si->si_ofs.btn_diff_ofs = si->si_ptrs.opcfg->btn_diff_ofs; + si->si_ofs.btn_diff_size = si->si_ptrs.opcfg->btn_diff_size; + + if (si->si_ofs.tch_rec_size > CY_TMA1036_TCH_REC_SIZE) { + /* Get the extended touch fields */ + for (i = 0; i < CY_NUM_EXT_TCH_FIELDS; abs++, i++) { + tch = &si->si_ofs.tch_abs[abs]; + tch_new = &si->si_ptrs.opcfg->tch_rec_new[i]; + + tch->ofs = tch_new->loc & CY_BYTE_OFS_MASK; + tch->size = cyttsp4_bits_2_bytes(tch_new->size, + &tch->max); + tch->bofs = (tch_new->loc & CY_BOFS_MASK) >> CY_BOFS_SHIFT; + } + } + + for (abs = 0; abs < CY_TCH_NUM_ABS; abs++) { + dev_dbg(cd->dev, "%s: tch_rec_%s\n", __func__, + cyttsp4_tch_abs_string[abs]); + dev_dbg(cd->dev, "%s: ofs =%2Zd\n", __func__, + si->si_ofs.tch_abs[abs].ofs); + dev_dbg(cd->dev, "%s: siz =%2Zd\n", __func__, + si->si_ofs.tch_abs[abs].size); + dev_dbg(cd->dev, "%s: max =%2Zd\n", __func__, + si->si_ofs.tch_abs[abs].max); + dev_dbg(cd->dev, "%s: bofs=%2Zd\n", __func__, + si->si_ofs.tch_abs[abs].bofs); + } + + si->si_ofs.mode_size = si->si_ofs.tt_stat_ofs + 1; + si->si_ofs.data_size = si->si_ofs.max_tchs * + si->si_ptrs.opcfg->tch_rec_size; + + cyttsp4_pr_buf(cd->dev, cd->pr_buf, (u8 *)si->si_ptrs.opcfg, + si->si_ofs.opcfg_size, "sysinfo_opcfg_data"); + +cyttsp4_si_get_opcfg_data_exit: + return rc; +} + +static int cyttsp4_si_get_ddata(struct cyttsp4 *cd) +{ + struct cyttsp4_sysinfo *si = &cd->sysinfo; + void *p; + int rc; + + si->si_ofs.ddata_size = si->si_ofs.mdata_ofs - si->si_ofs.ddata_ofs; + + p = krealloc(si->si_ptrs.ddata, si->si_ofs.ddata_size, GFP_KERNEL); + if (p == NULL) { + dev_err(cd->dev, "%s: fail alloc ddata memory\n", __func__); + return -ENOMEM; + } + si->si_ptrs.ddata = p; + + rc = cyttsp4_adap_read(cd, si->si_ofs.ddata_ofs, si->si_ofs.ddata_size, + si->si_ptrs.ddata); + if (rc < 0) + dev_err(cd->dev, "%s: fail read ddata data r=%d\n", + __func__, rc); + else + cyttsp4_pr_buf(cd->dev, cd->pr_buf, + (u8 *)si->si_ptrs.ddata, + si->si_ofs.ddata_size, "sysinfo_ddata"); + return rc; +} + +static int cyttsp4_si_get_mdata(struct cyttsp4 *cd) +{ + struct cyttsp4_sysinfo *si = &cd->sysinfo; + void *p; + int rc; + + si->si_ofs.mdata_size = si->si_ofs.map_sz - si->si_ofs.mdata_ofs; + + p = krealloc(si->si_ptrs.mdata, si->si_ofs.mdata_size, GFP_KERNEL); + if (p == NULL) { + dev_err(cd->dev, "%s: fail alloc mdata memory\n", __func__); + return -ENOMEM; + } + si->si_ptrs.mdata = p; + + rc = cyttsp4_adap_read(cd, si->si_ofs.mdata_ofs, si->si_ofs.mdata_size, + si->si_ptrs.mdata); + if (rc < 0) + dev_err(cd->dev, "%s: fail read mdata data r=%d\n", + __func__, rc); + else + cyttsp4_pr_buf(cd->dev, cd->pr_buf, + (u8 *)si->si_ptrs.mdata, + si->si_ofs.mdata_size, "sysinfo_mdata"); + return rc; +} + +static int cyttsp4_si_get_btn_data(struct cyttsp4 *cd) +{ + struct cyttsp4_sysinfo *si = &cd->sysinfo; + int btn; + int num_defined_keys; + u16 *key_table; + void *p; + int rc = 0; + + if (si->si_ofs.num_btns) { + si->si_ofs.btn_keys_size = si->si_ofs.num_btns * + sizeof(struct cyttsp4_btn); + + p = krealloc(si->btn, si->si_ofs.btn_keys_size, + GFP_KERNEL|__GFP_ZERO); + if (p == NULL) { + dev_err(cd->dev, "%s: %s\n", __func__, + "fail alloc btn_keys memory"); + return -ENOMEM; + } + si->btn = p; + + if (cd->cpdata->sett[CY_IC_GRPNUM_BTN_KEYS] == NULL) + num_defined_keys = 0; + else if (cd->cpdata->sett[CY_IC_GRPNUM_BTN_KEYS]->data == NULL) + num_defined_keys = 0; + else + num_defined_keys = cd->cpdata->sett + [CY_IC_GRPNUM_BTN_KEYS]->size; + + for (btn = 0; btn < si->si_ofs.num_btns && + btn < num_defined_keys; btn++) { + key_table = (u16 *)cd->cpdata->sett + [CY_IC_GRPNUM_BTN_KEYS]->data; + si->btn[btn].key_code = key_table[btn]; + si->btn[btn].state = CY_BTN_RELEASED; + si->btn[btn].enabled = true; + } + for (; btn < si->si_ofs.num_btns; btn++) { + si->btn[btn].key_code = KEY_RESERVED; + si->btn[btn].state = CY_BTN_RELEASED; + si->btn[btn].enabled = true; + } + + return rc; + } + + si->si_ofs.btn_keys_size = 0; + kfree(si->btn); + si->btn = NULL; + return rc; +} + +static int cyttsp4_si_get_op_data_ptrs(struct cyttsp4 *cd) +{ + struct cyttsp4_sysinfo *si = &cd->sysinfo; + void *p; + + p = krealloc(si->xy_mode, si->si_ofs.mode_size, GFP_KERNEL|__GFP_ZERO); + if (p == NULL) + return -ENOMEM; + si->xy_mode = p; + + p = krealloc(si->xy_data, si->si_ofs.data_size, GFP_KERNEL|__GFP_ZERO); + if (p == NULL) + return -ENOMEM; + si->xy_data = p; + + p = krealloc(si->btn_rec_data, + si->si_ofs.btn_rec_size * si->si_ofs.num_btns, + GFP_KERNEL|__GFP_ZERO); + if (p == NULL) + return -ENOMEM; + si->btn_rec_data = p; + + return 0; +} + +static void cyttsp4_si_put_log_data(struct cyttsp4 *cd) +{ + struct cyttsp4_sysinfo *si = &cd->sysinfo; + dev_dbg(cd->dev, "%s: cydata_ofs =%4Zd siz=%4Zd\n", __func__, + si->si_ofs.cydata_ofs, si->si_ofs.cydata_size); + dev_dbg(cd->dev, "%s: test_ofs =%4Zd siz=%4Zd\n", __func__, + si->si_ofs.test_ofs, si->si_ofs.test_size); + dev_dbg(cd->dev, "%s: pcfg_ofs =%4Zd siz=%4Zd\n", __func__, + si->si_ofs.pcfg_ofs, si->si_ofs.pcfg_size); + dev_dbg(cd->dev, "%s: opcfg_ofs =%4Zd siz=%4Zd\n", __func__, + si->si_ofs.opcfg_ofs, si->si_ofs.opcfg_size); + dev_dbg(cd->dev, "%s: ddata_ofs =%4Zd siz=%4Zd\n", __func__, + si->si_ofs.ddata_ofs, si->si_ofs.ddata_size); + dev_dbg(cd->dev, "%s: mdata_ofs =%4Zd siz=%4Zd\n", __func__, + si->si_ofs.mdata_ofs, si->si_ofs.mdata_size); + + dev_dbg(cd->dev, "%s: cmd_ofs =%4Zd\n", __func__, + si->si_ofs.cmd_ofs); + dev_dbg(cd->dev, "%s: rep_ofs =%4Zd\n", __func__, + si->si_ofs.rep_ofs); + dev_dbg(cd->dev, "%s: rep_sz =%4Zd\n", __func__, + si->si_ofs.rep_sz); + dev_dbg(cd->dev, "%s: num_btns =%4Zd\n", __func__, + si->si_ofs.num_btns); + dev_dbg(cd->dev, "%s: num_btn_regs =%4Zd\n", __func__, + si->si_ofs.num_btn_regs); + dev_dbg(cd->dev, "%s: tt_stat_ofs =%4Zd\n", __func__, + si->si_ofs.tt_stat_ofs); + dev_dbg(cd->dev, "%s: tch_rec_size =%4Zd\n", __func__, + si->si_ofs.tch_rec_size); + dev_dbg(cd->dev, "%s: max_tchs =%4Zd\n", __func__, + si->si_ofs.max_tchs); + dev_dbg(cd->dev, "%s: mode_size =%4Zd\n", __func__, + si->si_ofs.mode_size); + dev_dbg(cd->dev, "%s: data_size =%4Zd\n", __func__, + si->si_ofs.data_size); + dev_dbg(cd->dev, "%s: map_sz =%4Zd\n", __func__, + si->si_ofs.map_sz); + + dev_dbg(cd->dev, "%s: btn_rec_size =%2Zd\n", __func__, + si->si_ofs.btn_rec_size); + dev_dbg(cd->dev, "%s: btn_diff_ofs =%2Zd\n", __func__, + si->si_ofs.btn_diff_ofs); + dev_dbg(cd->dev, "%s: btn_diff_size =%2Zd\n", __func__, + si->si_ofs.btn_diff_size); + + dev_dbg(cd->dev, "%s: max_x = 0x%04ZX (%Zd)\n", __func__, + si->si_ofs.max_x, si->si_ofs.max_x); + dev_dbg(cd->dev, "%s: x_origin = %Zd (%s)\n", __func__, + si->si_ofs.x_origin, + si->si_ofs.x_origin == CY_NORMAL_ORIGIN ? + "left corner" : "right corner"); + dev_dbg(cd->dev, "%s: max_y = 0x%04ZX (%Zd)\n", __func__, + si->si_ofs.max_y, si->si_ofs.max_y); + dev_dbg(cd->dev, "%s: y_origin = %Zd (%s)\n", __func__, + si->si_ofs.y_origin, + si->si_ofs.y_origin == CY_NORMAL_ORIGIN ? + "upper corner" : "lower corner"); + dev_dbg(cd->dev, "%s: max_p = 0x%04ZX (%Zd)\n", __func__, + si->si_ofs.max_p, si->si_ofs.max_p); + + dev_dbg(cd->dev, "%s: xy_mode=%p xy_data=%p\n", __func__, + si->xy_mode, si->xy_data); +} + +static int cyttsp4_get_sysinfo_regs(struct cyttsp4 *cd) +{ + struct cyttsp4_sysinfo *si = &cd->sysinfo; + int rc; + + rc = cyttsp4_si_data_offsets(cd); + if (rc < 0) + return rc; + + rc = cyttsp4_si_get_cydata(cd); + if (rc < 0) + return rc; + + rc = cyttsp4_si_get_test_data(cd); + if (rc < 0) + return rc; + + rc = cyttsp4_si_get_pcfg_data(cd); + if (rc < 0) + return rc; + + rc = cyttsp4_si_get_opcfg_data(cd); + if (rc < 0) + return rc; + + rc = cyttsp4_si_get_ddata(cd); + if (rc < 0) + return rc; + + rc = cyttsp4_si_get_mdata(cd); + if (rc < 0) + return rc; + + rc = cyttsp4_si_get_btn_data(cd); + if (rc < 0) + return rc; + + rc = cyttsp4_si_get_op_data_ptrs(cd); + if (rc < 0) { + dev_err(cd->dev, "%s: failed to get_op_data\n", + __func__); + return rc; + } + + cyttsp4_si_put_log_data(cd); + + /* provide flow control handshake */ + rc = cyttsp4_handshake(cd, si->si_data.hst_mode); + if (rc < 0) + dev_err(cd->dev, "%s: handshake fail on sysinfo reg\n", + __func__); + + si->ready = true; + return rc; +} + +static void cyttsp4_queue_startup_(struct cyttsp4 *cd) +{ + if (cd->startup_state == STARTUP_NONE) { + cd->startup_state = STARTUP_QUEUED; + schedule_work(&cd->startup_work); + dev_dbg(cd->dev, "%s: cyttsp4_startup queued\n", __func__); + } else { + dev_dbg(cd->dev, "%s: startup_state = %d\n", __func__, + cd->startup_state); + } +} + +static void cyttsp4_report_slot_liftoff(struct cyttsp4_mt_data *md, + int max_slots) +{ + int t; + + if (md->num_prv_tch == 0) + return; + + for (t = 0; t < max_slots; t++) { + input_mt_slot(md->input, t); + input_mt_report_slot_state(md->input, + MT_TOOL_FINGER, false); + } +} + +static void cyttsp4_lift_all(struct cyttsp4_mt_data *md) +{ + if (!md->si) + return; + + if (md->num_prv_tch != 0) { + cyttsp4_report_slot_liftoff(md, + md->si->si_ofs.tch_abs[CY_TCH_T].max); + input_sync(md->input); + md->num_prv_tch = 0; + } +} + +static void cyttsp4_get_touch_axis(struct cyttsp4_mt_data *md, + int *axis, int size, int max, u8 *xy_data, int bofs) +{ + int nbyte; + int next; + + for (nbyte = 0, *axis = 0, next = 0; nbyte < size; nbyte++) { + dev_vdbg(&md->input->dev, + "%s: *axis=%02X(%d) size=%d max=%08X xy_data=%p" + " xy_data[%d]=%02X(%d) bofs=%d\n", + __func__, *axis, *axis, size, max, xy_data, next, + xy_data[next], xy_data[next], bofs); + *axis = (*axis * 256) + (xy_data[next] >> bofs); + next++; + } + + *axis &= max - 1; + + dev_vdbg(&md->input->dev, + "%s: *axis=%02X(%d) size=%d max=%08X xy_data=%p" + " xy_data[%d]=%02X(%d)\n", + __func__, *axis, *axis, size, max, xy_data, next, + xy_data[next], xy_data[next]); +} + +static void cyttsp4_get_touch(struct cyttsp4_mt_data *md, + struct cyttsp4_touch *touch, u8 *xy_data) +{ + struct device *dev = &md->input->dev; + struct cyttsp4_sysinfo *si = md->si; + enum cyttsp4_tch_abs abs; + int tmp; + bool flipped; + + for (abs = CY_TCH_X; abs < CY_TCH_NUM_ABS; abs++) { + cyttsp4_get_touch_axis(md, &touch->abs[abs], + si->si_ofs.tch_abs[abs].size, + si->si_ofs.tch_abs[abs].max, + xy_data + si->si_ofs.tch_abs[abs].ofs, + si->si_ofs.tch_abs[abs].bofs); + dev_vdbg(dev, "%s: get %s=%04X(%d)\n", __func__, + cyttsp4_tch_abs_string[abs], + touch->abs[abs], touch->abs[abs]); + } + + if (md->pdata->flags & CY_FLAG_FLIP) { + tmp = touch->abs[CY_TCH_X]; + touch->abs[CY_TCH_X] = touch->abs[CY_TCH_Y]; + touch->abs[CY_TCH_Y] = tmp; + flipped = true; + } else + flipped = false; + + if (md->pdata->flags & CY_FLAG_INV_X) { + if (flipped) + touch->abs[CY_TCH_X] = md->si->si_ofs.max_y - + touch->abs[CY_TCH_X]; + else + touch->abs[CY_TCH_X] = md->si->si_ofs.max_x - + touch->abs[CY_TCH_X]; + } + if (md->pdata->flags & CY_FLAG_INV_Y) { + if (flipped) + touch->abs[CY_TCH_Y] = md->si->si_ofs.max_x - + touch->abs[CY_TCH_Y]; + else + touch->abs[CY_TCH_Y] = md->si->si_ofs.max_y - + touch->abs[CY_TCH_Y]; + } + + dev_vdbg(dev, "%s: flip=%s inv-x=%s inv-y=%s x=%04X(%d) y=%04X(%d)\n", + __func__, flipped ? "true" : "false", + md->pdata->flags & CY_FLAG_INV_X ? "true" : "false", + md->pdata->flags & CY_FLAG_INV_Y ? "true" : "false", + touch->abs[CY_TCH_X], touch->abs[CY_TCH_X], + touch->abs[CY_TCH_Y], touch->abs[CY_TCH_Y]); +} + +static void cyttsp4_final_sync(struct input_dev *input, int max_slots, int *ids) +{ + int t; + + for (t = 0; t < max_slots; t++) { + if (ids[t]) + continue; + input_mt_slot(input, t); + input_mt_report_slot_state(input, MT_TOOL_FINGER, false); + } + + input_sync(input); +} + +static void cyttsp4_get_mt_touches(struct cyttsp4_mt_data *md, int num_cur_tch) +{ + struct device *dev = &md->input->dev; + struct cyttsp4_sysinfo *si = md->si; + struct cyttsp4_touch tch; + int sig; + int i, j, t = 0; + int ids[max(CY_TMA1036_MAX_TCH, CY_TMA4XX_MAX_TCH)]; + + memset(ids, 0, si->si_ofs.tch_abs[CY_TCH_T].max * sizeof(int)); + for (i = 0; i < num_cur_tch; i++) { + cyttsp4_get_touch(md, &tch, si->xy_data + + (i * si->si_ofs.tch_rec_size)); + if ((tch.abs[CY_TCH_T] < md->pdata->frmwrk->abs + [(CY_ABS_ID_OST * CY_NUM_ABS_SET) + CY_MIN_OST]) || + (tch.abs[CY_TCH_T] > md->pdata->frmwrk->abs + [(CY_ABS_ID_OST * CY_NUM_ABS_SET) + CY_MAX_OST])) { + dev_err(dev, "%s: tch=%d -> bad trk_id=%d max_id=%d\n", + __func__, i, tch.abs[CY_TCH_T], + md->pdata->frmwrk->abs[(CY_ABS_ID_OST * + CY_NUM_ABS_SET) + CY_MAX_OST]); + continue; + } + + /* use 0 based track id's */ + sig = md->pdata->frmwrk->abs + [(CY_ABS_ID_OST * CY_NUM_ABS_SET) + 0]; + if (sig != CY_IGNORE_VALUE) { + t = tch.abs[CY_TCH_T] - md->pdata->frmwrk->abs + [(CY_ABS_ID_OST * CY_NUM_ABS_SET) + CY_MIN_OST]; + if (tch.abs[CY_TCH_E] == CY_EV_LIFTOFF) { + dev_dbg(dev, "%s: t=%d e=%d lift-off\n", + __func__, t, tch.abs[CY_TCH_E]); + goto cyttsp4_get_mt_touches_pr_tch; + } + input_mt_slot(md->input, t); + input_mt_report_slot_state(md->input, MT_TOOL_FINGER, + true); + ids[t] = true; + } + + /* all devices: position and pressure fields */ + for (j = 0; j <= CY_ABS_W_OST; j++) { + sig = md->pdata->frmwrk->abs[((CY_ABS_X_OST + j) * + CY_NUM_ABS_SET) + 0]; + if (sig != CY_IGNORE_VALUE) + input_report_abs(md->input, sig, + tch.abs[CY_TCH_X + j]); + } + if (si->si_ofs.tch_rec_size > CY_TMA1036_TCH_REC_SIZE) { + /* + * TMA400 size and orientation fields: + * if pressure is non-zero and major touch + * signal is zero, then set major and minor touch + * signals to minimum non-zero value + */ + if (tch.abs[CY_TCH_P] > 0 && tch.abs[CY_TCH_MAJ] == 0) + tch.abs[CY_TCH_MAJ] = tch.abs[CY_TCH_MIN] = 1; + + /* Get the extended touch fields */ + for (j = 0; j < CY_NUM_EXT_TCH_FIELDS; j++) { + sig = md->pdata->frmwrk->abs + [((CY_ABS_MAJ_OST + j) * + CY_NUM_ABS_SET) + 0]; + if (sig != CY_IGNORE_VALUE) + input_report_abs(md->input, sig, + tch.abs[CY_TCH_MAJ + j]); + } + } + +cyttsp4_get_mt_touches_pr_tch: + if (si->si_ofs.tch_rec_size > CY_TMA1036_TCH_REC_SIZE) + dev_dbg(dev, + "%s: t=%d x=%d y=%d z=%d M=%d m=%d o=%d e=%d\n", + __func__, t, + tch.abs[CY_TCH_X], + tch.abs[CY_TCH_Y], + tch.abs[CY_TCH_P], + tch.abs[CY_TCH_MAJ], + tch.abs[CY_TCH_MIN], + tch.abs[CY_TCH_OR], + tch.abs[CY_TCH_E]); + else + dev_dbg(dev, + "%s: t=%d x=%d y=%d z=%d e=%d\n", __func__, + t, + tch.abs[CY_TCH_X], + tch.abs[CY_TCH_Y], + tch.abs[CY_TCH_P], + tch.abs[CY_TCH_E]); + } + + cyttsp4_final_sync(md->input, si->si_ofs.tch_abs[CY_TCH_T].max, ids); + + md->num_prv_tch = num_cur_tch; + + return; +} + +/* read xy_data for all current touches */ +static int cyttsp4_xy_worker(struct cyttsp4 *cd) +{ + struct cyttsp4_mt_data *md = &cd->md; + struct device *dev = &md->input->dev; + struct cyttsp4_sysinfo *si = md->si; + u8 num_cur_tch; + u8 hst_mode; + u8 rep_len; + u8 rep_stat; + u8 tt_stat; + int rc = 0; + + /* + * Get event data from cyttsp4 device. + * The event data includes all data + * for all active touches. + * Event data also includes button data + */ + /* + * Use 2 reads: + * 1st read to get mode + button bytes + touch count (core) + * 2nd read (optional) to get touch 1 - touch n data + */ + hst_mode = si->xy_mode[CY_REG_BASE]; + rep_len = si->xy_mode[si->si_ofs.rep_ofs]; + rep_stat = si->xy_mode[si->si_ofs.rep_ofs + 1]; + tt_stat = si->xy_mode[si->si_ofs.tt_stat_ofs]; + dev_vdbg(dev, "%s: %s%02X %s%d %s%02X %s%02X\n", __func__, + "hst_mode=", hst_mode, "rep_len=", rep_len, + "rep_stat=", rep_stat, "tt_stat=", tt_stat); + + num_cur_tch = GET_NUM_TOUCHES(tt_stat); + dev_vdbg(dev, "%s: num_cur_tch=%d\n", __func__, num_cur_tch); + + if (rep_len == 0 && num_cur_tch > 0) { + dev_err(dev, "%s: report length error rep_len=%d num_tch=%d\n", + __func__, rep_len, num_cur_tch); + goto cyttsp4_xy_worker_exit; + } + + /* read touches */ + if (num_cur_tch > 0) { + rc = cyttsp4_adap_read(cd, si->si_ofs.tt_stat_ofs + 1, + num_cur_tch * si->si_ofs.tch_rec_size, + si->xy_data); + if (rc < 0) { + dev_err(dev, "%s: read fail on touch regs r=%d\n", + __func__, rc); + goto cyttsp4_xy_worker_exit; + } + } + + /* print xy data */ + cyttsp4_pr_buf(dev, cd->pr_buf, si->xy_data, num_cur_tch * + si->si_ofs.tch_rec_size, "xy_data"); + + /* check any error conditions */ + if (IS_BAD_PKT(rep_stat)) { + dev_dbg(dev, "%s: Invalid buffer detected\n", __func__); + rc = 0; + goto cyttsp4_xy_worker_exit; + } + + if (IS_LARGE_AREA(tt_stat)) + dev_dbg(dev, "%s: Large area detected\n", __func__); + + if (num_cur_tch > si->si_ofs.max_tchs) { + dev_err(dev, "%s: too many tch; set to max tch (n=%d c=%Zd)\n", + __func__, num_cur_tch, si->si_ofs.max_tchs); + num_cur_tch = si->si_ofs.max_tchs; + } + + /* extract xy_data for all currently reported touches */ + dev_vdbg(dev, "%s: extract data num_cur_tch=%d\n", __func__, + num_cur_tch); + if (num_cur_tch) + cyttsp4_get_mt_touches(md, num_cur_tch); + else + cyttsp4_lift_all(md); + + rc = 0; + +cyttsp4_xy_worker_exit: + return rc; +} + +static int cyttsp4_mt_attention(struct cyttsp4 *cd) +{ + struct device *dev = cd->dev; + struct cyttsp4_mt_data *md = &cd->md; + int rc = 0; + + if (!md->si) + return 0; + + mutex_lock(&md->report_lock); + if (!md->is_suspended) { + /* core handles handshake */ + rc = cyttsp4_xy_worker(cd); + } else { + dev_vdbg(dev, "%s: Ignoring report while suspended\n", + __func__); + } + mutex_unlock(&md->report_lock); + if (rc < 0) + dev_err(dev, "%s: xy_worker error r=%d\n", __func__, rc); + + return rc; +} + +static irqreturn_t cyttsp4_irq(int irq, void *handle) +{ + struct cyttsp4 *cd = handle; + struct device *dev = cd->dev; + enum cyttsp4_mode cur_mode; + u8 cmd_ofs = cd->sysinfo.si_ofs.cmd_ofs; + u8 mode[3]; + int rc; + + /* + * Check whether this IRQ should be ignored (external) + * This should be the very first thing to check since + * ignore_irq may be set for a very short period of time + */ + if (atomic_read(&cd->ignore_irq)) { + dev_vdbg(dev, "%s: Ignoring IRQ\n", __func__); + return IRQ_HANDLED; + } + + dev_dbg(dev, "%s int:0x%x\n", __func__, cd->int_status); + + mutex_lock(&cd->system_lock); + + /* Just to debug */ + if (cd->sleep_state == SS_SLEEP_ON || cd->sleep_state == SS_SLEEPING) + dev_vdbg(dev, "%s: Received IRQ while in sleep\n", __func__); + + rc = cyttsp4_adap_read(cd, CY_REG_BASE, sizeof(mode), mode); + if (rc) { + dev_err(cd->dev, "%s: Fail read adapter r=%d\n", __func__, rc); + goto cyttsp4_irq_exit; + } + dev_vdbg(dev, "%s mode[0-2]:0x%X 0x%X 0x%X\n", __func__, + mode[0], mode[1], mode[2]); + + if (IS_BOOTLOADER(mode[0], mode[1])) { + cur_mode = CY_MODE_BOOTLOADER; + dev_vdbg(dev, "%s: bl running\n", __func__); + if (cd->mode == CY_MODE_BOOTLOADER) { + /* Signal bootloader heartbeat heard */ + wake_up(&cd->wait_q); + goto cyttsp4_irq_exit; + } + + /* switch to bootloader */ + dev_dbg(dev, "%s: restart switch to bl m=%d -> m=%d\n", + __func__, cd->mode, cur_mode); + + /* catch operation->bl glitch */ + if (cd->mode != CY_MODE_UNKNOWN) { + /* Incase startup_state do not let startup_() */ + cd->mode = CY_MODE_UNKNOWN; + cyttsp4_queue_startup_(cd); + goto cyttsp4_irq_exit; + } + + /* + * do not wake thread on this switch since + * it is possible to get an early heartbeat + * prior to performing the reset + */ + cd->mode = cur_mode; + + goto cyttsp4_irq_exit; + } + + switch (mode[0] & CY_HST_MODE) { + case CY_HST_OPERATE: + cur_mode = CY_MODE_OPERATIONAL; + dev_vdbg(dev, "%s: operational\n", __func__); + break; + case CY_HST_CAT: + cur_mode = CY_MODE_CAT; + dev_vdbg(dev, "%s: CaT\n", __func__); + break; + case CY_HST_SYSINFO: + cur_mode = CY_MODE_SYSINFO; + dev_vdbg(dev, "%s: sysinfo\n", __func__); + break; + default: + cur_mode = CY_MODE_UNKNOWN; + dev_err(dev, "%s: unknown HST mode 0x%02X\n", __func__, + mode[0]); + break; + } + + /* Check whether this IRQ should be ignored (internal) */ + if (cd->int_status & CY_INT_IGNORE) { + dev_vdbg(dev, "%s: Ignoring IRQ\n", __func__); + goto cyttsp4_irq_exit; + } + + /* Check for wake up interrupt */ + if (cd->int_status & CY_INT_AWAKE) { + cd->int_status &= ~CY_INT_AWAKE; + wake_up(&cd->wait_q); + dev_vdbg(dev, "%s: Received wake up interrupt\n", __func__); + goto cyttsp4_irq_handshake; + } + + /* Expecting mode change interrupt */ + if ((cd->int_status & CY_INT_MODE_CHANGE) + && (mode[0] & CY_HST_MODE_CHANGE) == 0) { + cd->int_status &= ~CY_INT_MODE_CHANGE; + dev_dbg(dev, "%s: finish mode switch m=%d -> m=%d\n", + __func__, cd->mode, cur_mode); + cd->mode = cur_mode; + wake_up(&cd->wait_q); + goto cyttsp4_irq_handshake; + } + + /* compare current core mode to current device mode */ + dev_vdbg(dev, "%s: cd->mode=%d cur_mode=%d\n", + __func__, cd->mode, cur_mode); + if ((mode[0] & CY_HST_MODE_CHANGE) == 0 && cd->mode != cur_mode) { + /* Unexpected mode change occurred */ + dev_err(dev, "%s %d->%d 0x%x\n", __func__, cd->mode, + cur_mode, cd->int_status); + dev_dbg(dev, "%s: Unexpected mode change, startup\n", + __func__); + cyttsp4_queue_startup_(cd); + goto cyttsp4_irq_exit; + } + + /* Expecting command complete interrupt */ + dev_vdbg(dev, "%s: command byte:0x%x\n", __func__, mode[cmd_ofs]); + if ((cd->int_status & CY_INT_EXEC_CMD) + && mode[cmd_ofs] & CY_CMD_COMPLETE) { + cd->int_status &= ~CY_INT_EXEC_CMD; + dev_vdbg(dev, "%s: Received command complete interrupt\n", + __func__); + wake_up(&cd->wait_q); + /* + * It is possible to receive a single interrupt for + * command complete and touch/button status report. + * Continue processing for a possible status report. + */ + } + + /* This should be status report, read status regs */ + if (cd->mode == CY_MODE_OPERATIONAL) { + dev_vdbg(dev, "%s: Read status registers\n", __func__); + rc = cyttsp4_load_status_regs(cd); + if (rc < 0) + dev_err(dev, "%s: fail read mode regs r=%d\n", + __func__, rc); + } + + cyttsp4_mt_attention(cd); + +cyttsp4_irq_handshake: + /* handshake the event */ + dev_vdbg(dev, "%s: Handshake mode=0x%02X r=%d\n", + __func__, mode[0], rc); + rc = cyttsp4_handshake(cd, mode[0]); + if (rc < 0) + dev_err(dev, "%s: Fail handshake mode=0x%02X r=%d\n", + __func__, mode[0], rc); + + /* + * a non-zero udelay period is required for using + * IRQF_TRIGGER_LOW in order to delay until the + * device completes isr deassert + */ + udelay(cd->cpdata->level_irq_udelay); + +cyttsp4_irq_exit: + mutex_unlock(&cd->system_lock); + return IRQ_HANDLED; +} + +static void cyttsp4_start_wd_timer(struct cyttsp4 *cd) +{ + if (!CY_WATCHDOG_TIMEOUT) + return; + + mod_timer(&cd->watchdog_timer, jiffies + + msecs_to_jiffies(CY_WATCHDOG_TIMEOUT)); +} + +static void cyttsp4_stop_wd_timer(struct cyttsp4 *cd) +{ + if (!CY_WATCHDOG_TIMEOUT) + return; + + /* + * Ensure we wait until the watchdog timer + * running on a different CPU finishes + */ + del_timer_sync(&cd->watchdog_timer); + cancel_work_sync(&cd->watchdog_work); + del_timer_sync(&cd->watchdog_timer); +} + +static void cyttsp4_watchdog_timer(unsigned long handle) +{ + struct cyttsp4 *cd = (struct cyttsp4 *)handle; + + dev_vdbg(cd->dev, "%s: Watchdog timer triggered\n", __func__); + + if (!cd) + return; + + if (!work_pending(&cd->watchdog_work)) + schedule_work(&cd->watchdog_work); + + return; +} + +static int cyttsp4_request_exclusive(struct cyttsp4 *cd, void *ownptr, + int timeout_ms) +{ + int t = msecs_to_jiffies(timeout_ms); + bool with_timeout = (timeout_ms != 0); + + mutex_lock(&cd->system_lock); + if (!cd->exclusive_dev && cd->exclusive_waits == 0) { + cd->exclusive_dev = ownptr; + goto exit; + } + + cd->exclusive_waits++; +wait: + mutex_unlock(&cd->system_lock); + if (with_timeout) { + t = wait_event_timeout(cd->wait_q, !cd->exclusive_dev, t); + if (IS_TMO(t)) { + dev_err(cd->dev, "%s: tmo waiting exclusive access\n", + __func__); + mutex_lock(&cd->system_lock); + cd->exclusive_waits--; + mutex_unlock(&cd->system_lock); + return -ETIME; + } + } else { + wait_event(cd->wait_q, !cd->exclusive_dev); + } + mutex_lock(&cd->system_lock); + if (cd->exclusive_dev) + goto wait; + cd->exclusive_dev = ownptr; + cd->exclusive_waits--; +exit: + mutex_unlock(&cd->system_lock); + + return 0; +} + +/* + * returns error if was not owned + */ +static int cyttsp4_release_exclusive(struct cyttsp4 *cd, void *ownptr) +{ + mutex_lock(&cd->system_lock); + if (cd->exclusive_dev != ownptr) { + mutex_unlock(&cd->system_lock); + return -EINVAL; + } + + dev_vdbg(cd->dev, "%s: exclusive_dev %p freed\n", + __func__, cd->exclusive_dev); + cd->exclusive_dev = NULL; + wake_up(&cd->wait_q); + mutex_unlock(&cd->system_lock); + return 0; +} + +static int cyttsp4_wait_bl_heartbeat(struct cyttsp4 *cd) +{ + long t; + int rc = 0; + + /* wait heartbeat */ + dev_vdbg(cd->dev, "%s: wait heartbeat...\n", __func__); + t = wait_event_timeout(cd->wait_q, cd->mode == CY_MODE_BOOTLOADER, + msecs_to_jiffies(CY_CORE_RESET_AND_WAIT_TIMEOUT)); + if (IS_TMO(t)) { + dev_err(cd->dev, "%s: tmo waiting bl heartbeat cd->mode=%d\n", + __func__, cd->mode); + rc = -ETIME; + } + + return rc; +} + +static int cyttsp4_wait_sysinfo_mode(struct cyttsp4 *cd) +{ + long t; + + dev_vdbg(cd->dev, "%s: wait sysinfo...\n", __func__); + + t = wait_event_timeout(cd->wait_q, cd->mode == CY_MODE_SYSINFO, + msecs_to_jiffies(CY_CORE_MODE_CHANGE_TIMEOUT)); + if (IS_TMO(t)) { + dev_err(cd->dev, "%s: tmo waiting exit bl cd->mode=%d\n", + __func__, cd->mode); + mutex_lock(&cd->system_lock); + cd->int_status &= ~CY_INT_MODE_CHANGE; + mutex_unlock(&cd->system_lock); + return -ETIME; + } + + return 0; +} + +static int cyttsp4_reset_and_wait(struct cyttsp4 *cd) +{ + int rc; + + /* reset hardware */ + mutex_lock(&cd->system_lock); + dev_dbg(cd->dev, "%s: reset hw...\n", __func__); + rc = cyttsp4_hw_reset(cd); + cd->mode = CY_MODE_UNKNOWN; + mutex_unlock(&cd->system_lock); + if (rc < 0) { + dev_err(cd->dev, "%s:Fail hw reset r=%d\n", __func__, rc); + return rc; + } + + return cyttsp4_wait_bl_heartbeat(cd); +} + +/* + * returns err if refused or timeout; block until mode change complete + * bit is set (mode change interrupt) + */ +static int cyttsp4_set_mode(struct cyttsp4 *cd, int new_mode) +{ + u8 new_dev_mode; + u8 mode; + long t; + int rc; + + switch (new_mode) { + case CY_MODE_OPERATIONAL: + new_dev_mode = CY_HST_OPERATE; + break; + case CY_MODE_SYSINFO: + new_dev_mode = CY_HST_SYSINFO; + break; + case CY_MODE_CAT: + new_dev_mode = CY_HST_CAT; + break; + default: + dev_err(cd->dev, "%s: invalid mode: %02X(%d)\n", + __func__, new_mode, new_mode); + return -EINVAL; + } + + /* change mode */ + dev_dbg(cd->dev, "%s: %s=%p new_dev_mode=%02X new_mode=%d\n", + __func__, "have exclusive", cd->exclusive_dev, + new_dev_mode, new_mode); + + mutex_lock(&cd->system_lock); + rc = cyttsp4_adap_read(cd, CY_REG_BASE, sizeof(mode), &mode); + if (rc < 0) { + mutex_unlock(&cd->system_lock); + dev_err(cd->dev, "%s: Fail read mode r=%d\n", + __func__, rc); + goto exit; + } + + /* Clear device mode bits and set to new mode */ + mode &= ~CY_HST_MODE; + mode |= new_dev_mode | CY_HST_MODE_CHANGE; + + cd->int_status |= CY_INT_MODE_CHANGE; + rc = cyttsp4_adap_write(cd, CY_REG_BASE, sizeof(mode), &mode); + mutex_unlock(&cd->system_lock); + if (rc < 0) { + dev_err(cd->dev, "%s: Fail write mode change r=%d\n", + __func__, rc); + goto exit; + } + + /* wait for mode change done interrupt */ + t = wait_event_timeout(cd->wait_q, + (cd->int_status & CY_INT_MODE_CHANGE) == 0, + msecs_to_jiffies(CY_CORE_MODE_CHANGE_TIMEOUT)); + dev_dbg(cd->dev, "%s: back from wait t=%ld cd->mode=%d\n", + __func__, t, cd->mode); + + if (IS_TMO(t)) { + dev_err(cd->dev, "%s: %s\n", __func__, + "tmo waiting mode change"); + mutex_lock(&cd->system_lock); + cd->int_status &= ~CY_INT_MODE_CHANGE; + mutex_unlock(&cd->system_lock); + rc = -EINVAL; + } + +exit: + return rc; +} + +static void cyttsp4_watchdog_work(struct work_struct *work) +{ + struct cyttsp4 *cd = + container_of(work, struct cyttsp4, watchdog_work); + u8 *mode; + int retval; + + if (cd == NULL) { + dev_err(cd->dev, "%s: NULL context pointer\n", __func__); + return; + } + + mutex_lock(&cd->system_lock); + retval = cyttsp4_load_status_regs(cd); + if (retval < 0) { + dev_err(cd->dev, + "%s: failed to access device in watchdog timer r=%d\n", + __func__, retval); + cyttsp4_queue_startup_(cd); + goto cyttsp4_timer_watchdog_exit_error; + } + mode = &cd->sysinfo.xy_mode[CY_REG_BASE]; + if (IS_BOOTLOADER(mode[0], mode[1])) { + dev_err(cd->dev, + "%s: device found in bootloader mode when operational mode\n", + __func__); + cyttsp4_queue_startup_(cd); + goto cyttsp4_timer_watchdog_exit_error; + } + + cyttsp4_start_wd_timer(cd); +cyttsp4_timer_watchdog_exit_error: + mutex_unlock(&cd->system_lock); + return; +} + +static int cyttsp4_core_sleep_(struct cyttsp4 *cd) +{ + enum cyttsp4_sleep_state ss = SS_SLEEP_ON; + enum cyttsp4_int_state int_status = CY_INT_IGNORE; + int rc = 0; + u8 mode[2]; + + /* Already in sleep mode? */ + mutex_lock(&cd->system_lock); + if (cd->sleep_state == SS_SLEEP_ON) { + mutex_unlock(&cd->system_lock); + return 0; + } + cd->sleep_state = SS_SLEEPING; + mutex_unlock(&cd->system_lock); + + cyttsp4_stop_wd_timer(cd); + + /* Wait until currently running IRQ handler exits and disable IRQ */ + disable_irq(cd->irq); + + dev_vdbg(cd->dev, "%s: write DEEP SLEEP...\n", __func__); + mutex_lock(&cd->system_lock); + rc = cyttsp4_adap_read(cd, CY_REG_BASE, sizeof(mode), &mode); + if (rc) { + mutex_unlock(&cd->system_lock); + dev_err(cd->dev, "%s: Fail read adapter r=%d\n", __func__, rc); + goto error; + } + + if (IS_BOOTLOADER(mode[0], mode[1])) { + mutex_unlock(&cd->system_lock); + dev_err(cd->dev, "%s: Device in BOOTLADER mode.\n", __func__); + rc = -EINVAL; + goto error; + } + + mode[0] |= CY_HST_SLEEP; + rc = cyttsp4_adap_write(cd, CY_REG_BASE, sizeof(mode[0]), &mode[0]); + mutex_unlock(&cd->system_lock); + if (rc) { + dev_err(cd->dev, "%s: Fail write adapter r=%d\n", __func__, rc); + goto error; + } + dev_vdbg(cd->dev, "%s: write DEEP SLEEP succeeded\n", __func__); + + if (cd->cpdata->power) { + dev_dbg(cd->dev, "%s: Power down HW\n", __func__); + rc = cd->cpdata->power(cd->cpdata, 0, cd->dev, &cd->ignore_irq); + } else { + dev_dbg(cd->dev, "%s: No power function\n", __func__); + rc = 0; + } + if (rc < 0) { + dev_err(cd->dev, "%s: HW Power down fails r=%d\n", + __func__, rc); + goto error; + } + + /* Give time to FW to sleep */ + msleep(50); + + goto exit; + +error: + ss = SS_SLEEP_OFF; + int_status = CY_INT_NONE; + cyttsp4_start_wd_timer(cd); + +exit: + mutex_lock(&cd->system_lock); + cd->sleep_state = ss; + cd->int_status |= int_status; + mutex_unlock(&cd->system_lock); + enable_irq(cd->irq); + return rc; +} + +static int cyttsp4_core_sleep(struct cyttsp4 *cd) +{ + int rc; + + rc = cyttsp4_request_exclusive(cd, cd->dev, + CY_CORE_SLEEP_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return 0; + } + + rc = cyttsp4_core_sleep_(cd); + + if (cyttsp4_release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + else + dev_vdbg(cd->dev, "%s: pass release exclusive\n", __func__); + + return rc; +} + +static int cyttsp4_core_wake_(struct cyttsp4 *cd) +{ + struct device *dev = cd->dev; + int rc; + u8 mode; + int t; + + /* Already woken? */ + mutex_lock(&cd->system_lock); + if (cd->sleep_state == SS_SLEEP_OFF) { + mutex_unlock(&cd->system_lock); + return 0; + } + cd->int_status &= ~CY_INT_IGNORE; + cd->int_status |= CY_INT_AWAKE; + cd->sleep_state = SS_WAKING; + + if (cd->cpdata->power) { + dev_dbg(dev, "%s: Power up HW\n", __func__); + rc = cd->cpdata->power(cd->cpdata, 1, dev, &cd->ignore_irq); + } else { + dev_dbg(dev, "%s: No power function\n", __func__); + rc = -ENOSYS; + } + if (rc < 0) { + dev_err(dev, "%s: HW Power up fails r=%d\n", + __func__, rc); + + /* Initiate a read transaction to wake up */ + cyttsp4_adap_read(cd, CY_REG_BASE, sizeof(mode), &mode); + } else + dev_vdbg(cd->dev, "%s: HW power up succeeds\n", + __func__); + mutex_unlock(&cd->system_lock); + + t = wait_event_timeout(cd->wait_q, + (cd->int_status & CY_INT_AWAKE) == 0, + msecs_to_jiffies(CY_CORE_WAKEUP_TIMEOUT)); + if (IS_TMO(t)) { + dev_err(dev, "%s: TMO waiting for wakeup\n", __func__); + mutex_lock(&cd->system_lock); + cd->int_status &= ~CY_INT_AWAKE; + /* Try starting up */ + cyttsp4_queue_startup_(cd); + mutex_unlock(&cd->system_lock); + } + + mutex_lock(&cd->system_lock); + cd->sleep_state = SS_SLEEP_OFF; + mutex_unlock(&cd->system_lock); + + cyttsp4_start_wd_timer(cd); + + return 0; +} + +static int cyttsp4_core_wake(struct cyttsp4 *cd) +{ + int rc; + + rc = cyttsp4_request_exclusive(cd, cd->dev, + CY_CORE_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return 0; + } + + rc = cyttsp4_core_wake_(cd); + + if (cyttsp4_release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + else + dev_vdbg(cd->dev, "%s: pass release exclusive\n", __func__); + + return rc; +} + +static int cyttsp4_startup_(struct cyttsp4 *cd) +{ + int retry = CY_CORE_STARTUP_RETRY_COUNT; + int rc; + + cyttsp4_stop_wd_timer(cd); + +reset: + if (retry != CY_CORE_STARTUP_RETRY_COUNT) + dev_dbg(cd->dev, "%s: Retry %d\n", __func__, + CY_CORE_STARTUP_RETRY_COUNT - retry); + + /* reset hardware and wait for heartbeat */ + rc = cyttsp4_reset_and_wait(cd); + if (rc < 0) { + dev_err(cd->dev, "%s: Error on h/w reset r=%d\n", __func__, rc); + if (retry--) + goto reset; + goto exit; + } + + /* exit bl into sysinfo mode */ + dev_vdbg(cd->dev, "%s: write exit ldr...\n", __func__); + mutex_lock(&cd->system_lock); + cd->int_status &= ~CY_INT_IGNORE; + cd->int_status |= CY_INT_MODE_CHANGE; + + rc = cyttsp4_adap_write(cd, CY_REG_BASE, sizeof(ldr_exit), + (u8 *)ldr_exit); + mutex_unlock(&cd->system_lock); + if (rc < 0) { + dev_err(cd->dev, "%s: Fail write r=%d\n", __func__, rc); + if (retry--) + goto reset; + goto exit; + } + + rc = cyttsp4_wait_sysinfo_mode(cd); + if (rc < 0) { + u8 buf[sizeof(ldr_err_app)]; + int rc1; + + /* Check for invalid/corrupted touch application */ + rc1 = cyttsp4_adap_read(cd, CY_REG_BASE, sizeof(ldr_err_app), + buf); + if (rc1) { + dev_err(cd->dev, "%s: Fail read r=%d\n", __func__, rc1); + } else if (!memcmp(buf, ldr_err_app, sizeof(ldr_err_app))) { + dev_err(cd->dev, "%s: Error launching touch application\n", + __func__); + mutex_lock(&cd->system_lock); + cd->invalid_touch_app = true; + mutex_unlock(&cd->system_lock); + goto exit_no_wd; + } + + if (retry--) + goto reset; + goto exit; + } + + mutex_lock(&cd->system_lock); + cd->invalid_touch_app = false; + mutex_unlock(&cd->system_lock); + + /* read sysinfo data */ + dev_vdbg(cd->dev, "%s: get sysinfo regs..\n", __func__); + rc = cyttsp4_get_sysinfo_regs(cd); + if (rc < 0) { + dev_err(cd->dev, "%s: failed to get sysinfo regs rc=%d\n", + __func__, rc); + if (retry--) + goto reset; + goto exit; + } + + rc = cyttsp4_set_mode(cd, CY_MODE_OPERATIONAL); + if (rc < 0) { + dev_err(cd->dev, "%s: failed to set mode to operational rc=%d\n", + __func__, rc); + if (retry--) + goto reset; + goto exit; + } + + cyttsp4_lift_all(&cd->md); + + /* restore to sleep if was suspended */ + mutex_lock(&cd->system_lock); + if (cd->sleep_state == SS_SLEEP_ON) { + cd->sleep_state = SS_SLEEP_OFF; + mutex_unlock(&cd->system_lock); + cyttsp4_core_sleep_(cd); + goto exit_no_wd; + } + mutex_unlock(&cd->system_lock); + +exit: + cyttsp4_start_wd_timer(cd); +exit_no_wd: + return rc; +} + +static int cyttsp4_startup(struct cyttsp4 *cd) +{ + int rc; + + mutex_lock(&cd->system_lock); + cd->startup_state = STARTUP_RUNNING; + mutex_unlock(&cd->system_lock); + + rc = cyttsp4_request_exclusive(cd, cd->dev, + CY_CORE_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + goto exit; + } + + rc = cyttsp4_startup_(cd); + + if (cyttsp4_release_exclusive(cd, cd->dev) < 0) + /* Don't return fail code, mode is already changed. */ + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + else + dev_vdbg(cd->dev, "%s: pass release exclusive\n", __func__); + +exit: + mutex_lock(&cd->system_lock); + cd->startup_state = STARTUP_NONE; + mutex_unlock(&cd->system_lock); + + /* Wake the waiters for end of startup */ + wake_up(&cd->wait_q); + + return rc; +} + +static void cyttsp4_startup_work_function(struct work_struct *work) +{ + struct cyttsp4 *cd = container_of(work, struct cyttsp4, startup_work); + int rc; + + rc = cyttsp4_startup(cd); + if (rc < 0) + dev_err(cd->dev, "%s: Fail queued startup r=%d\n", + __func__, rc); +} + +static void cyttsp4_free_si_ptrs(struct cyttsp4 *cd) +{ + struct cyttsp4_sysinfo *si = &cd->sysinfo; + + if (!si) + return; + + kfree(si->si_ptrs.cydata); + kfree(si->si_ptrs.test); + kfree(si->si_ptrs.pcfg); + kfree(si->si_ptrs.opcfg); + kfree(si->si_ptrs.ddata); + kfree(si->si_ptrs.mdata); + kfree(si->btn); + kfree(si->xy_mode); + kfree(si->xy_data); + kfree(si->btn_rec_data); +} + +#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM_RUNTIME) +static int cyttsp4_core_suspend(struct device *dev) +{ + struct cyttsp4 *cd = dev_get_drvdata(dev); + struct cyttsp4_mt_data *md = &cd->md; + int rc; + + md->is_suspended = true; + + rc = cyttsp4_core_sleep(cd); + if (rc < 0) { + dev_err(dev, "%s: Error on sleep\n", __func__); + return -EAGAIN; + } + return 0; +} + +static int cyttsp4_core_resume(struct device *dev) +{ + struct cyttsp4 *cd = dev_get_drvdata(dev); + struct cyttsp4_mt_data *md = &cd->md; + int rc; + + md->is_suspended = false; + + rc = cyttsp4_core_wake(cd); + if (rc < 0) { + dev_err(dev, "%s: Error on wake\n", __func__); + return -EAGAIN; + } + + return 0; +} +#endif + +const struct dev_pm_ops cyttsp4_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(cyttsp4_core_suspend, cyttsp4_core_resume) + SET_RUNTIME_PM_OPS(cyttsp4_core_suspend, cyttsp4_core_resume, NULL) +}; +EXPORT_SYMBOL_GPL(cyttsp4_pm_ops); + +static int cyttsp4_mt_open(struct input_dev *input) +{ + pm_runtime_get(input->dev.parent); + return 0; +} + +static void cyttsp4_mt_close(struct input_dev *input) +{ + struct cyttsp4_mt_data *md = input_get_drvdata(input); + mutex_lock(&md->report_lock); + if (!md->is_suspended) + pm_runtime_put(input->dev.parent); + mutex_unlock(&md->report_lock); +} + + +static int cyttsp4_setup_input_device(struct cyttsp4 *cd) +{ + struct device *dev = cd->dev; + struct cyttsp4_mt_data *md = &cd->md; + int signal = CY_IGNORE_VALUE; + int max_x, max_y, max_p, min, max; + int max_x_tmp, max_y_tmp; + int i; + int rc; + + dev_vdbg(dev, "%s: Initialize event signals\n", __func__); + __set_bit(EV_ABS, md->input->evbit); + __set_bit(EV_REL, md->input->evbit); + __set_bit(EV_KEY, md->input->evbit); + + max_x_tmp = md->si->si_ofs.max_x; + max_y_tmp = md->si->si_ofs.max_y; + + /* get maximum values from the sysinfo data */ + if (md->pdata->flags & CY_FLAG_FLIP) { + max_x = max_y_tmp - 1; + max_y = max_x_tmp - 1; + } else { + max_x = max_x_tmp - 1; + max_y = max_y_tmp - 1; + } + max_p = md->si->si_ofs.max_p; + + /* set event signal capabilities */ + for (i = 0; i < (md->pdata->frmwrk->size / CY_NUM_ABS_SET); i++) { + signal = md->pdata->frmwrk->abs + [(i * CY_NUM_ABS_SET) + CY_SIGNAL_OST]; + if (signal != CY_IGNORE_VALUE) { + __set_bit(signal, md->input->absbit); + min = md->pdata->frmwrk->abs + [(i * CY_NUM_ABS_SET) + CY_MIN_OST]; + max = md->pdata->frmwrk->abs + [(i * CY_NUM_ABS_SET) + CY_MAX_OST]; + if (i == CY_ABS_ID_OST) { + /* shift track ids down to start at 0 */ + max = max - min; + min = min - min; + } else if (i == CY_ABS_X_OST) + max = max_x; + else if (i == CY_ABS_Y_OST) + max = max_y; + else if (i == CY_ABS_P_OST) + max = max_p; + input_set_abs_params(md->input, signal, min, max, + md->pdata->frmwrk->abs + [(i * CY_NUM_ABS_SET) + CY_FUZZ_OST], + md->pdata->frmwrk->abs + [(i * CY_NUM_ABS_SET) + CY_FLAT_OST]); + dev_dbg(dev, "%s: register signal=%02X min=%d max=%d\n", + __func__, signal, min, max); + if ((i == CY_ABS_ID_OST) && + (md->si->si_ofs.tch_rec_size < + CY_TMA4XX_TCH_REC_SIZE)) + break; + } + } + + input_mt_init_slots(md->input, md->si->si_ofs.tch_abs[CY_TCH_T].max, + INPUT_MT_DIRECT); + rc = input_register_device(md->input); + if (rc < 0) + dev_err(dev, "%s: Error, failed register input device r=%d\n", + __func__, rc); + return rc; +} + +static int cyttsp4_mt_probe(struct cyttsp4 *cd) +{ + struct device *dev = cd->dev; + struct cyttsp4_mt_data *md = &cd->md; + struct cyttsp4_mt_platform_data *pdata = cd->pdata->mt_pdata; + int rc = 0; + + mutex_init(&md->report_lock); + md->pdata = pdata; + /* Create the input device and register it. */ + dev_vdbg(dev, "%s: Create the input device and register it\n", + __func__); + md->input = input_allocate_device(); + if (md->input == NULL) { + dev_err(dev, "%s: Error, failed to allocate input device\n", + __func__); + rc = -ENOSYS; + goto error_alloc_failed; + } + + md->input->name = pdata->inp_dev_name; + scnprintf(md->phys, sizeof(md->phys)-1, "%s", dev_name(dev)); + md->input->phys = md->phys; + md->input->id.bustype = cd->bus_ops->bustype; + md->input->dev.parent = dev; + md->input->open = cyttsp4_mt_open; + md->input->close = cyttsp4_mt_close; + input_set_drvdata(md->input, md); + + /* get sysinfo */ + md->si = &cd->sysinfo; + if (!md->si) { + dev_err(dev, "%s: Fail get sysinfo pointer from core p=%p\n", + __func__, md->si); + goto error_get_sysinfo; + } + + rc = cyttsp4_setup_input_device(cd); + if (rc) + goto error_init_input; + + return 0; + +error_init_input: + input_free_device(md->input); +error_get_sysinfo: + input_set_drvdata(md->input, NULL); +error_alloc_failed: + dev_err(dev, "%s failed.\n", __func__); + return rc; +} + +struct cyttsp4 *cyttsp4_probe(const struct cyttsp4_bus_ops *ops, + struct device *dev, u16 irq, size_t xfer_buf_size) +{ + struct cyttsp4 *cd; + struct cyttsp4_platform_data *pdata = dev_get_platdata(dev); + unsigned long irq_flags; + int rc = 0; + + if (!pdata || !pdata->core_pdata || !pdata->mt_pdata) { + dev_err(dev, "%s: Missing platform data\n", __func__); + rc = -ENODEV; + goto error_no_pdata; + } + + cd = kzalloc(sizeof(*cd), GFP_KERNEL); + if (!cd) { + dev_err(dev, "%s: Error, kzalloc\n", __func__); + rc = -ENOMEM; + goto error_alloc_data; + } + + cd->xfer_buf = kzalloc(xfer_buf_size, GFP_KERNEL); + if (!cd->xfer_buf) { + dev_err(dev, "%s: Error, kzalloc\n", __func__); + rc = -ENOMEM; + goto error_alloc_data; + } + + /* Initialize device info */ + cd->dev = dev; + cd->pdata = pdata; + cd->cpdata = pdata->core_pdata; + cd->bus_ops = ops; + + /* Initialize mutexes and spinlocks */ + mutex_init(&cd->system_lock); + mutex_init(&cd->adap_lock); + + /* Initialize wait queue */ + init_waitqueue_head(&cd->wait_q); + + /* Initialize works */ + INIT_WORK(&cd->startup_work, cyttsp4_startup_work_function); + INIT_WORK(&cd->watchdog_work, cyttsp4_watchdog_work); + + /* Initialize IRQ */ + cd->irq = gpio_to_irq(cd->cpdata->irq_gpio); + if (cd->irq < 0) { + rc = -EINVAL; + goto error_gpio_irq; + } + + dev_set_drvdata(dev, cd); + + /* Call platform init function */ + if (cd->cpdata->init) { + dev_dbg(cd->dev, "%s: Init HW\n", __func__); + rc = cd->cpdata->init(cd->cpdata, 1, cd->dev); + } else { + dev_dbg(cd->dev, "%s: No HW INIT function\n", __func__); + rc = 0; + } + if (rc < 0) + dev_err(cd->dev, "%s: HW Init fail r=%d\n", __func__, rc); + + dev_dbg(dev, "%s: initialize threaded irq=%d\n", __func__, cd->irq); + if (cd->cpdata->level_irq_udelay > 0) + /* use level triggered interrupts */ + irq_flags = IRQF_TRIGGER_LOW | IRQF_ONESHOT; + else + /* use edge triggered interrupts */ + irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; + + rc = request_threaded_irq(cd->irq, NULL, cyttsp4_irq, irq_flags, + dev_name(dev), cd); + if (rc < 0) { + dev_err(dev, "%s: Error, could not request irq\n", __func__); + goto error_request_irq; + } + + /* Setup watchdog timer */ + setup_timer(&cd->watchdog_timer, cyttsp4_watchdog_timer, + (unsigned long)cd); + + /* + * call startup directly to ensure that the device + * is tested before leaving the probe + */ + rc = cyttsp4_startup(cd); + + /* Do not fail probe if startup fails but the device is detected */ + if (rc < 0 && cd->mode == CY_MODE_UNKNOWN) { + dev_err(cd->dev, "%s: Fail initial startup r=%d\n", + __func__, rc); + goto error_startup; + } + + rc = cyttsp4_mt_probe(cd); + if (rc < 0) { + dev_err(dev, "%s: Error, fail mt probe\n", __func__); + goto error_startup; + } + + pm_runtime_enable(dev); + + return cd; + +error_startup: + cancel_work_sync(&cd->startup_work); + cyttsp4_stop_wd_timer(cd); + pm_runtime_disable(dev); + cyttsp4_free_si_ptrs(cd); + free_irq(cd->irq, cd); +error_request_irq: + if (cd->cpdata->init) + cd->cpdata->init(cd->cpdata, 0, dev); + dev_set_drvdata(dev, NULL); +error_gpio_irq: + kfree(cd); +error_alloc_data: +error_no_pdata: + dev_err(dev, "%s failed.\n", __func__); + return ERR_PTR(rc); +} +EXPORT_SYMBOL_GPL(cyttsp4_probe); + +static void cyttsp4_mt_release(struct cyttsp4_mt_data *md) +{ + input_unregister_device(md->input); + input_set_drvdata(md->input, NULL); +} + +int cyttsp4_remove(struct cyttsp4 *cd) +{ + struct device *dev = cd->dev; + + cyttsp4_mt_release(&cd->md); + + /* + * Suspend the device before freeing the startup_work and stopping + * the watchdog since sleep function restarts watchdog on failure + */ + pm_runtime_suspend(dev); + pm_runtime_disable(dev); + + cancel_work_sync(&cd->startup_work); + + cyttsp4_stop_wd_timer(cd); + + free_irq(cd->irq, cd); + if (cd->cpdata->init) + cd->cpdata->init(cd->cpdata, 0, dev); + dev_set_drvdata(dev, NULL); + cyttsp4_free_si_ptrs(cd); + kfree(cd); + return 0; +} +EXPORT_SYMBOL_GPL(cyttsp4_remove); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Cypress TrueTouch(R) Standard touchscreen core driver"); +MODULE_AUTHOR("Cypress"); diff --git a/drivers/input/touchscreen/cyttsp4_core.h b/drivers/input/touchscreen/cyttsp4_core.h new file mode 100644 index 000000000000..86a254354136 --- /dev/null +++ b/drivers/input/touchscreen/cyttsp4_core.h @@ -0,0 +1,472 @@ +/* + * cyttsp4_core.h + * Cypress TrueTouch(TM) Standard Product V4 Core driver module. + * For use with Cypress Txx4xx parts. + * Supported parts include: + * TMA4XX + * TMA1036 + * + * Copyright (C) 2012 Cypress Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Cypress Semiconductor at www.cypress.com + * + */ + +#ifndef _LINUX_CYTTSP4_CORE_H +#define _LINUX_CYTTSP4_CORE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CY_REG_BASE 0x00 + +#define CY_POST_CODEL_WDG_RST 0x01 +#define CY_POST_CODEL_CFG_DATA_CRC_FAIL 0x02 +#define CY_POST_CODEL_PANEL_TEST_FAIL 0x04 + +#define CY_NUM_BTN_PER_REG 4 + +/* touch record system information offset masks and shifts */ +#define CY_BYTE_OFS_MASK 0x1F +#define CY_BOFS_MASK 0xE0 +#define CY_BOFS_SHIFT 5 + +#define CY_TMA1036_TCH_REC_SIZE 6 +#define CY_TMA4XX_TCH_REC_SIZE 9 +#define CY_TMA1036_MAX_TCH 0x0E +#define CY_TMA4XX_MAX_TCH 0x1E + +#define CY_NORMAL_ORIGIN 0 /* upper, left corner */ +#define CY_INVERT_ORIGIN 1 /* lower, right corner */ + +/* helpers */ +#define GET_NUM_TOUCHES(x) ((x) & 0x1F) +#define IS_LARGE_AREA(x) ((x) & 0x20) +#define IS_BAD_PKT(x) ((x) & 0x20) +#define IS_BOOTLOADER(hst_mode, reset_detect) \ + ((hst_mode) & 0x01 || (reset_detect) != 0) +#define IS_TMO(t) ((t) == 0) + + +enum cyttsp_cmd_bits { + CY_CMD_COMPLETE = (1 << 6), +}; + +/* Timeout in ms. */ +#define CY_WATCHDOG_TIMEOUT 1000 + +#define CY_MAX_PRINT_SIZE 512 +#ifdef VERBOSE_DEBUG +#define CY_MAX_PRBUF_SIZE PIPE_BUF +#define CY_PR_TRUNCATED " truncated..." +#endif + +enum cyttsp4_ic_grpnum { + CY_IC_GRPNUM_RESERVED, + CY_IC_GRPNUM_CMD_REGS, + CY_IC_GRPNUM_TCH_REP, + CY_IC_GRPNUM_DATA_REC, + CY_IC_GRPNUM_TEST_REC, + CY_IC_GRPNUM_PCFG_REC, + CY_IC_GRPNUM_TCH_PARM_VAL, + CY_IC_GRPNUM_TCH_PARM_SIZE, + CY_IC_GRPNUM_RESERVED1, + CY_IC_GRPNUM_RESERVED2, + CY_IC_GRPNUM_OPCFG_REC, + CY_IC_GRPNUM_DDATA_REC, + CY_IC_GRPNUM_MDATA_REC, + CY_IC_GRPNUM_TEST_REGS, + CY_IC_GRPNUM_BTN_KEYS, + CY_IC_GRPNUM_TTHE_REGS, + CY_IC_GRPNUM_NUM +}; + +enum cyttsp4_int_state { + CY_INT_NONE, + CY_INT_IGNORE = (1 << 0), + CY_INT_MODE_CHANGE = (1 << 1), + CY_INT_EXEC_CMD = (1 << 2), + CY_INT_AWAKE = (1 << 3), +}; + +enum cyttsp4_mode { + CY_MODE_UNKNOWN, + CY_MODE_BOOTLOADER = (1 << 1), + CY_MODE_OPERATIONAL = (1 << 2), + CY_MODE_SYSINFO = (1 << 3), + CY_MODE_CAT = (1 << 4), + CY_MODE_STARTUP = (1 << 5), + CY_MODE_LOADER = (1 << 6), + CY_MODE_CHANGE_MODE = (1 << 7), + CY_MODE_CHANGED = (1 << 8), + CY_MODE_CMD_COMPLETE = (1 << 9), +}; + +enum cyttsp4_sleep_state { + SS_SLEEP_OFF, + SS_SLEEP_ON, + SS_SLEEPING, + SS_WAKING, +}; + +enum cyttsp4_startup_state { + STARTUP_NONE, + STARTUP_QUEUED, + STARTUP_RUNNING, +}; + +#define CY_NUM_REVCTRL 8 +struct cyttsp4_cydata { + u8 ttpidh; + u8 ttpidl; + u8 fw_ver_major; + u8 fw_ver_minor; + u8 revctrl[CY_NUM_REVCTRL]; + u8 blver_major; + u8 blver_minor; + u8 jtag_si_id3; + u8 jtag_si_id2; + u8 jtag_si_id1; + u8 jtag_si_id0; + u8 mfgid_sz; + u8 cyito_idh; + u8 cyito_idl; + u8 cyito_verh; + u8 cyito_verl; + u8 ttsp_ver_major; + u8 ttsp_ver_minor; + u8 device_info; + u8 mfg_id[]; +} __packed; + +struct cyttsp4_test { + u8 post_codeh; + u8 post_codel; +} __packed; + +struct cyttsp4_pcfg { + u8 electrodes_x; + u8 electrodes_y; + u8 len_xh; + u8 len_xl; + u8 len_yh; + u8 len_yl; + u8 res_xh; + u8 res_xl; + u8 res_yh; + u8 res_yl; + u8 max_zh; + u8 max_zl; + u8 panel_info0; +} __packed; + +struct cyttsp4_tch_rec_params { + u8 loc; + u8 size; +} __packed; + +#define CY_NUM_TCH_FIELDS 7 +#define CY_NUM_EXT_TCH_FIELDS 3 +struct cyttsp4_opcfg { + u8 cmd_ofs; + u8 rep_ofs; + u8 rep_szh; + u8 rep_szl; + u8 num_btns; + u8 tt_stat_ofs; + u8 obj_cfg0; + u8 max_tchs; + u8 tch_rec_size; + struct cyttsp4_tch_rec_params tch_rec_old[CY_NUM_TCH_FIELDS]; + u8 btn_rec_size; /* btn record size (in bytes) */ + u8 btn_diff_ofs; /* btn data loc, diff counts */ + u8 btn_diff_size; /* btn size of diff counts (in bits) */ + struct cyttsp4_tch_rec_params tch_rec_new[CY_NUM_EXT_TCH_FIELDS]; +} __packed; + +struct cyttsp4_sysinfo_ptr { + struct cyttsp4_cydata *cydata; + struct cyttsp4_test *test; + struct cyttsp4_pcfg *pcfg; + struct cyttsp4_opcfg *opcfg; + struct cyttsp4_ddata *ddata; + struct cyttsp4_mdata *mdata; +} __packed; + +struct cyttsp4_sysinfo_data { + u8 hst_mode; + u8 reserved; + u8 map_szh; + u8 map_szl; + u8 cydata_ofsh; + u8 cydata_ofsl; + u8 test_ofsh; + u8 test_ofsl; + u8 pcfg_ofsh; + u8 pcfg_ofsl; + u8 opcfg_ofsh; + u8 opcfg_ofsl; + u8 ddata_ofsh; + u8 ddata_ofsl; + u8 mdata_ofsh; + u8 mdata_ofsl; +} __packed; + +enum cyttsp4_tch_abs { /* for ordering within the extracted touch data array */ + CY_TCH_X, /* X */ + CY_TCH_Y, /* Y */ + CY_TCH_P, /* P (Z) */ + CY_TCH_T, /* TOUCH ID */ + CY_TCH_E, /* EVENT ID */ + CY_TCH_O, /* OBJECT ID */ + CY_TCH_W, /* SIZE */ + CY_TCH_MAJ, /* TOUCH_MAJOR */ + CY_TCH_MIN, /* TOUCH_MINOR */ + CY_TCH_OR, /* ORIENTATION */ + CY_TCH_NUM_ABS +}; + +static const char * const cyttsp4_tch_abs_string[] = { + [CY_TCH_X] = "X", + [CY_TCH_Y] = "Y", + [CY_TCH_P] = "P", + [CY_TCH_T] = "T", + [CY_TCH_E] = "E", + [CY_TCH_O] = "O", + [CY_TCH_W] = "W", + [CY_TCH_MAJ] = "MAJ", + [CY_TCH_MIN] = "MIN", + [CY_TCH_OR] = "OR", + [CY_TCH_NUM_ABS] = "INVALID" +}; + +struct cyttsp4_touch { + int abs[CY_TCH_NUM_ABS]; +}; + +struct cyttsp4_tch_abs_params { + size_t ofs; /* abs byte offset */ + size_t size; /* size in bits */ + size_t max; /* max value */ + size_t bofs; /* bit offset */ +}; + +struct cyttsp4_sysinfo_ofs { + size_t chip_type; + size_t cmd_ofs; + size_t rep_ofs; + size_t rep_sz; + size_t num_btns; + size_t num_btn_regs; /* ceil(num_btns/4) */ + size_t tt_stat_ofs; + size_t tch_rec_size; + size_t obj_cfg0; + size_t max_tchs; + size_t mode_size; + size_t data_size; + size_t map_sz; + size_t max_x; + size_t x_origin; /* left or right corner */ + size_t max_y; + size_t y_origin; /* upper or lower corner */ + size_t max_p; + size_t cydata_ofs; + size_t test_ofs; + size_t pcfg_ofs; + size_t opcfg_ofs; + size_t ddata_ofs; + size_t mdata_ofs; + size_t cydata_size; + size_t test_size; + size_t pcfg_size; + size_t opcfg_size; + size_t ddata_size; + size_t mdata_size; + size_t btn_keys_size; + struct cyttsp4_tch_abs_params tch_abs[CY_TCH_NUM_ABS]; + size_t btn_rec_size; /* btn record size (in bytes) */ + size_t btn_diff_ofs;/* btn data loc ,diff counts, (Op-Mode byte ofs) */ + size_t btn_diff_size;/* btn size of diff counts (in bits) */ +}; + +enum cyttsp4_btn_state { + CY_BTN_RELEASED, + CY_BTN_PRESSED, + CY_BTN_NUM_STATE +}; + +struct cyttsp4_btn { + bool enabled; + int state; /* CY_BTN_PRESSED, CY_BTN_RELEASED */ + int key_code; +}; + +struct cyttsp4_sysinfo { + bool ready; + struct cyttsp4_sysinfo_data si_data; + struct cyttsp4_sysinfo_ptr si_ptrs; + struct cyttsp4_sysinfo_ofs si_ofs; + struct cyttsp4_btn *btn; /* button states */ + u8 *btn_rec_data; /* button diff count data */ + u8 *xy_mode; /* operational mode and status regs */ + u8 *xy_data; /* operational touch regs */ +}; + +struct cyttsp4_mt_data { + struct cyttsp4_mt_platform_data *pdata; + struct cyttsp4_sysinfo *si; + struct input_dev *input; + struct mutex report_lock; + bool is_suspended; + char phys[NAME_MAX]; + int num_prv_tch; +}; + +struct cyttsp4 { + struct device *dev; + struct mutex system_lock; + struct mutex adap_lock; + enum cyttsp4_mode mode; + enum cyttsp4_sleep_state sleep_state; + enum cyttsp4_startup_state startup_state; + int int_status; + wait_queue_head_t wait_q; + int irq; + struct work_struct startup_work; + struct work_struct watchdog_work; + struct timer_list watchdog_timer; + struct cyttsp4_sysinfo sysinfo; + void *exclusive_dev; + int exclusive_waits; + atomic_t ignore_irq; + bool invalid_touch_app; + struct cyttsp4_mt_data md; + struct cyttsp4_platform_data *pdata; + struct cyttsp4_core_platform_data *cpdata; + const struct cyttsp4_bus_ops *bus_ops; + u8 *xfer_buf; +#ifdef VERBOSE_DEBUG + u8 pr_buf[CY_MAX_PRBUF_SIZE]; +#endif +}; + +struct cyttsp4_bus_ops { + u16 bustype; + int (*write)(struct device *dev, u8 *xfer_buf, u8 addr, u8 length, + const void *values); + int (*read)(struct device *dev, u8 *xfer_buf, u8 addr, u8 length, + void *values); +}; + +enum cyttsp4_hst_mode_bits { + CY_HST_TOGGLE = (1 << 7), + CY_HST_MODE_CHANGE = (1 << 3), + CY_HST_MODE = (7 << 4), + CY_HST_OPERATE = (0 << 4), + CY_HST_SYSINFO = (1 << 4), + CY_HST_CAT = (2 << 4), + CY_HST_LOWPOW = (1 << 2), + CY_HST_SLEEP = (1 << 1), + CY_HST_RESET = (1 << 0), +}; + +/* abs settings */ +#define CY_IGNORE_VALUE 0xFFFF + +/* abs signal capabilities offsets in the frameworks array */ +enum cyttsp4_sig_caps { + CY_SIGNAL_OST, + CY_MIN_OST, + CY_MAX_OST, + CY_FUZZ_OST, + CY_FLAT_OST, + CY_NUM_ABS_SET /* number of signal capability fields */ +}; + +/* abs axis signal offsets in the framworks array */ +enum cyttsp4_sig_ost { + CY_ABS_X_OST, + CY_ABS_Y_OST, + CY_ABS_P_OST, + CY_ABS_W_OST, + CY_ABS_ID_OST, + CY_ABS_MAJ_OST, + CY_ABS_MIN_OST, + CY_ABS_OR_OST, + CY_NUM_ABS_OST /* number of abs signals */ +}; + +enum cyttsp4_flags { + CY_FLAG_NONE = 0x00, + CY_FLAG_HOVER = 0x04, + CY_FLAG_FLIP = 0x08, + CY_FLAG_INV_X = 0x10, + CY_FLAG_INV_Y = 0x20, + CY_FLAG_VKEYS = 0x40, +}; + +enum cyttsp4_object_id { + CY_OBJ_STANDARD_FINGER, + CY_OBJ_LARGE_OBJECT, + CY_OBJ_STYLUS, + CY_OBJ_HOVER, +}; + +enum cyttsp4_event_id { + CY_EV_NO_EVENT, + CY_EV_TOUCHDOWN, + CY_EV_MOVE, /* significant displacement (> act dist) */ + CY_EV_LIFTOFF, /* record reports last position */ +}; + +/* x-axis resolution of panel in pixels */ +#define CY_PCFG_RESOLUTION_X_MASK 0x7F + +/* y-axis resolution of panel in pixels */ +#define CY_PCFG_RESOLUTION_Y_MASK 0x7F + +/* x-axis, 0:origin is on left side of panel, 1: right */ +#define CY_PCFG_ORIGIN_X_MASK 0x80 + +/* y-axis, 0:origin is on top side of panel, 1: bottom */ +#define CY_PCFG_ORIGIN_Y_MASK 0x80 + +static inline int cyttsp4_adap_read(struct cyttsp4 *ts, u8 addr, int size, + void *buf) +{ + return ts->bus_ops->read(ts->dev, ts->xfer_buf, addr, size, buf); +} + +static inline int cyttsp4_adap_write(struct cyttsp4 *ts, u8 addr, int size, + const void *buf) +{ + return ts->bus_ops->write(ts->dev, ts->xfer_buf, addr, size, buf); +} + +extern struct cyttsp4 *cyttsp4_probe(const struct cyttsp4_bus_ops *ops, + struct device *dev, u16 irq, size_t xfer_buf_size); +extern int cyttsp4_remove(struct cyttsp4 *ts); +int cyttsp_i2c_write_block_data(struct device *dev, u8 *xfer_buf, u8 addr, + u8 length, const void *values); +int cyttsp_i2c_read_block_data(struct device *dev, u8 *xfer_buf, u8 addr, + u8 length, void *values); +extern const struct dev_pm_ops cyttsp4_pm_ops; + +#endif /* _LINUX_CYTTSP4_CORE_H */ diff --git a/include/linux/platform_data/cyttsp4.h b/include/linux/platform_data/cyttsp4.h new file mode 100644 index 000000000000..6eba54aff1dc --- /dev/null +++ b/include/linux/platform_data/cyttsp4.h @@ -0,0 +1,76 @@ +/* + * Header file for: + * Cypress TrueTouch(TM) Standard Product (TTSP) touchscreen drivers. + * For use with Cypress Txx3xx parts. + * Supported parts include: + * CY8CTST341 + * CY8CTMA340 + * + * Copyright (C) 2009, 2010, 2011 Cypress Semiconductor, Inc. + * Copyright (C) 2012 Javier Martinez Canillas + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Contact Cypress Semiconductor at www.cypress.com (kev@cypress.com) + * + */ +#ifndef _CYTTSP4_H_ +#define _CYTTSP4_H_ + +#define CYTTSP4_MT_NAME "cyttsp4_mt" +#define CYTTSP4_I2C_NAME "cyttsp4_i2c_adapter" +#define CYTTSP4_SPI_NAME "cyttsp4_spi_adapter" + +#define CY_TOUCH_SETTINGS_MAX 32 + +struct touch_framework { + const uint16_t *abs; + uint8_t size; + uint8_t enable_vkeys; +} __packed; + +struct cyttsp4_mt_platform_data { + struct touch_framework *frmwrk; + unsigned short flags; + char const *inp_dev_name; +}; + +struct touch_settings { + const uint8_t *data; + uint32_t size; + uint8_t tag; +} __packed; + +struct cyttsp4_core_platform_data { + int irq_gpio; + int rst_gpio; + int level_irq_udelay; + int (*xres)(struct cyttsp4_core_platform_data *pdata, + struct device *dev); + int (*init)(struct cyttsp4_core_platform_data *pdata, + int on, struct device *dev); + int (*power)(struct cyttsp4_core_platform_data *pdata, + int on, struct device *dev, atomic_t *ignore_irq); + int (*irq_stat)(struct cyttsp4_core_platform_data *pdata, + struct device *dev); + struct touch_settings *sett[CY_TOUCH_SETTINGS_MAX]; +}; + +struct cyttsp4_platform_data { + struct cyttsp4_core_platform_data *core_pdata; + struct cyttsp4_mt_platform_data *mt_pdata; +}; + +#endif /* _CYTTSP4_H_ */ -- cgit v1.2.3 From e4760363ea14b7b707ba68ab780c8ecf98a84c15 Mon Sep 17 00:00:00 2001 From: Suman Anna Date: Mon, 1 Jul 2013 15:30:51 +0300 Subject: remoteproc/omap: fix a sparse warning This patch fixes a sparse warning in the omap remoteproc code when OMAP_REMOTEPROC is disabled. include/linux/platform_data/remoteproc-omap.h:76:13: warning: symbol 'omap_rproc_reserve_cma' was not declared. Should it be static? Signed-off-by: Suman Anna Signed-off-by: Ohad Ben-Cohen --- include/linux/platform_data/remoteproc-omap.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/linux/platform_data') diff --git a/include/linux/platform_data/remoteproc-omap.h b/include/linux/platform_data/remoteproc-omap.h index 3c1c6444ec4b..bfbd12b41162 100644 --- a/include/linux/platform_data/remoteproc-omap.h +++ b/include/linux/platform_data/remoteproc-omap.h @@ -50,7 +50,7 @@ void __init omap_rproc_reserve_cma(void); #else -void __init omap_rproc_reserve_cma(void) +static inline void __init omap_rproc_reserve_cma(void) { } -- cgit v1.2.3 From 290ad0f9d954b445788bf26652b239c59cec2060 Mon Sep 17 00:00:00 2001 From: Markus Pargmann Date: Sun, 26 May 2013 11:53:20 +0200 Subject: dma: imx-dma: Add oftree support Adding devicetree support for imx-dma driver. Use driver name for function 'imx_dma_is_general_purpose' because the devicename for devicetree initialized devices is different. Signed-off-by: Markus Pargmann Reviewed-by: Arnd Bergmann Reviewed-by: Shawn Guo Acked-by: Sascha Hauer Signed-off-by: Vinod Koul --- .../devicetree/bindings/dma/fsl-imx-dma.txt | 48 ++++++++++++++ drivers/dma/imx-dma.c | 75 ++++++++++++++++++++++ include/linux/platform_data/dma-imx.h | 6 +- 3 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 Documentation/devicetree/bindings/dma/fsl-imx-dma.txt (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/dma/fsl-imx-dma.txt b/Documentation/devicetree/bindings/dma/fsl-imx-dma.txt new file mode 100644 index 000000000000..2717ecb47db9 --- /dev/null +++ b/Documentation/devicetree/bindings/dma/fsl-imx-dma.txt @@ -0,0 +1,48 @@ +* Freescale Direct Memory Access (DMA) Controller for i.MX + +This document will only describe differences to the generic DMA Controller and +DMA request bindings as described in dma/dma.txt . + +* DMA controller + +Required properties: +- compatible : Should be "fsl,-dma". chip can be imx1, imx21 or imx27 +- reg : Should contain DMA registers location and length +- interrupts : First item should be DMA interrupt, second one is optional and + should contain DMA Error interrupt +- #dma-cells : Has to be 1. imx-dma does not support anything else. + +Optional properties: +- #dma-channels : Number of DMA channels supported. Should be 16. +- #dma-requests : Number of DMA requests supported. + +Example: + + dma: dma@10001000 { + compatible = "fsl,imx27-dma"; + reg = <0x10001000 0x1000>; + interrupts = <32 33>; + #dma-cells = <1>; + #dma-channels = <16>; + }; + + +* DMA client + +Clients have to specify the DMA requests with phandles in a list. + +Required properties: +- dmas: List of one or more DMA request specifiers. One DMA request specifier + consists of a phandle to the DMA controller followed by the integer + specifiying the request line. +- dma-names: List of string identifiers for the DMA requests. For the correct + names, have a look at the specific client driver. + +Example: + + sdhci1: sdhci@10013000 { + ... + dmas = <&dma 7>; + dma-names = "rx-tx"; + ... + }; diff --git a/drivers/dma/imx-dma.c b/drivers/dma/imx-dma.c index f28583370d00..34c54cf08231 100644 --- a/drivers/dma/imx-dma.c +++ b/drivers/dma/imx-dma.c @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include @@ -186,6 +188,11 @@ struct imxdma_engine { enum imx_dma_type devtype; }; +struct imxdma_filter_data { + struct imxdma_engine *imxdma; + int request; +}; + static struct platform_device_id imx_dma_devtype[] = { { .name = "imx1-dma", @@ -202,6 +209,22 @@ static struct platform_device_id imx_dma_devtype[] = { }; MODULE_DEVICE_TABLE(platform, imx_dma_devtype); +static const struct of_device_id imx_dma_of_dev_id[] = { + { + .compatible = "fsl,imx1-dma", + .data = &imx_dma_devtype[IMX1_DMA], + }, { + .compatible = "fsl,imx21-dma", + .data = &imx_dma_devtype[IMX21_DMA], + }, { + .compatible = "fsl,imx27-dma", + .data = &imx_dma_devtype[IMX27_DMA], + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, imx_dma_of_dev_id); + static inline int is_imx1_dma(struct imxdma_engine *imxdma) { return imxdma->devtype == IMX1_DMA; @@ -996,13 +1019,50 @@ static void imxdma_issue_pending(struct dma_chan *chan) spin_unlock_irqrestore(&imxdma->lock, flags); } +static bool imxdma_filter_fn(struct dma_chan *chan, void *param) +{ + struct imxdma_filter_data *fdata = param; + struct imxdma_channel *imxdma_chan = to_imxdma_chan(chan); + + if (chan->device->dev != fdata->imxdma->dev) + return false; + + imxdma_chan->dma_request = fdata->request; + chan->private = NULL; + + return true; +} + +static struct dma_chan *imxdma_xlate(struct of_phandle_args *dma_spec, + struct of_dma *ofdma) +{ + int count = dma_spec->args_count; + struct imxdma_engine *imxdma = ofdma->of_dma_data; + struct imxdma_filter_data fdata = { + .imxdma = imxdma, + }; + + if (count != 1) + return NULL; + + fdata.request = dma_spec->args[0]; + + return dma_request_channel(imxdma->dma_device.cap_mask, + imxdma_filter_fn, &fdata); +} + static int __init imxdma_probe(struct platform_device *pdev) { struct imxdma_engine *imxdma; struct resource *res; + const struct of_device_id *of_id; int ret, i; int irq, irq_err; + of_id = of_match_device(imx_dma_of_dev_id, &pdev->dev); + if (of_id) + pdev->id_entry = of_id->data; + imxdma = devm_kzalloc(&pdev->dev, sizeof(*imxdma), GFP_KERNEL); if (!imxdma) return -ENOMEM; @@ -1136,8 +1196,19 @@ static int __init imxdma_probe(struct platform_device *pdev) goto err; } + if (pdev->dev.of_node) { + ret = of_dma_controller_register(pdev->dev.of_node, + imxdma_xlate, imxdma); + if (ret) { + dev_err(&pdev->dev, "unable to register of_dma_controller\n"); + goto err_of_dma_controller; + } + } + return 0; +err_of_dma_controller: + dma_async_device_unregister(&imxdma->dma_device); err: clk_disable_unprepare(imxdma->dma_ipg); clk_disable_unprepare(imxdma->dma_ahb); @@ -1150,6 +1221,9 @@ static int imxdma_remove(struct platform_device *pdev) dma_async_device_unregister(&imxdma->dma_device); + if (pdev->dev.of_node) + of_dma_controller_free(pdev->dev.of_node); + clk_disable_unprepare(imxdma->dma_ipg); clk_disable_unprepare(imxdma->dma_ahb); @@ -1159,6 +1233,7 @@ static int imxdma_remove(struct platform_device *pdev) static struct platform_driver imxdma_driver = { .driver = { .name = "imx-dma", + .of_match_table = imx_dma_of_dev_id, }, .id_table = imx_dma_devtype, .remove = imxdma_remove, diff --git a/include/linux/platform_data/dma-imx.h b/include/linux/platform_data/dma-imx.h index f6d30cc1cb77..beac6b8b6a7b 100644 --- a/include/linux/platform_data/dma-imx.h +++ b/include/linux/platform_data/dma-imx.h @@ -60,10 +60,8 @@ static inline int imx_dma_is_ipu(struct dma_chan *chan) static inline int imx_dma_is_general_purpose(struct dma_chan *chan) { - return strstr(dev_name(chan->device->dev), "sdma") || - !strcmp(dev_name(chan->device->dev), "imx1-dma") || - !strcmp(dev_name(chan->device->dev), "imx21-dma") || - !strcmp(dev_name(chan->device->dev), "imx27-dma"); + return !strcmp(chan->device->dev->driver->name, "imx-sdma") || + !strcmp(chan->device->dev->driver->name, "imx-dma"); } #endif -- cgit v1.2.3 From 72ae6e4b31e40397eaa81007b39a1074638a6798 Mon Sep 17 00:00:00 2001 From: Nicolas Ferre Date: Fri, 10 May 2013 15:19:14 +0200 Subject: dmaengine: at_hdmac: extend hardware handshaking interface identification Peripheral handshaking identification numbers can be bigger than 15, so new fields have been created in the CFG register. Add macros to take this modification into account and use them in at_dma_xlate() function. Signed-off-by: Nicolas Ferre Acked-by: Jean-Christophe PLAGNIOL-VILLARD Signed-off-by: Vinod Koul --- drivers/dma/at_hdmac.c | 2 ++ include/linux/platform_data/dma-atmel.h | 4 ++++ 2 files changed, 6 insertions(+) (limited to 'include/linux/platform_data') diff --git a/drivers/dma/at_hdmac.c b/drivers/dma/at_hdmac.c index cd494209352a..78c3fb4b4e40 100644 --- a/drivers/dma/at_hdmac.c +++ b/drivers/dma/at_hdmac.c @@ -1230,6 +1230,8 @@ static struct dma_chan *at_dma_xlate(struct of_phandle_args *dma_spec, per_id = dma_spec->args[1]; atslave->cfg = ATC_FIFOCFG_HALFFIFO | ATC_DST_H2SEL_HW | ATC_SRC_H2SEL_HW | ATC_DST_PER(per_id) + | ATC_DST_PER_MSB(per_id) + | ATC_SRC_PER_MSB(per_id) | ATC_SRC_PER(per_id); atslave->dma_dev = &dmac_pdev->dev; diff --git a/include/linux/platform_data/dma-atmel.h b/include/linux/platform_data/dma-atmel.h index cab0997be3de..e95f19c65873 100644 --- a/include/linux/platform_data/dma-atmel.h +++ b/include/linux/platform_data/dma-atmel.h @@ -35,16 +35,20 @@ struct at_dma_slave { /* Platform-configurable bits in CFG */ +#define ATC_PER_MSB(h) ((0x30U & (h)) >> 4) /* Extract most significant bits of a handshaking identifier */ + #define ATC_SRC_PER(h) (0xFU & (h)) /* Channel src rq associated with periph handshaking ifc h */ #define ATC_DST_PER(h) ((0xFU & (h)) << 4) /* Channel dst rq associated with periph handshaking ifc h */ #define ATC_SRC_REP (0x1 << 8) /* Source Replay Mod */ #define ATC_SRC_H2SEL (0x1 << 9) /* Source Handshaking Mod */ #define ATC_SRC_H2SEL_SW (0x0 << 9) #define ATC_SRC_H2SEL_HW (0x1 << 9) +#define ATC_SRC_PER_MSB(h) (ATC_PER_MSB(h) << 10) /* Channel src rq (most significant bits) */ #define ATC_DST_REP (0x1 << 12) /* Destination Replay Mod */ #define ATC_DST_H2SEL (0x1 << 13) /* Destination Handshaking Mod */ #define ATC_DST_H2SEL_SW (0x0 << 13) #define ATC_DST_H2SEL_HW (0x1 << 13) +#define ATC_DST_PER_MSB(h) (ATC_PER_MSB(h) << 14) /* Channel dst rq (most significant bits) */ #define ATC_SOD (0x1 << 16) /* Stop On Done */ #define ATC_LOCK_IF (0x1 << 20) /* Interface Lock */ #define ATC_LOCK_B (0x1 << 21) /* AHB Bus Lock */ -- cgit v1.2.3