From af8482523ef1510c3bfcb50515fa41ca1ab2fea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:13:21 +0200 Subject: [PATCH 01/28] mc13892_wdog_reboot mc13892: reboot on wdi event By default, on wdi (watchdog input) event the mc13892 is powering off. This patch allows to change this behaviour throught platform_data. v3: - fix CodingStyle v2: - move to mc13xxx-core - make it configurable Signed-off-by: Arnaud Patard --- drivers/mfd/mc13xxx-core.c | 13 +++++++++++++ include/linux/mfd/mc13xxx.h | 2 ++ 2 files changed, 15 insertions(+), 0 deletions(-) diff --git a/drivers/mfd/mc13xxx-core.c b/drivers/mfd/mc13xxx-core.c index 7e4d44b..2b8fd3c 100644 --- a/drivers/mfd/mc13xxx-core.c +++ b/drivers/mfd/mc13xxx-core.c @@ -136,6 +136,9 @@ EXPORT_SYMBOL(mc13783_to_mc13xxx); #define MC13XXX_REVISION_FAB (0x03 << 11) #define MC13XXX_REVISION_ICIDCODE (0x3f << 13) +#define MC13892_POWERCTL2 15 +#define MC13892_POWERCTL2_WDIRESET (1 << 12) + #define MC13783_ADC1 44 #define MC13783_ADC1_ADEN (1 << 0) #define MC13783_ADC1_RAND (1 << 1) @@ -715,6 +718,7 @@ static int mc13xxx_probe(struct spi_device *spi) struct mc13xxx_platform_data *pdata = dev_get_platdata(&spi->dev); enum mc13xxx_id id; int ret; + unsigned int val; mc13xxx = kzalloc(sizeof(*mc13xxx), GFP_KERNEL); if (!mc13xxx) @@ -734,6 +738,15 @@ static int mc13xxx_probe(struct spi_device *spi) if (ret || id == MC13XXX_ID_INVALID) goto err_revision; + if (id == MC13XXX_ID_MC13892 && pdata->wdi_reboot) { + /* allows to reboot on wdi event */ + ret = mc13xxx_reg_read(mc13xxx, MC13892_POWERCTL2, &val); + if (!ret) { + val |= MC13892_POWERCTL2_WDIRESET; + mc13xxx_reg_write(mc13xxx, MC13892_POWERCTL2, val); + } + } + /* mask all irqs */ ret = mc13xxx_reg_write(mc13xxx, MC13XXX_IRQMASK0, 0x00ffffff); if (ret) diff --git a/include/linux/mfd/mc13xxx.h b/include/linux/mfd/mc13xxx.h index c064bea..1be4286 100644 --- a/include/linux/mfd/mc13xxx.h +++ b/include/linux/mfd/mc13xxx.h @@ -148,6 +148,8 @@ struct mc13xxx_platform_data { struct mc13xxx_regulator_platform_data regulators; struct mc13xxx_leds_platform_data *leds; + + unsigned int wdi_reboot; }; #endif /* ifndef __LINUX_MFD_MC13XXX_H */ -- 1.7.4.4 From d37bc6d58d732457244e5c545be41eca53d4cb44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:15:08 +0200 Subject: [PATCH 02/28] wdt efika mx/sb: enable watchdog Register watchdog for efika mx and sb systems. v2: - reorder Kconfig entry - set wdi_reboot in mc13xxx platform data Signed-off-by: Arnaud Patard --- arch/arm/mach-mx5/Kconfig | 1 + arch/arm/mach-mx5/mx51_efika.c | 2 ++ 2 files changed, 3 insertions(+), 0 deletions(-) diff --git a/arch/arm/mach-mx5/Kconfig b/arch/arm/mach-mx5/Kconfig index b4e7c58..07fee42 100644 --- a/arch/arm/mach-mx5/Kconfig +++ b/arch/arm/mach-mx5/Kconfig @@ -146,6 +146,7 @@ endchoice config MX51_EFIKA_COMMON bool select SOC_IMX51 + select IMX_HAVE_PLATFORM_IMX2_WDT select IMX_HAVE_PLATFORM_IMX_UART select IMX_HAVE_PLATFORM_SDHCI_ESDHC_IMX select IMX_HAVE_PLATFORM_SPI_IMX diff --git a/arch/arm/mach-mx5/mx51_efika.c b/arch/arm/mach-mx5/mx51_efika.c index c920945..58eceb9 100644 --- a/arch/arm/mach-mx5/mx51_efika.c +++ b/arch/arm/mach-mx5/mx51_efika.c @@ -572,6 +572,7 @@ static struct mc13xxx_platform_data mx51_efika_mc13892_data = { .num_regulators = ARRAY_SIZE(mx51_efika_regulators), .regulators = mx51_efika_regulators, }, + .wdi_reboot = 1, }; static struct spi_board_info mx51_efika_spi_board_info[] __initdata = { @@ -609,6 +610,7 @@ void __init efika_board_common_init(void) ARRAY_SIZE(mx51efika_pads)); imx51_add_imx_uart(0, &uart_pdata); mx51_efika_usb(); + imx51_add_imx2_wdt(0, NULL); imx51_add_sdhci_esdhc_imx(0, NULL); /* FIXME: comes from original code. check this. */ -- 1.7.4.4 From be8c29d68df3b48af79309a3dd3489cc0e83e728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:16:22 +0200 Subject: [PATCH 03/28] imx-sgtl5000 ASoC: imx: add support for imx board with sgtl5000 codec This driver is adding support for sgtl5000 codec on imx boards. It has been tested on efika imx5 platforms. This version is a modified version of Sascha Hauers imx-sgtl5000 driver. The difference is in hp jack support. TODO: test on my loco board --- include/sound/sgtl5000.h | 13 ++ sound/soc/imx/Kconfig | 9 + sound/soc/imx/Makefile | 2 + sound/soc/imx/imx-sgtl5000.c | 341 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 365 insertions(+), 0 deletions(-) create mode 100644 include/sound/sgtl5000.h create mode 100644 sound/soc/imx/imx-sgtl5000.c diff --git a/include/sound/sgtl5000.h b/include/sound/sgtl5000.h new file mode 100644 index 0000000..14d3cdd --- /dev/null +++ b/include/sound/sgtl5000.h @@ -0,0 +1,13 @@ +#ifndef _SOUND_SGTL5000_H +#define _SOUND_SGTL5000_H + +struct sgtl5000_platdata { + char *name; + int main_amp; + int spk_amp; + int hp_det; + int hp_det_inv; + unsigned int freq; +}; + +#endif diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig index bb699bb..6e72c8e 100644 --- a/sound/soc/imx/Kconfig +++ b/sound/soc/imx/Kconfig @@ -56,4 +56,13 @@ config SND_SOC_EUKREA_TLV320 Enable I2S based access to the TLV320AIC23B codec attached to the SSI interface +config SND_SOC_IMX_SGTL5000 + tristate "SoC Audio support for i.MX boards with sgtl5000" + depends on MACH_MX35_3DS || MACH_MX51_BABBAGE || MX51_EFIKA_COMMON + select SND_SOC_SGTL5000 + select SND_MXC_SOC_MX2 + help + Say Y if you want to add support for SoC audio on an i.MX board with + a sgtl5000 codec. + endif # SND_IMX_SOC diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile index d6d609b..38693ed 100644 --- a/sound/soc/imx/Makefile +++ b/sound/soc/imx/Makefile @@ -12,8 +12,10 @@ snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o snd-soc-phycore-ac97-objs := phycore-ac97.o snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o snd-soc-wm1133-ev1-objs := wm1133-ev1.o +snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o +obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o diff --git a/sound/soc/imx/imx-sgtl5000.c b/sound/soc/imx/imx-sgtl5000.c new file mode 100644 index 0000000..c95fd5f --- /dev/null +++ b/sound/soc/imx/imx-sgtl5000.c @@ -0,0 +1,341 @@ +/* + * sound/soc/imx/3ds-sgtl5000.c -- SoC audio for i.MX 3ds boards with + * sgtl5000 codec + * + * Copyright 2009 Sascha Hauer, Pengutronix + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../codecs/sgtl5000.h" +#include "imx-ssi.h" + +static struct snd_soc_card imx_sgtl5000; +static struct snd_soc_jack hp_jack; +static struct sgtl5000_platdata sgtl5000_infos; + +static struct snd_soc_jack_pin hp_jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, { + .pin = "Ext Spk", + .mask = SND_JACK_HEADPHONE, + .invert = 1, + }, +}; + +static struct snd_soc_jack_gpio hp_jack_gpios[] = { + [0] = { + .name = "hp-gpio", + .report = SND_JACK_HEADPHONE, + .debounce_time = 200, + }, +}; + +static int sgtl5000_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + u32 dai_format; + int ret; + unsigned int channels = params_channels(params); + + snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, + sgtl5000_infos.freq, 0); + snd_soc_dai_set_sysclk(codec_dai, SGTL5000_LRCLK, + params_rate(params), 0); + + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, dai_format); + if (ret < 0) + return ret; + + /* TODO: The SSI driver should figure this out for us */ + switch (channels) { + case 2: + snd_soc_dai_set_tdm_slot(cpu_dai, 0xfffffffc, 0xfffffffc, 2, 0); + break; + case 1: + snd_soc_dai_set_tdm_slot(cpu_dai, 0xfffffffe, 0xfffffffe, 1, 0); + break; + default: + return -EINVAL; + } + + /* set cpu DAI configuration */ + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_CBM_CFM; + ret = snd_soc_dai_set_fmt(cpu_dai, dai_format); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops imx_sgtl5000_hifi_ops = { + .hw_params = sgtl5000_params, +}; + +static int sgtl5000_line_in_func; + +static const char *line_in_function[] = { "off", "on" }; + +static const struct soc_enum sgtl5000_enum[] = { + SOC_ENUM_SINGLE_EXT(2, line_in_function), +}; + +static int sgtl5000_get_line_in(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = sgtl5000_line_in_func; + return 0; +} + +static int sgtl5000_set_line_in(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_context *dapm = &codec->dapm; + + if (sgtl5000_line_in_func == ucontrol->value.enumerated.item[0]) + return 0; + + sgtl5000_line_in_func = ucontrol->value.enumerated.item[0]; + if (sgtl5000_line_in_func) + snd_soc_dapm_enable_pin(dapm, "Line In Jack"); + else + snd_soc_dapm_disable_pin(dapm, "Line In Jack"); + + snd_soc_dapm_sync(dapm); + return 1; +} + +static int sgtl5000_spk_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + if (sgtl5000_infos.main_amp) { + if (SND_SOC_DAPM_EVENT_ON(event)) + gpio_set_value(sgtl5000_infos.main_amp, 1); + else + gpio_set_value(sgtl5000_infos.main_amp, 0); + } + + if (sgtl5000_infos.spk_amp) { + if (SND_SOC_DAPM_EVENT_ON(event)) + gpio_set_value(sgtl5000_infos.spk_amp, 1); + else + gpio_set_value(sgtl5000_infos.spk_amp, 0); + } + + return 0; +} + +static int sgtl5000_amp_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + if (sgtl5000_infos.main_amp) { + if (SND_SOC_DAPM_EVENT_ON(event)) + gpio_set_value(sgtl5000_infos.main_amp, 1); + else + gpio_set_value(sgtl5000_infos.main_amp, 0); + } + + return 0; +} + +/* imx_3stack card dapm widgets */ +static const struct snd_soc_dapm_widget imx_3stack_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", sgtl5000_spk_power), + SND_SOC_DAPM_HP("Headphone Jack", sgtl5000_amp_power), +}; + +static const struct snd_kcontrol_new sgtl5000_machine_controls[] = { + SOC_ENUM_EXT("Line In Function", sgtl5000_enum[0], sgtl5000_get_line_in, + sgtl5000_set_line_in), +}; + +/* imx_3stack machine connections to the codec pins */ +static const struct snd_soc_dapm_route audio_map[] = { + + /* Mic Jack --> MIC_IN (with automatic bias) */ + {"MIC_IN", NULL, "Mic Jack"}, + + /* Line in Jack --> LINE_IN */ + {"LINE_IN", NULL, "Line In Jack"}, + + /* HP_OUT --> Headphone Jack */ + {"Headphone Jack", NULL, "HP_OUT"}, + + /* LINE_OUT --> Ext Speaker */ + {"Ext Spk", NULL, "LINE_OUT"}, +}; + +static int imx_3stack_sgtl5000_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + int ret; + + ret = snd_soc_add_controls(codec, sgtl5000_machine_controls, + ARRAY_SIZE(sgtl5000_machine_controls)); + if (ret) + return ret; + + /* Add imx_3stack specific widgets */ + snd_soc_dapm_new_controls(dapm, imx_3stack_dapm_widgets, + ARRAY_SIZE(imx_3stack_dapm_widgets)); + + /* Set up imx_3stack specific audio path audio_map */ + snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_disable_pin(dapm, "Line In Jack"); + snd_soc_dapm_enable_pin(dapm, "Headphone Jack"); + snd_soc_dapm_sync(dapm); + + snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE, + &hp_jack); + + snd_soc_jack_add_pins(&hp_jack, ARRAY_SIZE(hp_jack_pins), + hp_jack_pins); + + hp_jack_gpios[0].gpio = sgtl5000_infos.hp_det; + hp_jack_gpios[0].invert = sgtl5000_infos.hp_det_inv ? 1 : 0; + snd_soc_jack_add_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios), + hp_jack_gpios); + + return 0; +} + +static struct snd_soc_dai_link imx_sgtl5000_dai[] = { + { + .name = "HiFi", + .stream_name = "HiFi", + .codec_dai_name = "sgtl5000", + .codec_name = "sgtl5000.1-000a", + .cpu_dai_name = "imx-ssi.0", + .platform_name = "imx-pcm-audio.0", + .init = imx_3stack_sgtl5000_init, + .ops = &imx_sgtl5000_hifi_ops, + }, +}; + +static struct snd_soc_card imx_sgtl5000 = { + .name = "sgtl5000-audio", + .dai_link = imx_sgtl5000_dai, + .num_links = ARRAY_SIZE(imx_sgtl5000_dai), +}; + +static struct platform_device *imx_sgtl5000_snd_device; + +static int __devinit imx_sgtl5000_probe(struct platform_device *pdev) +{ + int ret; + struct sgtl5000_platdata *infos = pdev->dev.platform_data; + + if (!infos || !infos->hp_det) { + printk(KERN_ERR "%s: no platform data provided\n", __func__); + return -EINVAL; + } + + memcpy(&sgtl5000_infos, infos, sizeof(struct sgtl5000_platdata)); + sgtl5000_infos.name = kstrdup(infos->name, GFP_KERNEL); + + imx_sgtl5000_dai[0].codec_name = sgtl5000_infos.name; + + if (sgtl5000_infos.main_amp) { + gpio_request(sgtl5000_infos.main_amp, "main_amp"); + gpio_direction_output(sgtl5000_infos.main_amp, 0); + } + + if (sgtl5000_infos.spk_amp) { + gpio_request(sgtl5000_infos.spk_amp, "spk_amp"); + gpio_direction_output(sgtl5000_infos.spk_amp, 0); + } + + + imx_sgtl5000_snd_device = platform_device_alloc("soc-audio", -1); + if (!imx_sgtl5000_snd_device) { + kfree(sgtl5000_infos.name); + return -ENOMEM; + } + + platform_set_drvdata(imx_sgtl5000_snd_device, &imx_sgtl5000); + + ret = platform_device_add(imx_sgtl5000_snd_device); + if (ret) { + printk(KERN_ERR "ASoC: Platform device allocation failed\n"); + platform_device_put(imx_sgtl5000_snd_device); + } + + return ret; +} + +static int imx_sgtl5000_remove(struct platform_device *pdev) +{ + if (sgtl5000_infos.main_amp) { + gpio_set_value(sgtl5000_infos.main_amp, 0); + gpio_free(sgtl5000_infos.main_amp); + } + + if (sgtl5000_infos.spk_amp) { + gpio_set_value(sgtl5000_infos.spk_amp, 0); + gpio_free(sgtl5000_infos.spk_amp); + } + + snd_soc_jack_free_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios), + hp_jack_gpios); + + platform_device_unregister(imx_sgtl5000_snd_device); + kfree(sgtl5000_infos.name); + + return 0; +} + +static struct platform_driver imx_sgtl5000_driver = { + .probe = imx_sgtl5000_probe, + .remove = imx_sgtl5000_remove, + .driver = { + .name = "imx-sgtl5000", + }, +}; + +static int __init imx_sgtl5000_init(void) +{ + return platform_driver_register(&imx_sgtl5000_driver); +} + +static void __exit imx_sgtl5000_exit(void) +{ + platform_driver_unregister(&imx_sgtl5000_driver); +} +module_init(imx_sgtl5000_init); +module_exit(imx_sgtl5000_exit); +MODULE_ALIAS("platform:imx-sgtl5000"); + +MODULE_AUTHOR("Sascha Hauer "); +MODULE_DESCRIPTION("imx-sgtl5000 ALSA SoC driver"); +MODULE_LICENSE("GPL"); -- 1.7.4.4 From 5c58267c9e17b474c65b8c6ae6869b5886090121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:20:01 +0200 Subject: [PATCH 04/28] imx-sdma-sound-mx353ds-mx51_babbage --- arch/arm/plat-mxc/ssi-fiq.S | 10 ++++------ sound/soc/imx/imx-pcm-fiq.c | 2 +- sound/soc/imx/imx-ssi.c | 6 ++++++ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/arch/arm/plat-mxc/ssi-fiq.S b/arch/arm/plat-mxc/ssi-fiq.S index 8397a2d..3652155 100644 --- a/arch/arm/plat-mxc/ssi-fiq.S +++ b/arch/arm/plat-mxc/ssi-fiq.S @@ -50,9 +50,8 @@ imx_ssi_fiq_start: tst r13, #SSI_SISR_TFE0 beq 1f - mov r10, #0x10000 - sub r10, #1 - and r10, r10, r8 /* r10: current buffer offset */ + lsl r10, r8, #16 + lsr r10, r10, #16 /* r10: current buffer offset */ add r11, r11, r10 @@ -88,9 +87,8 @@ imx_ssi_fiq_start: ldr r11, imx_ssi_fiq_rx_buffer - mov r10, #0x10000 - sub r10, #1 - and r10, r10, r9 /* r10: current buffer offset */ + lsl r10, r9, #16 + lsr r10, r10, #16 /* r10: current buffer offset */ add r11, r11, r10 diff --git a/sound/soc/imx/imx-pcm-fiq.c b/sound/soc/imx/imx-pcm-fiq.c index 309c59e..8b7c7cc 100644 --- a/sound/soc/imx/imx-pcm-fiq.c +++ b/sound/soc/imx/imx-pcm-fiq.c @@ -175,7 +175,7 @@ static struct snd_pcm_hardware snd_imx_hardware = { SNDRV_PCM_INFO_RESUME, .formats = SNDRV_PCM_FMTBIT_S16_LE, .rate_min = 8000, - .channels_min = 2, + .channels_min = 1, .channels_max = 2, .buffer_bytes_max = IMX_SSI_DMABUF_SIZE, .period_bytes_min = 128, diff --git a/sound/soc/imx/imx-ssi.c b/sound/soc/imx/imx-ssi.c index 10a8e27..f5d5cb6 100644 --- a/sound/soc/imx/imx-ssi.c +++ b/sound/soc/imx/imx-ssi.c @@ -574,6 +574,9 @@ static void imx_ssi_ac97_reset(struct snd_ac97 *ac97) if (imx_ssi->ac97_reset) imx_ssi->ac97_reset(ac97); + + /* First read sometimes fails, do a dummy read */ + imx_ssi_ac97_read(ac97, 0); } static void imx_ssi_ac97_warm_reset(struct snd_ac97 *ac97) @@ -582,6 +585,9 @@ static void imx_ssi_ac97_warm_reset(struct snd_ac97 *ac97) if (imx_ssi->ac97_warm_reset) imx_ssi->ac97_warm_reset(ac97); + + /* First read sometimes fails, do a dummy read */ + imx_ssi_ac97_read(ac97, 0); } struct snd_ac97_bus_ops soc_ac97_ops = { -- 1.7.4.4 From 31b4d0de5924fabfb781eca9c916ba23d00ac1b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:20:50 +0200 Subject: [PATCH 05/28] efika_pata_add_dev imx51: add pata device Declare the pata device found on mx51 --- arch/arm/mach-mx5/devices-imx51.h | 4 ++ arch/arm/plat-mxc/devices/Kconfig | 3 ++ arch/arm/plat-mxc/devices/Makefile | 1 + arch/arm/plat-mxc/devices/platform-pata_imx.c | 38 +++++++++++++++++++++++ arch/arm/plat-mxc/include/mach/devices-common.h | 7 ++++ 5 files changed, 53 insertions(+), 0 deletions(-) create mode 100644 arch/arm/plat-mxc/devices/platform-pata_imx.c diff --git a/arch/arm/mach-mx5/devices-imx51.h b/arch/arm/mach-mx5/devices-imx51.h index e11bc0e..89fe77f 100644 --- a/arch/arm/mach-mx5/devices-imx51.h +++ b/arch/arm/mach-mx5/devices-imx51.h @@ -52,3 +52,7 @@ extern const struct imx_mxc_pwm_data imx51_mxc_pwm_data[]; extern const struct imx_imx_keypad_data imx51_imx_keypad_data; #define imx51_add_imx_keypad(pdata) \ imx_add_imx_keypad(&imx51_imx_keypad_data, pdata) + +extern const struct imx_pata_imx_data imx51_pata_imx_data; +#define imx51_add_pata_imx() \ + imx_add_pata_imx(&imx51_pata_imx_data) diff --git a/arch/arm/plat-mxc/devices/Kconfig b/arch/arm/plat-mxc/devices/Kconfig index bd294ad..c55916c 100644 --- a/arch/arm/plat-mxc/devices/Kconfig +++ b/arch/arm/plat-mxc/devices/Kconfig @@ -31,6 +31,9 @@ config IMX_HAVE_PLATFORM_IMX_I2C config IMX_HAVE_PLATFORM_IMX_KEYPAD bool +config IMX_HAVE_PLATFORM_PATA_IMX + bool + config IMX_HAVE_PLATFORM_IMX_SSI bool diff --git a/arch/arm/plat-mxc/devices/Makefile b/arch/arm/plat-mxc/devices/Makefile index b41bf97..a093b45 100644 --- a/arch/arm/plat-mxc/devices/Makefile +++ b/arch/arm/plat-mxc/devices/Makefile @@ -10,6 +10,7 @@ obj-y += platform-imx-dma.o obj-$(CONFIG_IMX_HAVE_PLATFORM_IMX_FB) += platform-imx-fb.o obj-$(CONFIG_IMX_HAVE_PLATFORM_IMX_I2C) += platform-imx-i2c.o obj-$(CONFIG_IMX_HAVE_PLATFORM_IMX_KEYPAD) += platform-imx-keypad.o +obj-$(CONFIG_IMX_HAVE_PLATFORM_PATA_IMX) += platform-pata_imx.o obj-$(CONFIG_IMX_HAVE_PLATFORM_IMX_SSI) += platform-imx-ssi.o obj-$(CONFIG_IMX_HAVE_PLATFORM_IMX_UART) += platform-imx-uart.o obj-$(CONFIG_IMX_HAVE_PLATFORM_IMX_UDC) += platform-imx_udc.o diff --git a/arch/arm/plat-mxc/devices/platform-pata_imx.c b/arch/arm/plat-mxc/devices/platform-pata_imx.c new file mode 100644 index 0000000..c1cbaf9 --- /dev/null +++ b/arch/arm/plat-mxc/devices/platform-pata_imx.c @@ -0,0 +1,38 @@ +/* + * 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 + +#define imx_pata_imx_data_entry_single(soc) \ + { \ + .iobase = soc ## _ATA_BASE_ADDR, \ + .irq = soc ## _MXC_INT_ATA, \ + } + +#ifdef CONFIG_SOC_IMX51 +const struct imx_pata_imx_data imx51_pata_imx_data __initconst = + imx_pata_imx_data_entry_single(MX51); +#endif /* ifdef CONFIG_SOC_IMX51 */ + +struct platform_device *__init imx_add_pata_imx( + const struct imx_pata_imx_data *data) +{ + struct resource res[] = { + { + .start = data->iobase, + .end = data->iobase + SZ_16K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = data->irq, + .end = data->irq, + .flags = IORESOURCE_IRQ, + }, + }; + return imx_add_platform_device("pata_imx", -1, + res, ARRAY_SIZE(res), NULL, 0); +} + diff --git a/arch/arm/plat-mxc/include/mach/devices-common.h b/arch/arm/plat-mxc/include/mach/devices-common.h index 524538a..1b76c67 100644 --- a/arch/arm/plat-mxc/include/mach/devices-common.h +++ b/arch/arm/plat-mxc/include/mach/devices-common.h @@ -251,6 +251,13 @@ struct platform_device *__init imx_add_mxc_nand( const struct imx_mxc_nand_data *data, const struct mxc_nand_platform_data *pdata); +struct imx_pata_imx_data { + resource_size_t iobase; + resource_size_t irq; +}; +struct platform_device *__init imx_add_pata_imx( + const struct imx_pata_imx_data *data); + struct imx_mxc_pwm_data { int id; resource_size_t iobase; -- 1.7.4.4 From d3696cfe27957623aad3eb4c6fd493429cbe5cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:22:01 +0200 Subject: [PATCH 06/28] efika_pata_add_clock imx51: add pata clock Define the clock used by the imx51 pata device. Signed-off-by: Arnaud Patard --- arch/arm/mach-mx5/clock-mx51-mx53.c | 5 +++++ 1 files changed, 5 insertions(+), 0 deletions(-) diff --git a/arch/arm/mach-mx5/clock-mx51-mx53.c b/arch/arm/mach-mx5/clock-mx51-mx53.c index f7bf996..e4251a8 100644 --- a/arch/arm/mach-mx5/clock-mx51-mx53.c +++ b/arch/arm/mach-mx5/clock-mx51-mx53.c @@ -1418,6 +1418,10 @@ DEFINE_CLOCK(ipu_di0_clk, 0, MXC_CCM_CCGR6, MXC_CCM_CCGRx_CG5_OFFSET, DEFINE_CLOCK(ipu_di1_clk, 0, MXC_CCM_CCGR6, MXC_CCM_CCGRx_CG6_OFFSET, NULL, NULL, &pll3_sw_clk, NULL); +/* PATA */ +DEFINE_CLOCK(pata_clk, 0, MXC_CCM_CCGR4, MXC_CCM_CCGRx_CG0_OFFSET, + NULL, NULL, &ipg_clk, &spba_clk); + #define _REGISTER_CLOCK(d, n, c) \ { \ .dev_id = d, \ @@ -1474,6 +1478,7 @@ static struct clk_lookup mx51_lookups[] = { _REGISTER_CLOCK("imx-ipuv3", "di0", ipu_di0_clk) _REGISTER_CLOCK("imx-ipuv3", "di1", ipu_di1_clk) _REGISTER_CLOCK(NULL, "gpc_dvfs", gpc_dvfs_clk) + _REGISTER_CLOCK("pata_imx", NULL, pata_clk) }; static struct clk_lookup mx53_lookups[] = { -- 1.7.4.4 From 09c88ccf9967b6473d66feb1df7628f407454889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:22:42 +0200 Subject: [PATCH 07/28] efika_pata_enable_pata imx: efika: Enable pata. Enable pata support on efika mx/sb platforms. --- arch/arm/mach-mx5/Kconfig | 1 + arch/arm/mach-mx5/mx51_efika.c | 2 ++ 2 files changed, 3 insertions(+), 0 deletions(-) diff --git a/arch/arm/mach-mx5/Kconfig b/arch/arm/mach-mx5/Kconfig index 07fee42..151d254 100644 --- a/arch/arm/mach-mx5/Kconfig +++ b/arch/arm/mach-mx5/Kconfig @@ -148,6 +148,7 @@ config MX51_EFIKA_COMMON select SOC_IMX51 select IMX_HAVE_PLATFORM_IMX2_WDT select IMX_HAVE_PLATFORM_IMX_UART + select IMX_HAVE_PLATFORM_PATA_IMX select IMX_HAVE_PLATFORM_SDHCI_ESDHC_IMX select IMX_HAVE_PLATFORM_SPI_IMX select MXC_ULPI if USB_ULPI diff --git a/arch/arm/mach-mx5/mx51_efika.c b/arch/arm/mach-mx5/mx51_efika.c index 58eceb9..2459543 100644 --- a/arch/arm/mach-mx5/mx51_efika.c +++ b/arch/arm/mach-mx5/mx51_efika.c @@ -629,6 +629,8 @@ void __init efika_board_common_init(void) ARRAY_SIZE(mx51_efika_spi_board_info)); imx51_add_ecspi(0, &mx51_efika_spi_pdata); + imx51_add_pata_imx(); + #if defined(CONFIG_CPU_FREQ_IMX) get_cpu_op = mx51_get_cpu_op; #endif -- 1.7.4.4 From 75e86f1e769b93401c2f3d1a0b4ad4484f5105e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:24:13 +0200 Subject: [PATCH 08/28] efikamx_audio efikamx: add audio support Signed-off-by: Arnaud Patard --- arch/arm/mach-mx5/Kconfig | 2 + arch/arm/mach-mx5/mx51_efika.c | 91 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 1 deletions(-) diff --git a/arch/arm/mach-mx5/Kconfig b/arch/arm/mach-mx5/Kconfig index 151d254..56477fb 100644 --- a/arch/arm/mach-mx5/Kconfig +++ b/arch/arm/mach-mx5/Kconfig @@ -147,6 +147,8 @@ config MX51_EFIKA_COMMON bool select SOC_IMX51 select IMX_HAVE_PLATFORM_IMX2_WDT + select IMX_HAVE_PLATFORM_IMX_I2C + select IMX_HAVE_PLATFORM_IMX_SSI select IMX_HAVE_PLATFORM_IMX_UART select IMX_HAVE_PLATFORM_PATA_IMX select IMX_HAVE_PLATFORM_SDHCI_ESDHC_IMX diff --git a/arch/arm/mach-mx5/mx51_efika.c b/arch/arm/mach-mx5/mx51_efika.c index 2459543..3e0c180 100644 --- a/arch/arm/mach-mx5/mx51_efika.c +++ b/arch/arm/mach-mx5/mx51_efika.c @@ -25,10 +25,14 @@ #include #include #include +#include +#include #include #include #include +#include +#include #include #include @@ -57,6 +61,12 @@ #define EFIKAMX_PMIC IMX_GPIO_NR(1, 6) +#define EFIKAMX_AMP IMX_GPIO_NR(2, 17) +#define EFIKAMX_HP_DET IMX_GPIO_NR(3, 8) +#define EFIKAMX_AUDIO_CLK_B IMX_GPIO_NR(4, 26) + +#define MX51_PAD_HP_DET (_MX51_PAD_DISPB2_SER_RS__GPIO3_8 | MUX_PAD_CTRL(PAD_CTL_PUS_100K_UP)) + static iomux_v3_cfg_t mx51efika_pads[] = { /* UART1 */ MX51_PAD_UART1_RXD__UART1_RXD, @@ -91,7 +101,6 @@ static iomux_v3_cfg_t mx51efika_pads[] = { MX51_PAD_CSPI1_MISO__ECSPI1_MISO, MX51_PAD_CSPI1_SS0__GPIO4_24, MX51_PAD_CSPI1_SS1__GPIO4_25, - MX51_PAD_CSPI1_RDY__ECSPI1_RDY, MX51_PAD_CSPI1_SCLK__ECSPI1_SCLK, MX51_PAD_GPIO1_6__GPIO1_6, @@ -117,6 +126,23 @@ static iomux_v3_cfg_t mx51efika_pads[] = { /* USB PHY RESET */ MX51_PAD_EIM_D27__GPIO2_9, + + /* I2C */ + MX51_PAD_GPIO1_2__GPIO1_2, + MX51_PAD_GPIO1_3__GPIO1_3, + MX51_PAD_KEY_COL4__I2C2_SCL, + MX51_PAD_KEY_COL5__I2C2_SDA, + + /* SSI */ + MX51_PAD_AUD3_BB_TXD__AUD3_TXD, + MX51_PAD_AUD3_BB_RXD__AUD3_RXD, + MX51_PAD_AUD3_BB_CK__AUD3_TXC, + MX51_PAD_AUD3_BB_FS__AUD3_TXFS, + + /* audio */ + MX51_PAD_HP_DET, + MX51_PAD_CSPI1_RDY__GPIO4_26, + MX51_PAD_EIM_A23__GPIO2_17, }; /* Serial ports */ @@ -604,13 +630,75 @@ static const struct spi_imx_master mx51_efika_spi_pdata __initconst = { .num_chipselect = ARRAY_SIZE(mx51_efika_spi_cs), }; +static struct i2c_board_info mx51_efika_i2c2_board_info[] __initdata = { + { + I2C_BOARD_INFO("sgtl5000", 0x0a), + }, +}; + +static const struct imxi2c_platform_data mx51_efika_imxi2c2_data __initconst = { + .bitrate = 100000, +}; + +static void __init mx51_efika_i2c(void) +{ + i2c_register_board_info(1, mx51_efika_i2c2_board_info, + ARRAY_SIZE(mx51_efika_i2c2_board_info)); + imx51_add_imx_i2c(1, &mx51_efika_imxi2c2_data); +} + +static struct imx_ssi_platform_data mx51_efika_ssi_pdata = { + .flags = IMX_SSI_DMA, +}; + +static struct sgtl5000_platdata sgtl5000_infos = { + .name = "sgtl5000.1-000a", + .main_amp = EFIKAMX_AMP, + .hp_det = EFIKAMX_HP_DET, + .freq = 12288000, +}; + +static struct platform_device mx51_efika_sgtl5000 = { + .name = "imx-sgtl5000", + .id = -1, + .dev = { + .platform_data = &sgtl5000_infos, + }, +}; + +static void __init mx51_efika_audio(void) +{ + gpio_request(EFIKAMX_AUDIO_CLK_B, "audio_clk_en_b"); + gpio_direction_output(EFIKAMX_AUDIO_CLK_B, 0); + + imx51_add_imx_ssi(0, &mx51_efika_ssi_pdata); + + if (machine_is_mx51_efikasb()) + sgtl5000_infos.hp_det_inv = 1; + platform_device_register(&mx51_efika_sgtl5000); +} + void __init efika_board_common_init(void) { + mxc_audmux_v2_configure_port(0, + MXC_AUDMUX_V2_PTCR_SYN | /* 4wire mode */ + MXC_AUDMUX_V2_PTCR_TFSEL(2) | + MXC_AUDMUX_V2_PTCR_TCSEL(2) | + MXC_AUDMUX_V2_PTCR_TFSDIR | + MXC_AUDMUX_V2_PTCR_TCLKDIR, + MXC_AUDMUX_V2_PDCR_RXDSEL(2)); + + mxc_audmux_v2_configure_port(2, + MXC_AUDMUX_V2_PTCR_SYN | /* 4wire mode */ + MXC_AUDMUX_V2_PTCR_TCSEL(0), + MXC_AUDMUX_V2_PDCR_RXDSEL(0)); + mxc_iomux_v3_setup_multiple_pads(mx51efika_pads, ARRAY_SIZE(mx51efika_pads)); imx51_add_imx_uart(0, &uart_pdata); mx51_efika_usb(); imx51_add_imx2_wdt(0, NULL); + mx51_efika_i2c(); imx51_add_sdhci_esdhc_imx(0, NULL); /* FIXME: comes from original code. check this. */ @@ -634,5 +722,6 @@ void __init efika_board_common_init(void) #if defined(CONFIG_CPU_FREQ_IMX) get_cpu_op = mx51_get_cpu_op; #endif + mx51_efika_audio(); } -- 1.7.4.4 From c482e854b72c780cecf8c4af47b6343193edab50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:25:31 +0200 Subject: [PATCH 09/28] ipuv3_fb1 Author: Sascha Hauer Email: s.hauer@pengutronix.de Subject: Add a mfd IPUv3 driver Date: Wed, 16 Feb 2011 15:10:46 +0100 The IPU is the Image Processing Unit found on i.MX51/53 SoCs. It features several units for image processing, this patch adds support for the units needed for Framebuffer support, namely: - Display Controller (dc) - Display Interface (di) - Display Multi Fifo Controller (dmfc) - Display Processor (dp) - Image DMA Controller (idmac) This patch is based on the Freescale driver, but follows a different approach. The Freescale code implements logical idmac channels and the handling of the subunits is hidden in common idmac code pathes in big switch/case statements. This patch instead just provides code and resource management for the different subunits. The user, in this case the framebuffer driver, decides how the different units play together. The IPU has other units missing in this patch: - CMOS Sensor Interface (csi) - Video Deinterlacer (vdi) - Sensor Multi FIFO Controler (smfc) - Image Converter (ic) - Image Rotator (irt) So expect more files to come in this directory. Signed-off-by: Sascha Hauer Cc: linux-kernel@vger.kernel.org Cc: linux-fbdev@vger.kernel.org Cc: Paul Mundt Cc: Samuel Ortiz --- arch/arm/plat-mxc/include/mach/ipu-v3.h | 49 +++ drivers/video/Kconfig | 2 + drivers/video/Makefile | 1 + drivers/video/imx-ipu-v3/Makefile | 3 + drivers/video/imx-ipu-v3/ipu-common.c | 707 +++++++++++++++++++++++++++++++ drivers/video/imx-ipu-v3/ipu-cpmem.c | 612 ++++++++++++++++++++++++++ drivers/video/imx-ipu-v3/ipu-dc.c | 364 ++++++++++++++++ drivers/video/imx-ipu-v3/ipu-di.c | 550 ++++++++++++++++++++++++ drivers/video/imx-ipu-v3/ipu-dmfc.c | 355 ++++++++++++++++ drivers/video/imx-ipu-v3/ipu-dp.c | 476 +++++++++++++++++++++ drivers/video/imx-ipu-v3/ipu-prv.h | 216 ++++++++++ include/linux/mfd/imx-ipu-v3.h | 219 ++++++++++ 12 files changed, 3554 insertions(+), 0 deletions(-) create mode 100644 arch/arm/plat-mxc/include/mach/ipu-v3.h create mode 100644 drivers/video/imx-ipu-v3/Makefile create mode 100644 drivers/video/imx-ipu-v3/ipu-common.c create mode 100644 drivers/video/imx-ipu-v3/ipu-cpmem.c create mode 100644 drivers/video/imx-ipu-v3/ipu-dc.c create mode 100644 drivers/video/imx-ipu-v3/ipu-di.c create mode 100644 drivers/video/imx-ipu-v3/ipu-dmfc.c create mode 100644 drivers/video/imx-ipu-v3/ipu-dp.c create mode 100644 drivers/video/imx-ipu-v3/ipu-prv.h create mode 100644 include/linux/mfd/imx-ipu-v3.h diff --git a/arch/arm/plat-mxc/include/mach/ipu-v3.h b/arch/arm/plat-mxc/include/mach/ipu-v3.h new file mode 100644 index 0000000..f8900b9 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/ipu-v3.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2010 Sascha Hauer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#ifndef __MACH_IPU_V3_H_ +#define __MACH_IPU_V3_H_ + +/* IPU specific extensions to struct fb_videomode flag field */ +#define FB_SYNC_OE_LOW_ACT (1 << 8) +#define FB_SYNC_CLK_LAT_FALL (1 << 9) +#define FB_SYNC_DATA_INVERT (1 << 10) +#define FB_SYNC_CLK_IDLE_EN (1 << 11) +#define FB_SYNC_SHARP_MODE (1 << 12) +#define FB_SYNC_SWAP_RGB (1 << 13) + +struct ipuv3_fb_platform_data { + const struct fb_videomode *modes; + int num_modes; + char *mode_str; + u32 interface_pix_fmt; + +#define IMX_IPU_FB_USE_MODEDB (1 << 0) +#define IMX_IPU_FB_USE_OVERLAY (1 << 1) + unsigned long flags; + + int ipu_channel_bg; + int ipu_channel_fg; + int dc_channel; + int dp_channel; + int display; +}; + +struct imx_ipuv3_platform_data { + int rev; + struct ipuv3_fb_platform_data *fb_head0_platform_data; + struct ipuv3_fb_platform_data *fb_head1_platform_data; +}; + +#endif /* __MACH_IPU_V3_H_ */ diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 549b960..3ba3a12 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -23,6 +23,8 @@ source "drivers/gpu/drm/Kconfig" source "drivers/gpu/stub/Kconfig" +source "drivers/video/imx-ipu-v3/Kconfig" + config VGASTATE tristate default n diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 8b83129..0b9db1e 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -156,6 +156,7 @@ obj-$(CONFIG_FB_BFIN_7393) += bfin_adv7393fb.o obj-$(CONFIG_FB_MX3) += mx3fb.o obj-$(CONFIG_FB_DA8XX) += da8xx-fb.o obj-$(CONFIG_FB_MXS) += mxsfb.o +obj-$(CONFIG_MFD_IMX_IPU_V3) += imx-ipu-v3/ # the test framebuffer is last obj-$(CONFIG_FB_VIRTUAL) += vfb.o diff --git a/drivers/video/imx-ipu-v3/Makefile b/drivers/video/imx-ipu-v3/Makefile new file mode 100644 index 0000000..ff70fe8 --- /dev/null +++ b/drivers/video/imx-ipu-v3/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_MFD_IMX_IPU_V3) += imx-ipu-v3.o + +imx-ipu-v3-objs := ipu-common.o ipu-dc.o ipu-di.o ipu-dp.o ipu-dmfc.o ipu-cpmem.o diff --git a/drivers/video/imx-ipu-v3/ipu-common.c b/drivers/video/imx-ipu-v3/ipu-common.c new file mode 100644 index 0000000..19140e5 --- /dev/null +++ b/drivers/video/imx-ipu-v3/ipu-common.c @@ -0,0 +1,707 @@ +/* + * Copyright (c) 2010 Sascha Hauer + * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * 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 +#include + +#include "ipu-prv.h" + +static struct clk *ipu_clk; +static struct device *ipu_dev; + +static DEFINE_SPINLOCK(ipu_lock); +static DEFINE_MUTEX(ipu_channel_lock); +void __iomem *ipu_cm_reg; +void __iomem *ipu_idmac_reg; + +static int ipu_use_count; + +static struct ipu_channel channels[64]; + +struct ipu_channel *ipu_idmac_get(unsigned num) +{ + struct ipu_channel *channel; + + dev_dbg(ipu_dev, "%s %d\n", __func__, num); + + if (num > 63) + return ERR_PTR(-ENODEV); + + mutex_lock(&ipu_channel_lock); + + channel = &channels[num]; + + if (channel->busy) { + channel = ERR_PTR(-EBUSY); + goto out; + } + + channel->busy = 1; + channel->num = num; + +out: + mutex_unlock(&ipu_channel_lock); + + return channel; +} +EXPORT_SYMBOL(ipu_idmac_get); + +void ipu_idmac_put(struct ipu_channel *channel) +{ + dev_dbg(ipu_dev, "%s %d\n", __func__, channel->num); + + mutex_lock(&ipu_channel_lock); + + channel->busy = 0; + + mutex_unlock(&ipu_channel_lock); +} +EXPORT_SYMBOL(ipu_idmac_put); + +void ipu_idmac_set_double_buffer(struct ipu_channel *channel, bool doublebuffer) +{ + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&ipu_lock, flags); + + reg = ipu_cm_read(IPU_CHA_DB_MODE_SEL(channel->num)); + if (doublebuffer) + reg |= idma_mask(channel->num); + else + reg &= ~idma_mask(channel->num); + ipu_cm_write(reg, IPU_CHA_DB_MODE_SEL(channel->num)); + + spin_unlock_irqrestore(&ipu_lock, flags); +} +EXPORT_SYMBOL(ipu_idmac_set_double_buffer); + +int ipu_module_enable(u32 mask) +{ + unsigned long lock_flags; + u32 ipu_conf; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + ipu_conf = ipu_cm_read(IPU_CONF); + ipu_conf |= mask; + ipu_cm_write(ipu_conf, IPU_CONF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +int ipu_module_disable(u32 mask) +{ + unsigned long lock_flags; + u32 ipu_conf; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + ipu_conf = ipu_cm_read(IPU_CONF); + ipu_conf &= ~mask; + ipu_cm_write(ipu_conf, IPU_CONF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +void ipu_idmac_select_buffer(struct ipu_channel *channel, u32 buf_num) +{ + unsigned int chno = channel->num; + unsigned long flags; + + spin_lock_irqsave(&ipu_lock, flags); + + /* Mark buffer as ready. */ + if (buf_num == 0) + ipu_cm_write(idma_mask(chno), IPU_CHA_BUF0_RDY(chno)); + else + ipu_cm_write(idma_mask(chno), IPU_CHA_BUF1_RDY(chno)); + + spin_unlock_irqrestore(&ipu_lock, flags); +} +EXPORT_SYMBOL(ipu_idmac_select_buffer); + +int ipu_idmac_enable_channel(struct ipu_channel *channel) +{ + u32 val; + unsigned long flags; + + ipu_get(); + + spin_lock_irqsave(&ipu_lock, flags); + + val = ipu_idmac_read(IDMAC_CHA_EN(channel->num)); + val |= idma_mask(channel->num); + ipu_idmac_write(val, IDMAC_CHA_EN(channel->num)); + + spin_unlock_irqrestore(&ipu_lock, flags); + + return 0; +} +EXPORT_SYMBOL(ipu_idmac_enable_channel); + +int ipu_idmac_disable_channel(struct ipu_channel *channel) +{ + u32 val; + unsigned long flags; + + spin_lock_irqsave(&ipu_lock, flags); + + /* Disable DMA channel(s) */ + val = ipu_idmac_read(IDMAC_CHA_EN(channel->num)); + val &= ~idma_mask(channel->num); + ipu_idmac_write(val, IDMAC_CHA_EN(channel->num)); + + /* Set channel buffers NOT to be ready */ + ipu_cm_write(0xf0000000, IPU_GPR); /* write one to clear */ + + if (ipu_idma_is_set(IPU_CHA_BUF0_RDY, channel->num)) { + ipu_cm_write(idma_mask(channel->num), + IPU_CHA_BUF0_RDY(channel->num)); + } + if (ipu_idma_is_set(IPU_CHA_BUF1_RDY, channel->num)) { + ipu_cm_write(idma_mask(channel->num), + IPU_CHA_BUF1_RDY(channel->num)); + } + + ipu_cm_write(0x0, IPU_GPR); /* write one to set */ + + /* Reset the double buffer */ + val = ipu_cm_read(IPU_CHA_DB_MODE_SEL(channel->num)); + val &= ~idma_mask(channel->num); + ipu_cm_write(val, IPU_CHA_DB_MODE_SEL(channel->num)); + + spin_unlock_irqrestore(&ipu_lock, flags); + + ipu_put(); + + return 0; +} +EXPORT_SYMBOL(ipu_idmac_disable_channel); + +static LIST_HEAD(ipu_irq_handlers); + +static void ipu_irq_update_irq_mask(void) +{ + struct ipu_irq_handler *handler; + int i; + + DECLARE_IPU_IRQ_BITMAP(irqs); + + bitmap_zero(irqs, IPU_IRQ_COUNT); + + list_for_each_entry(handler, &ipu_irq_handlers, list) + bitmap_or(irqs, irqs, handler->ipu_irqs, IPU_IRQ_COUNT); + + for (i = 0; i < BITS_TO_LONGS(IPU_IRQ_COUNT); i++) + ipu_cm_write(irqs[i], IPU_INT_CTRL(i + 1)); +} + +int ipu_irq_add_handler(struct ipu_irq_handler *ipuirq) +{ + unsigned long flags; + + spin_lock_irqsave(&ipu_lock, flags); + + list_add_tail(&ipuirq->list, &ipu_irq_handlers); + ipu_irq_update_irq_mask(); + + spin_unlock_irqrestore(&ipu_lock, flags); + return 0; +} +EXPORT_SYMBOL(ipu_irq_add_handler); + +void ipu_irq_remove_handler(struct ipu_irq_handler *handler) +{ + unsigned long flags; + + spin_lock_irqsave(&ipu_lock, flags); + + list_del(&handler->list); + ipu_irq_update_irq_mask(); + + spin_unlock_irqrestore(&ipu_lock, flags); +} +EXPORT_SYMBOL(ipu_irq_remove_handler); + +int ipu_irq_update_handler(struct ipu_irq_handler *handler, + unsigned long *bitmap) +{ + unsigned long flags; + + spin_lock_irqsave(&ipu_lock, flags); + + bitmap_copy(handler->ipu_irqs, bitmap, IPU_IRQ_COUNT); + ipu_irq_update_irq_mask(); + + spin_unlock_irqrestore(&ipu_lock, flags); + + return 0; +} +EXPORT_SYMBOL(ipu_irq_update_handler); + +static void ipu_completion_handler(unsigned long *bitmask, void *context) +{ + struct completion *completion = context; + + complete(completion); +} + +int ipu_wait_for_interrupt(int interrupt, int timeout_ms) +{ + struct ipu_irq_handler handler; + DECLARE_COMPLETION_ONSTACK(completion); + int ret; + + bitmap_zero(handler.ipu_irqs, IPU_IRQ_COUNT); + bitmap_set(handler.ipu_irqs, interrupt, 1); + + ipu_cm_write(1 << (interrupt % 32), IPU_INT_STAT(interrupt / 32 + 1)); + + handler.handler = ipu_completion_handler; + handler.context = &completion; + ipu_irq_add_handler(&handler); + + ret = wait_for_completion_timeout(&completion, + msecs_to_jiffies(timeout_ms)); + + ipu_irq_remove_handler(&handler); + + if (ret > 0) + ret = 0; + else + ret = -ETIMEDOUT; + + return ret; +} +EXPORT_SYMBOL(ipu_wait_for_interrupt); + +static irqreturn_t ipu_irq_handler(int irq, void *desc) +{ + DECLARE_IPU_IRQ_BITMAP(status); + struct ipu_irq_handler *handler; + int i; + + for (i = 0; i < BITS_TO_LONGS(IPU_IRQ_COUNT); i++) { + status[i] = ipu_cm_read(IPU_INT_STAT(i + 1)); + ipu_cm_write(status[i], IPU_INT_STAT(i + 1)); + } + + list_for_each_entry(handler, &ipu_irq_handlers, list) { + DECLARE_IPU_IRQ_BITMAP(tmp); + if (bitmap_and(tmp, status, handler->ipu_irqs, IPU_IRQ_COUNT)) + handler->handler(tmp, handler->context); + } + + return IRQ_HANDLED; +} + +ipu_color_space_t format_to_colorspace(u32 fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_RGB666: + case IPU_PIX_FMT_RGB565: + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_BGR32: + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_RGB32: + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_ABGR32: + case IPU_PIX_FMT_LVDS666: + case IPU_PIX_FMT_LVDS888: + return RGB; + + default: + return YCbCr; + } +} + +static int ipu_reset(void) +{ + int timeout = 10000; + u32 val; + + /* hard reset the IPU */ + val = readl(MX51_IO_ADDRESS(MX51_SRC_BASE_ADDR)); + val |= 1 << 3; + writel(val, MX51_IO_ADDRESS(MX51_SRC_BASE_ADDR)); + + ipu_cm_write(0x807FFFFF, IPU_MEM_RST); + + while (ipu_cm_read(IPU_MEM_RST) & 0x80000000) { + if (!timeout--) + return -ETIME; + udelay(100); + } + + return 0; +} + +/* + * The MIPI HSC unit has been removed from the i.MX51 Reference Manual by + * the Freescale marketing division. However this did not remove the + * hardware from the chip which still needs to be configured... + */ +static int __devinit ipu_mipi_setup(void) +{ + struct clk *hsc_clk; + void __iomem *hsc_addr; + int ret = 0; + + hsc_addr = ioremap(MX51_MIPI_HSC_BASE_ADDR, PAGE_SIZE); + if (!hsc_addr) + return -ENOMEM; + + hsc_clk = clk_get_sys(NULL, "mipi_hsp"); + if (IS_ERR(hsc_clk)) { + ret = PTR_ERR(hsc_clk); + goto unmap; + } + clk_enable(hsc_clk); + + /* setup MIPI module to legacy mode */ + __raw_writel(0xF00, hsc_addr); + + /* CSI mode: reserved; DI control mode: legacy (from Freescale BSP) */ + __raw_writel(__raw_readl(hsc_addr + 0x800) | 0x30ff, + hsc_addr + 0x800); + + clk_disable(hsc_clk); + clk_put(hsc_clk); +unmap: + iounmap(hsc_addr); + + return ret; +} + +static int ipu_submodules_init(struct platform_device *pdev, + unsigned long ipu_base, struct clk *ipu_clk) +{ + char *unit; + int ret; + + ret = ipu_di_init(pdev, 0, ipu_base + IPU_DI0_REG_BASE, + IPU_CONF_DI0_EN, ipu_clk); + if (ret) { + unit = "di0"; + goto err_di_0; + } + + ret = ipu_di_init(pdev, 1, ipu_base + IPU_DI1_REG_BASE, + IPU_CONF_DI1_EN, ipu_clk); + if (ret) { + unit = "di1"; + goto err_di_1; + } + + ret = ipu_dc_init(pdev, ipu_base + IPU_DC_REG_BASE, + ipu_base + IPU_DC_TMPL_REG_BASE); + if (ret) { + unit = "dc_template"; + goto err_dc; + } + + ret = ipu_dmfc_init(pdev, ipu_base + IPU_DMFC_REG_BASE, ipu_clk); + if (ret) { + unit = "dmfc"; + goto err_dmfc; + } + + ret = ipu_dp_init(pdev, ipu_base + IPU_SRM_REG_BASE); + if (ret) { + unit = "dp"; + goto err_dp; + } + + ret = ipu_cpmem_init(pdev, ipu_base + IPU_CPMEM_REG_BASE); + if (ret) { + unit = "cpmem"; + goto err_cpmem; + } + + return 0; + +err_cpmem: + ipu_dp_exit(pdev); +err_dp: + ipu_dmfc_exit(pdev); +err_dmfc: + ipu_dc_exit(pdev); +err_dc: + ipu_di_exit(pdev, 1); +err_di_1: + ipu_di_exit(pdev, 0); +err_di_0: + dev_err(&pdev->dev, "init %s failed with %d\n", unit, ret); + return ret; +} + +void ipu_get(void) +{ + mutex_lock(&ipu_channel_lock); + + ipu_use_count++; + + if (ipu_use_count == 1) + clk_enable(ipu_clk); + + mutex_unlock(&ipu_channel_lock); +} + +void ipu_put(void) +{ + mutex_lock(&ipu_channel_lock); + + ipu_use_count--; + + if (ipu_use_count == 0) + clk_disable(ipu_clk); + + if (ipu_use_count < 0) { + dev_err(ipu_dev, "ipu use count < 0\n"); + ipu_use_count = 0; + } + + mutex_unlock(&ipu_channel_lock); +} + +static void ipu_submodules_exit(struct platform_device *pdev, + unsigned long ipu_base) +{ + ipu_cpmem_exit(pdev); + ipu_dp_exit(pdev); + ipu_dmfc_exit(pdev); + ipu_dc_exit(pdev); + ipu_di_exit(pdev, 1); + ipu_di_exit(pdev, 0); +} + +static int ipu_add_subdevice_pdata(struct platform_device *pdev, + const char *name, int id, void *pdata) +{ + struct mfd_cell cell = { + .mfd_data = pdata, + }; + + cell.name = name; + + return mfd_add_devices(&pdev->dev, id, &cell, 1, NULL, 0); +} + +static int ipu_add_client_devices(struct platform_device *pdev) +{ + struct imx_ipuv3_platform_data *plat_data = pdev->dev.platform_data; + struct ipuv3_fb_platform_data *fbdata; + + fbdata = plat_data->fb_head0_platform_data; + if (fbdata) { + fbdata->ipu_channel_bg = + MX51_IPU_CHANNEL_MEM_BG_SYNC; + fbdata->ipu_channel_fg = + MX51_IPU_CHANNEL_MEM_FG_SYNC; + fbdata->dc_channel = 5; + fbdata->dp_channel = IPU_DP_FLOW_SYNC; + + ipu_add_subdevice_pdata(pdev, "imx-ipuv3-fb", 0, + fbdata); + } + + fbdata = plat_data->fb_head1_platform_data; + if (fbdata) { + fbdata->ipu_channel_bg = + MX51_IPU_CHANNEL_MEM_DC_SYNC; + fbdata->ipu_channel_fg = -1; + fbdata->dc_channel = 1; + fbdata->dp_channel = -1; + + ipu_add_subdevice_pdata(pdev, "imx-ipuv3-fb", 1, + fbdata); + } + + return 0; +} + +static int __devinit ipu_probe(struct platform_device *pdev) +{ + struct resource *res; + unsigned long ipu_base; + int ret, irq1, irq2; + + /* There can be only one */ + if (ipu_dev) + return -EBUSY; + + spin_lock_init(&ipu_lock); + + ipu_dev = &pdev->dev; + + irq1 = platform_get_irq(pdev, 0); + irq2 = platform_get_irq(pdev, 1); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!res || irq1 < 0 || irq2 < 0) + return -ENODEV; + + ipu_base = res->start; + + ipu_cm_reg = ioremap(ipu_base + IPU_CM_REG_BASE, PAGE_SIZE); + if (!ipu_cm_reg) { + ret = -ENOMEM; + goto failed_ioremap1; + } + + ipu_idmac_reg = ioremap(ipu_base + IPU_IDMAC_REG_BASE, PAGE_SIZE); + if (!ipu_idmac_reg) { + ret = -ENOMEM; + goto failed_ioremap2; + } + + ret = ipu_mipi_setup(); + if (ret) + goto failed_mipi_setup; + + ipu_clk = clk_get(&pdev->dev, "ipu"); + if (IS_ERR(ipu_clk)) { + ret = PTR_ERR(ipu_clk); + dev_err(&pdev->dev, "clk_get failed with %d", ret); + goto failed_clk_get; + } + + ipu_get(); + + ret = request_irq(irq1, ipu_irq_handler, IRQF_DISABLED, pdev->name, + &pdev->dev); + if (ret) { + dev_err(&pdev->dev, "request irq %d failed with: %d\n", irq1, ret); + goto failed_request_irq1; + } + + ret = request_irq(irq2, ipu_irq_handler, IRQF_DISABLED, pdev->name, + &pdev->dev); + if (ret) { + dev_err(&pdev->dev, "request irq %d failed with: %d\n", irq2, ret); + goto failed_request_irq2; + } + + ipu_reset(); + + ret = ipu_submodules_init(pdev, ipu_base, ipu_clk); + if (ret) + goto failed_submodules_init; + + /* Set sync refresh channels as high priority */ + ipu_idmac_write(0x18800000, IDMAC_CHA_PRI(0)); + + ret = ipu_add_client_devices(pdev); + if (ret) { + dev_err(&pdev->dev, "adding client devices failed with %d\n", ret); + goto failed_add_clients; + } + + ipu_put(); + + return 0; + +failed_add_clients: + ipu_submodules_exit(pdev, ipu_base); +failed_submodules_init: + free_irq(irq2, &pdev->dev); +failed_request_irq2: + free_irq(irq1, &pdev->dev); + ipu_put(); +failed_request_irq1: + clk_put(ipu_clk); +failed_clk_get: +failed_mipi_setup: + iounmap(ipu_idmac_reg); +failed_ioremap2: + iounmap(ipu_cm_reg); +failed_ioremap1: + + return ret; +} + +static int __devexit ipu_remove(struct platform_device *pdev) +{ + struct resource *res; + unsigned long ipu_base; + int irq1, irq2; + + irq1 = platform_get_irq(pdev, 0); + irq2 = platform_get_irq(pdev, 1); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ipu_base = res->start; + + mfd_remove_devices(&pdev->dev); + ipu_submodules_exit(pdev, ipu_base); + free_irq(irq2, &pdev->dev); + free_irq(irq1, &pdev->dev); + iounmap(ipu_idmac_reg); + iounmap(ipu_cm_reg); + + if (ipu_use_count != 0) { + dev_err(ipu_dev, "unbalanced use count: %d\n", ipu_use_count); + clk_disable(ipu_clk); + } + + clk_put(ipu_clk); + ipu_dev = NULL; + + return 0; +} + +static struct platform_driver mxcipu_driver = { + .driver = { + .name = "imx-ipuv3", + }, + .probe = ipu_probe, + .remove = __devexit_p(ipu_remove), +}; + +static int __init ipu_gen_init(void) +{ + int32_t ret; + + ret = platform_driver_register(&mxcipu_driver); + return 0; +} +subsys_initcall(ipu_gen_init); + +static void __exit ipu_gen_uninit(void) +{ + platform_driver_unregister(&mxcipu_driver); +} +module_exit(ipu_gen_uninit); + +MODULE_DESCRIPTION("i.MX IPU v3 driver"); +MODULE_AUTHOR("Sascha Hauer "); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/imx-ipu-v3/ipu-cpmem.c b/drivers/video/imx-ipu-v3/ipu-cpmem.c new file mode 100644 index 0000000..faff5ee --- /dev/null +++ b/drivers/video/imx-ipu-v3/ipu-cpmem.c @@ -0,0 +1,612 @@ +/* + * Copyright (c) 2010 Sascha Hauer + * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * 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 "ipu-prv.h" + +#define __F(word, ofs, size) ((((word) * 160 + (ofs)) << 8) | (size)) + +#define IPU_FIELD_XV __F(0, 0, 10) +#define IPU_FIELD_YV __F(0, 10, 9) +#define IPU_FIELD_XB __F(0, 19, 13) +#define IPU_FIELD_YB __F(0, 32, 12) +#define IPU_FIELD_NSB_B __F(0, 44, 1) +#define IPU_FIELD_CF __F(0, 45, 1) +#define IPU_FIELD_UBO __F(0, 46, 22) +#define IPU_FIELD_VBO __F(0, 68, 22) +#define IPU_FIELD_IOX __F(0, 90, 4) +#define IPU_FIELD_RDRW __F(0, 94, 1) +#define IPU_FIELD_SO __F(0, 113, 1) +#define IPU_FIELD_BNDM __F(0, 114, 3) +#define IPU_FIELD_BM __F(0, 117, 2) +#define IPU_FIELD_ROT __F(0, 119, 1) +#define IPU_FIELD_HF __F(0, 120, 1) +#define IPU_FIELD_VF __F(0, 121, 1) +#define IPU_FIELD_THE __F(0, 122, 1) +#define IPU_FIELD_CAP __F(0, 123, 1) +#define IPU_FIELD_CAE __F(0, 124, 1) +#define IPU_FIELD_FW __F(0, 125, 13) +#define IPU_FIELD_FH __F(0, 138, 12) +#define IPU_FIELD_EBA0 __F(1, 0, 29) +#define IPU_FIELD_EBA1 __F(1, 29, 29) +#define IPU_FIELD_ILO __F(1, 58, 20) +#define IPU_FIELD_NPB __F(1, 78, 7) +#define IPU_FIELD_PFS __F(1, 85, 4) +#define IPU_FIELD_ALU __F(1, 89, 1) +#define IPU_FIELD_ALBM __F(1, 90, 3) +#define IPU_FIELD_ID __F(1, 93, 2) +#define IPU_FIELD_TH __F(1, 95, 7) +#define IPU_FIELD_SLY __F(1, 102, 14) +#define IPU_FIELD_WID3 __F(1, 125, 3) +#define IPU_FIELD_SLUV __F(1, 128, 14) +#define IPU_FIELD_CRE __F(1, 149, 1) + +#define IPU_FIELD_XV __F(0, 0, 10) +#define IPU_FIELD_YV __F(0, 10, 9) +#define IPU_FIELD_XB __F(0, 19, 13) +#define IPU_FIELD_YB __F(0, 32, 12) +#define IPU_FIELD_NSB_B __F(0, 44, 1) +#define IPU_FIELD_CF __F(0, 45, 1) +#define IPU_FIELD_SX __F(0, 46, 12) +#define IPU_FIELD_SY __F(0, 58, 11) +#define IPU_FIELD_NS __F(0, 69, 10) +#define IPU_FIELD_SDX __F(0, 79, 7) +#define IPU_FIELD_SM __F(0, 86, 10) +#define IPU_FIELD_SCC __F(0, 96, 1) +#define IPU_FIELD_SCE __F(0, 97, 1) +#define IPU_FIELD_SDY __F(0, 98, 7) +#define IPU_FIELD_SDRX __F(0, 105, 1) +#define IPU_FIELD_SDRY __F(0, 106, 1) +#define IPU_FIELD_BPP __F(0, 107, 3) +#define IPU_FIELD_DEC_SEL __F(0, 110, 2) +#define IPU_FIELD_DIM __F(0, 112, 1) +#define IPU_FIELD_SO __F(0, 113, 1) +#define IPU_FIELD_BNDM __F(0, 114, 3) +#define IPU_FIELD_BM __F(0, 117, 2) +#define IPU_FIELD_ROT __F(0, 119, 1) +#define IPU_FIELD_HF __F(0, 120, 1) +#define IPU_FIELD_VF __F(0, 121, 1) +#define IPU_FIELD_THE __F(0, 122, 1) +#define IPU_FIELD_CAP __F(0, 123, 1) +#define IPU_FIELD_CAE __F(0, 124, 1) +#define IPU_FIELD_FW __F(0, 125, 13) +#define IPU_FIELD_FH __F(0, 138, 12) +#define IPU_FIELD_EBA0 __F(1, 0, 29) +#define IPU_FIELD_EBA1 __F(1, 29, 29) +#define IPU_FIELD_ILO __F(1, 58, 20) +#define IPU_FIELD_NPB __F(1, 78, 7) +#define IPU_FIELD_PFS __F(1, 85, 4) +#define IPU_FIELD_ALU __F(1, 89, 1) +#define IPU_FIELD_ALBM __F(1, 90, 3) +#define IPU_FIELD_ID __F(1, 93, 2) +#define IPU_FIELD_TH __F(1, 95, 7) +#define IPU_FIELD_SL __F(1, 102, 14) +#define IPU_FIELD_WID0 __F(1, 116, 3) +#define IPU_FIELD_WID1 __F(1, 119, 3) +#define IPU_FIELD_WID2 __F(1, 122, 3) +#define IPU_FIELD_WID3 __F(1, 125, 3) +#define IPU_FIELD_OFS0 __F(1, 128, 5) +#define IPU_FIELD_OFS1 __F(1, 133, 5) +#define IPU_FIELD_OFS2 __F(1, 138, 5) +#define IPU_FIELD_OFS3 __F(1, 143, 5) +#define IPU_FIELD_SXYS __F(1, 148, 1) +#define IPU_FIELD_CRE __F(1, 149, 1) +#define IPU_FIELD_DEC_SEL2 __F(1, 150, 1) + +static u32 *ipu_cpmem_base; +static struct device *ipu_dev; + +struct ipu_ch_param_word { + u32 data[5]; + u32 res[3]; +}; + +struct ipu_ch_param { + struct ipu_ch_param_word word[2]; +}; + +static u32 ipu_bytes_per_pixel(u32 fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_GENERIC: /* generic data */ + case IPU_PIX_FMT_RGB332: + case IPU_PIX_FMT_YUV420P: + case IPU_PIX_FMT_YUV422P: + return 1; + + case IPU_PIX_FMT_RGB565: + case IPU_PIX_FMT_YUYV: + case IPU_PIX_FMT_UYVY: + return 2; + + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + return 3; + + case IPU_PIX_FMT_GENERIC_32: /* generic data */ + case IPU_PIX_FMT_BGR32: + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_RGB32: + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_ABGR32: + return 4; + + default: + return 1; + } +} + +bool ipu_pixel_format_has_alpha(u32 fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_ABGR32: + return true; + + default: + return false; + } +} + +#define ipu_ch_param_addr(ch) (((struct ipu_ch_param *)ipu_cpmem_base) + (ch)) + +static inline void ipu_ch_param_set_field(struct ipu_ch_param *base, u32 wbs, u32 v) +{ + u32 bit = (wbs >> 8) % 160; + u32 size = wbs & 0xff; + u32 word = (wbs >> 8) / 160; + u32 i = bit / 32; + u32 ofs = bit % 32; + u32 mask = (1 << size) - 1; + + pr_debug("%s %d %d %d\n", __func__, word, bit , size); + + base->word[word].data[i] &= ~(mask << ofs); + base->word[word].data[i] |= v << ofs; + + if ((bit + size - 1) / 32 > i) { + base->word[word].data[i + 1] &= ~(v >> (mask ? (32 - ofs) : 0)); + base->word[word].data[i + 1] |= v >> (ofs ? (32 - ofs) : 0); + } +} + +static inline u32 ipu_ch_param_read_field(struct ipu_ch_param *base, u32 wbs) +{ + u32 bit = (wbs >> 8) % 160; + u32 size = wbs & 0xff; + u32 word = (wbs >> 8) / 160; + u32 i = bit / 32; + u32 ofs = bit % 32; + u32 mask = (1 << size) - 1; + u32 val = 0; + + pr_debug("%s %d %d %d\n", __func__, word, bit , size); + + val = (base->word[word].data[i] >> ofs) & mask; + + if ((bit + size - 1) / 32 > i) { + u32 tmp; + tmp = base->word[word].data[i + 1]; + tmp &= mask >> (ofs ? (32 - ofs) : 0); + val |= tmp << (ofs ? (32 - ofs) : 0); + } + + return val; +} + +static inline void ipu_ch_params_set_packing(struct ipu_ch_param *p, + int red_width, int red_offset, + int green_width, int green_offset, + int blue_width, int blue_offset, + int alpha_width, int alpha_offset) +{ + /* Setup red width and offset */ + ipu_ch_param_set_field(p, IPU_FIELD_WID0, red_width - 1); + ipu_ch_param_set_field(p, IPU_FIELD_OFS0, red_offset); + /* Setup green width and offset */ + ipu_ch_param_set_field(p, IPU_FIELD_WID1, green_width - 1); + ipu_ch_param_set_field(p, IPU_FIELD_OFS1, green_offset); + /* Setup blue width and offset */ + ipu_ch_param_set_field(p, IPU_FIELD_WID2, blue_width - 1); + ipu_ch_param_set_field(p, IPU_FIELD_OFS2, blue_offset); + /* Setup alpha width and offset */ + ipu_ch_param_set_field(p, IPU_FIELD_WID3, alpha_width - 1); + ipu_ch_param_set_field(p, IPU_FIELD_OFS3, alpha_offset); +} + +static inline void ipu_ch_param_dump(int ch) +{ + struct ipu_ch_param *p = ipu_ch_param_addr(ch); + pr_debug("ch %d word 0 - %08X %08X %08X %08X %08X\n", ch, + p->word[0].data[0], p->word[0].data[1], p->word[0].data[2], + p->word[0].data[3], p->word[0].data[4]); + pr_debug("ch %d word 1 - %08X %08X %08X %08X %08X\n", ch, + p->word[1].data[0], p->word[1].data[1], p->word[1].data[2], + p->word[1].data[3], p->word[1].data[4]); + pr_debug("PFS 0x%x\n", ipu_ch_param_read_field(p, IPU_FIELD_PFS)); + pr_debug("BPP 0x%x\n", ipu_ch_param_read_field(p, IPU_FIELD_BPP)); + pr_debug("NPB 0x%x\n", ipu_ch_param_read_field(p, IPU_FIELD_NPB)); + + pr_debug("FW %d\n", ipu_ch_param_read_field(p, IPU_FIELD_FW)); + pr_debug("FH %d\n", ipu_ch_param_read_field(p, IPU_FIELD_FH)); + pr_debug("Stride %d\n", ipu_ch_param_read_field(p, IPU_FIELD_SL)); + + pr_debug("Width0 %d+1\n", ipu_ch_param_read_field(p, IPU_FIELD_WID0)); + pr_debug("Width1 %d+1\n", ipu_ch_param_read_field(p, IPU_FIELD_WID1)); + pr_debug("Width2 %d+1\n", ipu_ch_param_read_field(p, IPU_FIELD_WID2)); + pr_debug("Width3 %d+1\n", ipu_ch_param_read_field(p, IPU_FIELD_WID3)); + pr_debug("Offset0 %d\n", ipu_ch_param_read_field(p, IPU_FIELD_OFS0)); + pr_debug("Offset1 %d\n", ipu_ch_param_read_field(p, IPU_FIELD_OFS1)); + pr_debug("Offset2 %d\n", ipu_ch_param_read_field(p, IPU_FIELD_OFS2)); + pr_debug("Offset3 %d\n", ipu_ch_param_read_field(p, IPU_FIELD_OFS3)); +} + +static inline void ipu_ch_param_set_burst_size(u32 ch, + u16 burst_pixels) +{ + ipu_ch_param_set_field(ipu_ch_param_addr(ch), IPU_FIELD_NPB, + burst_pixels - 1); +}; + +static inline int ipu_ch_param_get_burst_size(u32 ch) +{ + return ipu_ch_param_read_field(ipu_ch_param_addr(ch), IPU_FIELD_NPB) + 1; +}; + +static inline int ipu_ch_param_get_bpp(u32 ch) +{ + return ipu_ch_param_read_field(ipu_ch_param_addr(ch), IPU_FIELD_BPP); +}; + +static inline void ipu_ch_param_set_buffer(u32 ch, int bufNum, + dma_addr_t phyaddr) +{ + ipu_ch_param_set_field(ipu_ch_param_addr(ch), + bufNum ? IPU_FIELD_EBA1 : IPU_FIELD_EBA0, + phyaddr / 8); +}; + +#define IPU_FIELD_ROT_HF_VF __F(0, 119, 3) + +static inline void ipu_ch_param_set_rotation(u32 ch, + ipu_rotate_mode_t rot) +{ + u32 temp_rot = bitrev8(rot) >> 5; + ipu_ch_param_set_field(ipu_ch_param_addr(ch), IPU_FIELD_ROT_HF_VF, temp_rot); +}; + +static inline void ipu_ch_param_set_block_mode(u32 ch) +{ + ipu_ch_param_set_field(ipu_ch_param_addr(ch), IPU_FIELD_BM, 1); +}; + +static inline void ipu_ch_param_set_alpha_use_separate_channel(u32 ch, + bool option) +{ + if (option) + ipu_ch_param_set_field(ipu_ch_param_addr(ch), IPU_FIELD_ALU, 1); + else + ipu_ch_param_set_field(ipu_ch_param_addr(ch), IPU_FIELD_ALU, 0); +}; + +static inline void ipu_ch_param_set_alpha_condition_read(u32 ch) +{ + ipu_ch_param_set_field(ipu_ch_param_addr(ch), IPU_FIELD_CRE, 1); +}; + +static inline void ipu_ch_param_set_alpha_buffer_memory(u32 ch) +{ + int alp_mem_idx; + + switch (ch) { + case 14: /* PRP graphic */ + alp_mem_idx = 0; + break; + case 15: /* PP graphic */ + alp_mem_idx = 1; + break; + case 23: /* DP BG SYNC graphic */ + alp_mem_idx = 4; + break; + case 27: /* DP FG SYNC graphic */ + alp_mem_idx = 2; + break; + default: + dev_err(ipu_dev, "unsupported correlated channel of local alpha channel\n"); + return; + } + + ipu_ch_param_set_field(ipu_ch_param_addr(ch), IPU_FIELD_ALBM, alp_mem_idx); +}; + +static inline void ipu_ch_param_set_interlaced_scan(u32 ch) +{ + u32 stride; + ipu_ch_param_set_field(ipu_ch_param_addr(ch), IPU_FIELD_SO, 1); + stride = ipu_ch_param_read_field(ipu_ch_param_addr(ch), IPU_FIELD_SL) + 1; + ipu_ch_param_set_field(ipu_ch_param_addr(ch), IPU_FIELD_ILO, stride / 8); + stride *= 2; + ipu_ch_param_set_field(ipu_ch_param_addr(ch), IPU_FIELD_SLY, stride - 1); +}; + +static inline void ipu_ch_param_set_high_priority(u32 ch) +{ + ipu_ch_param_set_field(ipu_ch_param_addr(ch), IPU_FIELD_ID, 1); +}; + +static inline void ipu_ch_params_set_alpha_width(u32 ch, int alpha_width) +{ + ipu_ch_param_set_field(ipu_ch_param_addr(ch), IPU_FIELD_WID3, + alpha_width - 1); +} + +static int ipu_ch_param_init(int ch, + u32 pixel_fmt, u32 width, + u32 height, u32 stride, + u32 u, u32 v, + u32 uv_stride, dma_addr_t addr0, + dma_addr_t addr1) +{ + u32 u_offset = 0; + u32 v_offset = 0; + struct ipu_ch_param params; + + memset(¶ms, 0, sizeof(params)); + + ipu_ch_param_set_field(¶ms, IPU_FIELD_FW, width - 1); + ipu_ch_param_set_field(¶ms, IPU_FIELD_FH, height - 1); + ipu_ch_param_set_field(¶ms, IPU_FIELD_SLY, stride - 1); + ipu_ch_param_set_field(¶ms, IPU_FIELD_EBA0, addr0 >> 3); + ipu_ch_param_set_field(¶ms, IPU_FIELD_EBA1, addr1 >> 3); + + switch (pixel_fmt) { + case IPU_PIX_FMT_GENERIC: + /* Represents 8-bit Generic data */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_BPP, 5); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_PFS, 6); /* pix format */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_NPB, 63); /* burst size */ + + break; + case IPU_PIX_FMT_GENERIC_32: + /* Represents 32-bit Generic data */ + break; + case IPU_PIX_FMT_RGB565: + ipu_ch_param_set_field(¶ms, IPU_FIELD_BPP, 3); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_PFS, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_NPB, 15); /* burst size */ + + ipu_ch_params_set_packing(¶ms, 5, 0, 6, 5, 5, 11, 8, 16); + break; + case IPU_PIX_FMT_BGR24: + ipu_ch_param_set_field(¶ms, IPU_FIELD_BPP, 1); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_PFS, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_NPB, 19); /* burst size */ + + ipu_ch_params_set_packing(¶ms, 8, 0, 8, 8, 8, 16, 8, 24); + break; + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_YUV444: + ipu_ch_param_set_field(¶ms, IPU_FIELD_BPP, 1); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_PFS, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_NPB, 19); /* burst size */ + + ipu_ch_params_set_packing(¶ms, 8, 16, 8, 8, 8, 0, 8, 24); + break; + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_BGR32: + ipu_ch_param_set_field(¶ms, IPU_FIELD_BPP, 0); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_PFS, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_NPB, 15); /* burst size */ + + ipu_ch_params_set_packing(¶ms, 8, 8, 8, 16, 8, 24, 8, 0); + break; + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_RGB32: + ipu_ch_param_set_field(¶ms, IPU_FIELD_BPP, 0); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_PFS, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_NPB, 15); /* burst size */ + + ipu_ch_params_set_packing(¶ms, 8, 24, 8, 16, 8, 8, 8, 0); + break; + case IPU_PIX_FMT_ABGR32: + ipu_ch_param_set_field(¶ms, IPU_FIELD_BPP, 0); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_PFS, 7); /* pix format */ + + ipu_ch_params_set_packing(¶ms, 8, 0, 8, 8, 8, 16, 8, 24); + break; + case IPU_PIX_FMT_UYVY: + ipu_ch_param_set_field(¶ms, IPU_FIELD_BPP, 3); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_PFS, 0xA); /* pix format */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_NPB, 15); /* burst size */ + break; + case IPU_PIX_FMT_YUYV: + ipu_ch_param_set_field(¶ms, IPU_FIELD_BPP, 3); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_PFS, 0x8); /* pix format */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_NPB, 31); /* burst size */ + break; + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YUV420P: + ipu_ch_param_set_field(¶ms, IPU_FIELD_PFS, 2); /* pix format */ + + if (uv_stride < stride / 2) + uv_stride = stride / 2; + + u_offset = stride * height; + v_offset = u_offset + (uv_stride * height / 2); + ipu_ch_param_set_field(¶ms, IPU_FIELD_NPB, 31); /* burst size */ + break; + case IPU_PIX_FMT_YVU422P: + /* BPP & pixel format */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_PFS, 1); /* pix format */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_NPB, 31); /* burst size */ + + if (uv_stride < stride / 2) + uv_stride = stride / 2; + + v_offset = (v == 0) ? stride * height : v; + u_offset = (u == 0) ? v_offset + v_offset / 2 : u; + break; + case IPU_PIX_FMT_YUV422P: + /* BPP & pixel format */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_PFS, 1); /* pix format */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_NPB, 31); /* burst size */ + + if (uv_stride < stride / 2) + uv_stride = stride / 2; + + u_offset = (u == 0) ? stride * height : u; + v_offset = (v == 0) ? u_offset + u_offset / 2 : v; + break; + case IPU_PIX_FMT_NV12: + /* BPP & pixel format */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_PFS, 4); /* pix format */ + ipu_ch_param_set_field(¶ms, IPU_FIELD_NPB, 31); /* burst size */ + uv_stride = stride; + u_offset = (u == 0) ? stride * height : u; + break; + default: + dev_err(ipu_dev, "mxc ipu: unimplemented pixel format: %d\n", + pixel_fmt); + return -EINVAL; + } + /* set burst size to 16 */ + if (uv_stride) + ipu_ch_param_set_field(¶ms, IPU_FIELD_SLUV, uv_stride - 1); + + if (u > u_offset) + u_offset = u; + + if (v > v_offset) + v_offset = v; + + ipu_ch_param_set_field(¶ms, IPU_FIELD_UBO, u_offset / 8); + ipu_ch_param_set_field(¶ms, IPU_FIELD_VBO, v_offset / 8); + + pr_debug("initializing idma ch %d @ %p\n", ch, ipu_ch_param_addr(ch)); + memcpy(ipu_ch_param_addr(ch), ¶ms, sizeof(params)); + return 0; +} + +/* + * This function is called to initialize a buffer for a IPU channel. + * + * @param channel The IPU channel. + * + * @param pixel_fmt Input parameter for pixel format of buffer. + * Pixel format is a FOURCC ASCII code. + * + * @param width Input parameter for width of buffer in pixels. + * + * @param height Input parameter for height of buffer in pixels. + * + * @param stride Input parameter for stride length of buffer + * in pixels. + * + * @param rot_mode Input parameter for rotation setting of buffer. + * A rotation setting other than + * IPU_ROTATE_VERT_FLIP + * should only be used for input buffers of + * rotation channels. + * + * @param phyaddr_0 Input parameter buffer 0 physical address. + * + * @param phyaddr_1 Input parameter buffer 1 physical address. + * Setting this to a value other than NULL enables + * double buffering mode. + * + * @param u private u offset for additional cropping, + * zero if not used. + * + * @param v private v offset for additional cropping, + * zero if not used. + * + * @return Returns 0 on success or negative error code on fail + */ +int ipu_idmac_init_channel_buffer(struct ipu_channel *channel, + u32 pixel_fmt, + u16 width, u16 height, + u32 stride, + ipu_rotate_mode_t rot_mode, + dma_addr_t phyaddr_0, dma_addr_t phyaddr_1, + u32 u, u32 v, bool interlaced) +{ + int ret = 0; + u32 dma_chan = channel->num; + + if (stride < width * ipu_bytes_per_pixel(pixel_fmt)) + stride = width * ipu_bytes_per_pixel(pixel_fmt); + + if (stride % 4) { + dev_err(ipu_dev, + "Stride not 32-bit aligned, stride = %d\n", stride); + return -EINVAL; + } + + ipu_get(); + + /* Build parameter memory data for DMA channel */ + ret = ipu_ch_param_init(dma_chan, pixel_fmt, width, height, stride, u, v, 0, + phyaddr_0, phyaddr_1); + if (ret) + goto out; + + if (rot_mode) + ipu_ch_param_set_rotation(dma_chan, rot_mode); + + if (interlaced) + ipu_ch_param_set_interlaced_scan(dma_chan); + + if (idmac_idma_is_set(IDMAC_CHA_PRI, dma_chan)) + ipu_ch_param_set_high_priority(dma_chan); + + ipu_ch_param_dump(dma_chan); +out: + ipu_put(); + + return ret; +} +EXPORT_SYMBOL(ipu_idmac_init_channel_buffer); + +int ipu_idmac_update_channel_buffer(struct ipu_channel *channel, + u32 buf_num, dma_addr_t phyaddr) +{ + u32 dma_chan = channel->num; + + ipu_ch_param_set_buffer(dma_chan, buf_num, phyaddr); + + return 0; +} +EXPORT_SYMBOL(ipu_idmac_update_channel_buffer); + +int ipu_cpmem_init(struct platform_device *pdev, unsigned long base) +{ + ipu_cpmem_base = ioremap(base, PAGE_SIZE); + if (!ipu_cpmem_base) + return -ENOMEM; + ipu_dev = &pdev->dev; + return 0; +} + +void ipu_cpmem_exit(struct platform_device *pdev) +{ + iounmap(ipu_cpmem_base); +} diff --git a/drivers/video/imx-ipu-v3/ipu-dc.c b/drivers/video/imx-ipu-v3/ipu-dc.c new file mode 100644 index 0000000..b8c601e --- /dev/null +++ b/drivers/video/imx-ipu-v3/ipu-dc.c @@ -0,0 +1,364 @@ +/* + * Copyright (c) 2010 Sascha Hauer + * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * 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 "ipu-prv.h" + +#define ASYNC_SER_WAVE 6 + +#define DC_DISP_ID_SERIAL 2 +#define DC_DISP_ID_ASYNC 3 + +#define DC_MAP_CONF_PTR(n) (ipu_dc_reg + 0x0108 + ((n) & ~0x1) * 2) +#define DC_MAP_CONF_VAL(n) (ipu_dc_reg + 0x0144 + ((n) & ~0x1) * 2) + +#define DC_EVT_NF 0 +#define DC_EVT_NL 1 +#define DC_EVT_EOF 2 +#define DC_EVT_NFIELD 3 +#define DC_EVT_EOL 4 +#define DC_EVT_EOFIELD 5 +#define DC_EVT_NEW_ADDR 6 +#define DC_EVT_NEW_CHAN 7 +#define DC_EVT_NEW_DATA 8 + +#define DC_EVT_NEW_ADDR_W_0 0 +#define DC_EVT_NEW_ADDR_W_1 1 +#define DC_EVT_NEW_CHAN_W_0 2 +#define DC_EVT_NEW_CHAN_W_1 3 +#define DC_EVT_NEW_DATA_W_0 4 +#define DC_EVT_NEW_DATA_W_1 5 +#define DC_EVT_NEW_ADDR_R_0 6 +#define DC_EVT_NEW_ADDR_R_1 7 +#define DC_EVT_NEW_CHAN_R_0 8 +#define DC_EVT_NEW_CHAN_R_1 9 +#define DC_EVT_NEW_DATA_R_0 10 +#define DC_EVT_NEW_DATA_R_1 11 + +#define DC_WR_CH_CONF(ch) (ipu_dc_reg + dc_channels[ch].channel_offset) +#define DC_WR_CH_ADDR(ch) (ipu_dc_reg + dc_channels[ch].channel_offset + 4) +#define DC_RL_CH(ch, evt) (ipu_dc_reg + dc_channels[ch].channel_offset + 8 + ((evt) & ~0x1) * 2) + +#define DC_GEN (ipu_dc_reg + 0x00D4) +#define DC_DISP_CONF1(disp) (ipu_dc_reg + 0x00D8 + disp * 4) +#define DC_DISP_CONF2(disp) (ipu_dc_reg + 0x00E8 + disp * 4) +#define DC_STAT (ipu_dc_reg + 0x01C8) + +#define WROD(lf) (0x18 | (lf << 1)) + +#define DC_WR_CH_CONF_FIELD_MODE (1 << 9) +#define DC_WR_CH_CONF_PROG_TYPE_OFFSET 5 +#define DC_WR_CH_CONF_PROG_TYPE_MASK (7 << 5) +#define DC_WR_CH_CONF_PROG_DI_ID (1 << 2) +#define DC_WR_CH_CONF_PROG_DISP_ID_OFFSET 3 +#define DC_WR_CH_CONF_PROG_DISP_ID_MASK (3 << 3) + +static void __iomem *ipu_dc_reg; +static void __iomem *ipu_dc_tmpl_reg; +static struct device *ipu_dev; + +struct ipu_dc { + unsigned int di; /* The display interface number assigned to this dc channel */ + unsigned int channel_offset; +}; + +static struct ipu_dc dc_channels[10]; + +static void ipu_dc_link_event(int chan, int event, int addr, int priority) +{ + u32 reg; + + reg = __raw_readl(DC_RL_CH(chan, event)); + reg &= ~(0xFFFF << (16 * (event & 0x1))); + reg |= ((addr << 8) | priority) << (16 * (event & 0x1)); + __raw_writel(reg, DC_RL_CH(chan, event)); +} + +static void ipu_dc_write_tmpl(int word, u32 opcode, u32 operand, int map, + int wave, int glue, int sync) +{ + u32 reg; + int stop = 1; + + reg = sync; + reg |= (glue << 4); + reg |= (++wave << 11); + reg |= (++map << 15); + reg |= (operand << 20) & 0xFFF00000; + __raw_writel(reg, ipu_dc_tmpl_reg + word * 8); + + reg = (operand >> 12); + reg |= opcode << 4; + reg |= (stop << 9); + __raw_writel(reg, ipu_dc_tmpl_reg + word * 8 + 4); +} + +static int ipu_pixfmt_to_map(u32 fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_GENERIC: + case IPU_PIX_FMT_RGB24: + return 0; + case IPU_PIX_FMT_RGB666: + return 1; + case IPU_PIX_FMT_YUV444: + return 2; + case IPU_PIX_FMT_RGB565: + return 3; + case IPU_PIX_FMT_LVDS666: + return 4; + } + + return -EINVAL; +} + +#define SYNC_WAVE 0 + +int ipu_dc_init_sync(int dc_chan, int di, bool interlaced, u32 pixel_fmt, u32 width) +{ + u32 reg = 0, map; + + dc_channels[dc_chan].di = di; + + map = ipu_pixfmt_to_map(pixel_fmt); + if (map < 0) { + dev_dbg(ipu_dev, "IPU_DISP: No MAP\n"); + return -EINVAL; + } + + ipu_get(); + + if (interlaced) { + ipu_dc_link_event(dc_chan, DC_EVT_NL, 0, 3); + ipu_dc_link_event(dc_chan, DC_EVT_EOL, 0, 2); + ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA, 0, 1); + + /* Init template microcode */ + ipu_dc_write_tmpl(0, WROD(0), 0, map, SYNC_WAVE, 0, 8); + } else { + if (di) { + ipu_dc_link_event(dc_chan, DC_EVT_NL, 2, 3); + ipu_dc_link_event(dc_chan, DC_EVT_EOL, 3, 2); + ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA, 4, 1); + /* Init template microcode */ + ipu_dc_write_tmpl(2, WROD(0), 0, map, SYNC_WAVE, 8, 5); + ipu_dc_write_tmpl(3, WROD(0), 0, map, SYNC_WAVE, 4, 5); + ipu_dc_write_tmpl(4, WROD(0), 0, map, SYNC_WAVE, 0, 5); + } else { + ipu_dc_link_event(dc_chan, DC_EVT_NL, 5, 3); + ipu_dc_link_event(dc_chan, DC_EVT_EOL, 6, 2); + ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA, 7, 1); + /* Init template microcode */ + ipu_dc_write_tmpl(5, WROD(0), 0, map, SYNC_WAVE, 8, 5); + ipu_dc_write_tmpl(6, WROD(0), 0, map, SYNC_WAVE, 4, 5); + ipu_dc_write_tmpl(7, WROD(0), 0, map, SYNC_WAVE, 0, 5); + } + } + ipu_dc_link_event(dc_chan, DC_EVT_NF, 0, 0); + ipu_dc_link_event(dc_chan, DC_EVT_NFIELD, 0, 0); + ipu_dc_link_event(dc_chan, DC_EVT_EOF, 0, 0); + ipu_dc_link_event(dc_chan, DC_EVT_EOFIELD, 0, 0); + ipu_dc_link_event(dc_chan, DC_EVT_NEW_CHAN, 0, 0); + ipu_dc_link_event(dc_chan, DC_EVT_NEW_ADDR, 0, 0); + + reg = 0x2; + reg |= di << DC_WR_CH_CONF_PROG_DISP_ID_OFFSET; + reg |= di << 2; + if (interlaced) + reg |= DC_WR_CH_CONF_FIELD_MODE; + + __raw_writel(reg, DC_WR_CH_CONF(dc_chan)); + + __raw_writel(0x00000000, DC_WR_CH_ADDR(dc_chan)); + + __raw_writel(0x00000084, DC_GEN); + + __raw_writel(width, DC_DISP_CONF2(di)); + + ipu_module_enable(IPU_CONF_DC_EN); + + ipu_put(); + + return 0; +} +EXPORT_SYMBOL(ipu_dc_init_sync); + +void ipu_dc_init_async(int dc_chan, int di, bool interlaced) +{ + u32 reg = 0; + dc_channels[dc_chan].di = di; + ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_W_0, 0x64, 1); + ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_W_1, 0x64, 1); + + reg = 0x3; + reg |= DC_DISP_ID_SERIAL << DC_WR_CH_CONF_PROG_DISP_ID_OFFSET; + __raw_writel(reg, DC_WR_CH_CONF(dc_chan)); + + __raw_writel(0x00000000, DC_WR_CH_ADDR(dc_chan)); + + __raw_writel(0x00000084, DC_GEN); + + ipu_module_enable(IPU_CONF_DC_EN); +} +EXPORT_SYMBOL(ipu_dc_init_async); + +void ipu_dc_enable_channel(u32 dc_chan) +{ + int di; + u32 reg; + + di = dc_channels[dc_chan].di; + + /* Make sure other DC sync channel is not assigned same DI */ + reg = __raw_readl(DC_WR_CH_CONF(6 - dc_chan)); + if ((di << 2) == (reg & DC_WR_CH_CONF_PROG_DI_ID)) { + reg &= ~DC_WR_CH_CONF_PROG_DI_ID; + reg |= di ? 0 : DC_WR_CH_CONF_PROG_DI_ID; + __raw_writel(reg, DC_WR_CH_CONF(6 - dc_chan)); + } + + reg = __raw_readl(DC_WR_CH_CONF(dc_chan)); + reg |= 4 << DC_WR_CH_CONF_PROG_TYPE_OFFSET; + __raw_writel(reg, DC_WR_CH_CONF(dc_chan)); +} +EXPORT_SYMBOL(ipu_dc_enable_channel); + +void ipu_dc_disable_channel(u32 dc_chan) +{ + u32 reg; + int irq = 0, ret, timeout = 50; + + if (dc_chan == 1) { + irq = IPU_IRQ_DC_FC_1; + } else if (dc_chan == 5) { + irq = IPU_IRQ_DP_SF_END; + } else { + return; + } + + ret = ipu_wait_for_interrupt(irq, 50); + if (ret) + return; + + /* Wait for DC triple buffer to empty */ + if (dc_channels[dc_chan].di == 0) + while ((__raw_readl(DC_STAT) & 0x00000002) + != 0x00000002) { + msleep(2); + timeout -= 2; + if (timeout <= 0) + break; + } + else if (dc_channels[dc_chan].di == 1) + while ((__raw_readl(DC_STAT) & 0x00000020) + != 0x00000020) { + msleep(2); + timeout -= 2; + if (timeout <= 0) + break; + } + + reg = __raw_readl(DC_WR_CH_CONF(dc_chan)); + reg &= ~DC_WR_CH_CONF_PROG_TYPE_MASK; + __raw_writel(reg, DC_WR_CH_CONF(dc_chan)); +} +EXPORT_SYMBOL(ipu_dc_disable_channel); + +static void ipu_dc_map_config(int map, int byte_num, int offset, int mask) +{ + int ptr = map * 3 + byte_num; + u32 reg; + + reg = __raw_readl(DC_MAP_CONF_VAL(ptr)); + reg &= ~(0xffff << (16 * (ptr & 0x1))); + reg |= ((offset << 8) | mask) << (16 * (ptr & 0x1)); + __raw_writel(reg, DC_MAP_CONF_VAL(ptr)); + + reg = __raw_readl(DC_MAP_CONF_PTR(map)); + reg &= ~(0x1f << ((16 * (map & 0x1)) + (5 * byte_num))); + reg |= ptr << ((16 * (map & 0x1)) + (5 * byte_num)); + __raw_writel(reg, DC_MAP_CONF_PTR(map)); +} + +static void ipu_dc_map_clear(int map) +{ + u32 reg = __raw_readl(DC_MAP_CONF_PTR(map)); + __raw_writel(reg & ~(0xffff << (16 * (map & 0x1))), + DC_MAP_CONF_PTR(map)); +} + +int ipu_dc_init(struct platform_device *pdev, unsigned long base, unsigned long template_base) +{ + static int channel_offsets[] = { 0, 0x1c, 0x38, 0x54, 0x58, 0x5c, 0x78, 0, 0x94, 0xb4}; + int i; + + ipu_dc_reg = ioremap(base, PAGE_SIZE); + if (!ipu_dc_reg) + return -ENOMEM; + + ipu_dev = &pdev->dev; + + ipu_dc_tmpl_reg = ioremap(template_base, PAGE_SIZE); + if (!ipu_dc_tmpl_reg) { + iounmap(ipu_dc_reg); + return -ENOMEM; + } + + for (i = 0; i < 10; i++) + dc_channels[i].channel_offset = channel_offsets[i]; + + /* IPU_PIX_FMT_RGB24 */ + ipu_dc_map_clear(0); + ipu_dc_map_config(0, 0, 7, 0xff); + ipu_dc_map_config(0, 1, 15, 0xff); + ipu_dc_map_config(0, 2, 23, 0xff); + + /* IPU_PIX_FMT_RGB666 */ + ipu_dc_map_clear(1); + ipu_dc_map_config(1, 0, 5, 0xfc); + ipu_dc_map_config(1, 1, 11, 0xfc); + ipu_dc_map_config(1, 2, 17, 0xfc); + + /* IPU_PIX_FMT_YUV444 */ + ipu_dc_map_clear(2); + ipu_dc_map_config(2, 0, 15, 0xff); + ipu_dc_map_config(2, 1, 23, 0xff); + ipu_dc_map_config(2, 2, 7, 0xff); + + /* IPU_PIX_FMT_RGB565 */ + ipu_dc_map_clear(3); + ipu_dc_map_config(3, 0, 4, 0xf8); + ipu_dc_map_config(3, 1, 10, 0xfc); + ipu_dc_map_config(3, 2, 15, 0xf8); + + /* IPU_PIX_FMT_LVDS666 */ + ipu_dc_map_clear(4); + ipu_dc_map_config(4, 0, 5, 0xfc); + ipu_dc_map_config(4, 1, 13, 0xfc); + ipu_dc_map_config(4, 2, 21, 0xfc); + + return 0; +} + +void ipu_dc_exit(struct platform_device *pdev) +{ + iounmap(ipu_dc_reg); + iounmap(ipu_dc_tmpl_reg); +} diff --git a/drivers/video/imx-ipu-v3/ipu-di.c b/drivers/video/imx-ipu-v3/ipu-di.c new file mode 100644 index 0000000..62e3d01 --- /dev/null +++ b/drivers/video/imx-ipu-v3/ipu-di.c @@ -0,0 +1,550 @@ +/* + * Copyright (c) 2010 Sascha Hauer + * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * 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 "ipu-prv.h" + +#define SYNC_WAVE 0 + +#define DC_DISP_ID_SYNC(di) (di) + +struct ipu_di { + void __iomem *base; + int id; + u32 module; + struct clk *clk; + struct clk *ipu_clk; + bool external_clk; + bool inuse; + bool initialized; +}; + +static struct ipu_di dis[2]; + +static DEFINE_MUTEX(di_mutex); +static struct device *ipu_dev; + +struct di_sync_config { + int run_count; + int run_src; + int offset_count; + int offset_src; + int repeat_count; + int cnt_clr_src; + int cnt_polarity_gen_en; + int cnt_polarity_clr_src; + int cnt_polarity_trigger_src; + int cnt_up; + int cnt_down; +}; + +enum di_pins { + DI_PIN11 = 0, + DI_PIN12 = 1, + DI_PIN13 = 2, + DI_PIN14 = 3, + DI_PIN15 = 4, + DI_PIN16 = 5, + DI_PIN17 = 6, + DI_PIN_CS = 7, + + DI_PIN_SER_CLK = 0, + DI_PIN_SER_RS = 1, +}; + +enum di_sync_wave { + DI_SYNC_NONE = 0, + DI_SYNC_CLK = 1, + DI_SYNC_INT_HSYNC = 2, + DI_SYNC_HSYNC = 3, + DI_SYNC_VSYNC = 4, + DI_SYNC_DE = 6, +}; + +#define DI_GENERAL 0x0000 +#define DI_BS_CLKGEN0 0x0004 +#define DI_BS_CLKGEN1 0x0008 +#define DI_SW_GEN0(gen) (0x000c + 4 * ((gen) - 1)) +#define DI_SW_GEN1(gen) (0x0030 + 4 * ((gen) - 1)) +#define DI_STP_REP(gen) (0x0148 + 4 * (((gen) - 1)/2)) +#define DI_SYNC_AS_GEN 0x0054 +#define DI_DW_GEN(gen) (0x0058 + 4 * (gen)) +#define DI_DW_SET(gen, set) (0x0088 + 4 * ((gen) + 0xc * (set))) +#define DI_SER_CONF 0x015c +#define DI_SSC 0x0160 +#define DI_POL 0x0164 +#define DI_AW0 0x0168 +#define DI_AW1 0x016c +#define DI_SCR_CONF 0x0170 +#define DI_STAT 0x0174 + +#define DI_SW_GEN0_RUN_COUNT(x) ((x) << 19) +#define DI_SW_GEN0_RUN_SRC(x) ((x) << 16) +#define DI_SW_GEN0_OFFSET_COUNT(x) ((x) << 3) +#define DI_SW_GEN0_OFFSET_SRC(x) ((x) << 0) + +#define DI_SW_GEN1_CNT_POL_GEN_EN(x) ((x) << 29) +#define DI_SW_GEN1_CNT_CLR_SRC(x) ((x) << 25) +#define DI_SW_GEN1_CNT_POL_TRIGGER_SRC(x) ((x) << 12) +#define DI_SW_GEN1_CNT_POL_CLR_SRC(x) ((x) << 9) +#define DI_SW_GEN1_CNT_DOWN(x) ((x) << 16) +#define DI_SW_GEN1_CNT_UP(x) (x) +#define DI_SW_GEN1_AUTO_RELOAD (0x10000000) + +#define DI_DW_GEN_ACCESS_SIZE_OFFSET 24 +#define DI_DW_GEN_COMPONENT_SIZE_OFFSET 16 + +#define DI_GEN_DI_CLK_EXT (1 << 20) +#define DI_GEN_POLARITY_1 (1 << 0) +#define DI_GEN_POLARITY_2 (1 << 1) +#define DI_GEN_POLARITY_3 (1 << 2) +#define DI_GEN_POLARITY_4 (1 << 3) +#define DI_GEN_POLARITY_5 (1 << 4) +#define DI_GEN_POLARITY_6 (1 << 5) +#define DI_GEN_POLARITY_7 (1 << 6) +#define DI_GEN_POLARITY_8 (1 << 7) + +#define DI_POL_DRDY_DATA_POLARITY (1 << 7) +#define DI_POL_DRDY_POLARITY_15 (1 << 4) + +#define DI_VSYNC_SEL_OFFSET 13 + +#define DI0_COUNTER_RELEASE (1 << 24) +#define DI1_COUNTER_RELEASE (1 << 25) + +static inline u32 ipu_di_read(struct ipu_di *di, unsigned offset) +{ + return __raw_readl(di->base + offset); +} + +static inline void ipu_di_write(struct ipu_di *di, u32 value, unsigned offset) +{ + __raw_writel(value, di->base + offset); +} + +static void ipu_di_data_wave_config(struct ipu_di *di, + int wave_gen, + int access_size, int component_size) +{ + u32 reg; + reg = (access_size << DI_DW_GEN_ACCESS_SIZE_OFFSET) | + (component_size << DI_DW_GEN_COMPONENT_SIZE_OFFSET); + ipu_di_write(di, reg, DI_DW_GEN(wave_gen)); +} + +static void ipu_di_data_pin_config(struct ipu_di *di, int wave_gen, int di_pin, int set, + int up, int down) +{ + u32 reg; + + reg = ipu_di_read(di, DI_DW_GEN(wave_gen)); + reg &= ~(0x3 << (di_pin * 2)); + reg |= set << (di_pin * 2); + ipu_di_write(di, reg, DI_DW_GEN(wave_gen)); + + ipu_di_write(di, (down << 16) | up, DI_DW_SET(wave_gen, set)); +} + +static void ipu_di_sync_config(struct ipu_di *di, struct di_sync_config *config, int count) +{ + u32 reg; + int i; + + for (i = 0; i < count; i++) { + struct di_sync_config *c = &config[i]; + int wave_gen = i + 1; + + pr_debug("%s %d\n", __func__, wave_gen); + if ((c->run_count >= 0x1000) || (c->offset_count >= 0x1000) || (c->repeat_count >= 0x1000) || + (c->cnt_up >= 0x400) || (c->cnt_down >= 0x400)) { + dev_err(ipu_dev, "DI%d counters out of range.\n", di->id); + return; + } + + reg = DI_SW_GEN0_RUN_COUNT(c->run_count) | + DI_SW_GEN0_RUN_SRC(c->run_src) | + DI_SW_GEN0_OFFSET_COUNT(c->offset_count) | + DI_SW_GEN0_OFFSET_SRC(c->offset_src); + ipu_di_write(di, reg, DI_SW_GEN0(wave_gen)); + + reg = DI_SW_GEN1_CNT_POL_GEN_EN(c->cnt_polarity_gen_en) | + DI_SW_GEN1_CNT_CLR_SRC(c->cnt_clr_src) | + DI_SW_GEN1_CNT_POL_TRIGGER_SRC(c->cnt_polarity_trigger_src) | + DI_SW_GEN1_CNT_POL_CLR_SRC(c->cnt_polarity_clr_src) | + DI_SW_GEN1_CNT_DOWN(c->cnt_down) | + DI_SW_GEN1_CNT_UP(c->cnt_up); + + if (c->repeat_count == 0) { + /* Enable auto reload */ + reg |= DI_SW_GEN1_AUTO_RELOAD; + } + + ipu_di_write(di, reg, DI_SW_GEN1(wave_gen)); + + reg = ipu_di_read(di, DI_STP_REP(wave_gen)); + reg &= ~(0xffff << (16 * ((wave_gen - 1) & 0x1))); + reg |= c->repeat_count << (16 * ((wave_gen - 1) & 0x1)); + ipu_di_write(di, reg, DI_STP_REP(wave_gen)); + } +} + +static void ipu_di_sync_config_interlaced(struct ipu_di *di, struct ipu_di_signal_cfg *sig) +{ + u32 h_total = sig->width + sig->h_sync_width + sig->h_start_width + sig->h_end_width; + u32 v_total = sig->height + sig->v_sync_width + sig->v_start_width + sig->v_end_width; + u32 reg; + struct di_sync_config cfg[] = { + { + .run_count = h_total / 2 - 1, + .run_src = DI_SYNC_CLK, + }, { + .run_count = h_total - 11, + .run_src = DI_SYNC_CLK, + .cnt_down = 4, + }, { + .run_count = v_total * 2 - 1, + .run_src = DI_SYNC_INT_HSYNC, + .offset_count = 1, + .offset_src = DI_SYNC_INT_HSYNC, + .cnt_down = 4, + }, { + .run_count = v_total / 2 - 1, + .run_src = DI_SYNC_HSYNC, + .offset_count = sig->v_start_width, + .offset_src = DI_SYNC_HSYNC, + .repeat_count = 2, + .cnt_clr_src = DI_SYNC_VSYNC, + }, { + .run_src = DI_SYNC_HSYNC, + .repeat_count = sig->height / 2, + .cnt_clr_src = 4, + }, { + .run_count = v_total - 1, + .run_src = DI_SYNC_HSYNC, + }, { + .run_count = v_total / 2 - 1, + .run_src = DI_SYNC_HSYNC, + .offset_count = 9, + .offset_src = DI_SYNC_HSYNC, + .repeat_count = 2, + .cnt_clr_src = DI_SYNC_VSYNC, + }, { + .run_src = DI_SYNC_CLK, + .offset_count = sig->h_start_width, + .offset_src = DI_SYNC_CLK, + .repeat_count = sig->width, + .cnt_clr_src = 5, + }, { + .run_count = v_total - 1, + .run_src = DI_SYNC_INT_HSYNC, + .offset_count = v_total / 2, + .offset_src = DI_SYNC_INT_HSYNC, + .cnt_clr_src = DI_SYNC_HSYNC, + .cnt_down = 4, + } + }; + + ipu_di_sync_config(di, cfg, ARRAY_SIZE(cfg)); + + /* set gentime select and tag sel */ + reg = ipu_di_read(di, DI_SW_GEN1(9)); + reg &= 0x1FFFFFFF; + reg |= (3 - 1) << 29 | 0x00008000; + ipu_di_write(di, reg, DI_SW_GEN1(9)); + + ipu_di_write(di, v_total / 2 - 1, DI_SCR_CONF); +} + +static void ipu_di_sync_config_noninterlaced(struct ipu_di *di, + struct ipu_di_signal_cfg *sig, int div) +{ + u32 h_total = sig->width + sig->h_sync_width + sig->h_start_width + + sig->h_end_width; + u32 v_total = sig->height + sig->v_sync_width + sig->v_start_width + + sig->v_end_width + (sig->v_end_width < 2 ? 1 : 0); + struct di_sync_config cfg[] = { + { + .run_count = h_total - 1, + .run_src = DI_SYNC_CLK, + } , { + .run_count = h_total - 1, + .run_src = DI_SYNC_CLK, + .offset_count = div * sig->v_to_h_sync, + .offset_src = DI_SYNC_CLK, + .cnt_polarity_gen_en = 1, + .cnt_polarity_trigger_src = DI_SYNC_CLK, + .cnt_down = sig->h_sync_width * 2, + } , { + .run_count = v_total - 1, + .run_src = DI_SYNC_INT_HSYNC, + .cnt_polarity_gen_en = 1, + .cnt_polarity_trigger_src = DI_SYNC_INT_HSYNC, + .cnt_down = sig->v_sync_width * 2, + } , { + .run_src = DI_SYNC_HSYNC, + .offset_count = sig->v_sync_width + sig->v_start_width, + .offset_src = DI_SYNC_HSYNC, + .repeat_count = sig->height, + .cnt_clr_src = DI_SYNC_VSYNC, + } , { + .run_src = DI_SYNC_CLK, + .offset_count = sig->h_sync_width + sig->h_start_width, + .offset_src = DI_SYNC_CLK, + .repeat_count = sig->width, + .cnt_clr_src = 5, + } , { + /* unused */ + } , { + /* unused */ + } , { + /* unused */ + } , { + /* unused */ + }, + }; + + ipu_di_write(di, v_total - 1, DI_SCR_CONF); + ipu_di_sync_config(di, cfg, ARRAY_SIZE(cfg)); +} + +int ipu_di_init_sync_panel(struct ipu_di *di, u32 pixel_clk, struct ipu_di_signal_cfg *sig) +{ + u32 reg; + u32 disp_gen, di_gen, vsync_cnt; + u32 div; + u32 h_total, v_total; + struct clk *di_clk; + + dev_dbg(ipu_dev, "disp %d: panel size = %d x %d\n", + di->id, sig->width, sig->height); + + if ((sig->v_sync_width == 0) || (sig->h_sync_width == 0)) + return -EINVAL; + + h_total = sig->width + sig->h_sync_width + sig->h_start_width + sig->h_end_width; + v_total = sig->height + sig->v_sync_width + sig->v_start_width + sig->v_end_width; + + mutex_lock(&di_mutex); + ipu_get(); + + /* Init clocking */ + if (sig->ext_clk) { + di->external_clk = true; + di_clk = di->clk; + } else { + di->external_clk = false; + di_clk = di->ipu_clk; + } + + /* + * Calculate divider + * Fractional part is 4 bits, + * so simply multiply by 2^4 to get fractional part. + */ + div = (clk_get_rate(di_clk) * 16) / pixel_clk; + if (div < 0x10) /* Min DI disp clock divider is 1 */ + div = 0x10; + + disp_gen = ipu_cm_read(IPU_DISP_GEN); + disp_gen &= di->id ? ~DI1_COUNTER_RELEASE : ~DI0_COUNTER_RELEASE; + ipu_cm_write(disp_gen, IPU_DISP_GEN); + + ipu_di_write(di, div, DI_BS_CLKGEN0); + + /* Setup pixel clock timing */ + /* Down time is half of period */ + ipu_di_write(di, (div / 16) << 16, DI_BS_CLKGEN1); + + ipu_di_data_wave_config(di, SYNC_WAVE, div / 16 - 1, div / 16 - 1); + ipu_di_data_pin_config(di, SYNC_WAVE, DI_PIN15, 3, 0, div / 16 * 2); + + div = div / 16; /* Now divider is integer portion */ + + di_gen = 0; + if (sig->ext_clk) + di_gen |= DI_GEN_DI_CLK_EXT; + + if (sig->interlaced) { + ipu_di_sync_config_interlaced(di, sig); + + /* set y_sel = 1 */ + di_gen |= 0x10000000; + di_gen |= DI_GEN_POLARITY_5; + di_gen |= DI_GEN_POLARITY_8; + + vsync_cnt = 7; + + if (sig->Hsync_pol) + di_gen |= DI_GEN_POLARITY_3; + if (sig->Vsync_pol) + di_gen |= DI_GEN_POLARITY_2; + } else { + ipu_di_sync_config_noninterlaced(di, sig, div); + + vsync_cnt = 3; + + if (sig->Hsync_pol) + di_gen |= DI_GEN_POLARITY_2; + if (sig->Vsync_pol) + di_gen |= DI_GEN_POLARITY_3; + } + + ipu_di_write(di, di_gen, DI_GENERAL); + ipu_di_write(di, (--vsync_cnt << DI_VSYNC_SEL_OFFSET) | 0x00000002, + DI_SYNC_AS_GEN); + + reg = ipu_di_read(di, DI_POL); + reg &= ~(DI_POL_DRDY_DATA_POLARITY | DI_POL_DRDY_POLARITY_15); + + if (sig->enable_pol) + reg |= DI_POL_DRDY_POLARITY_15; + if (sig->data_pol) + reg |= DI_POL_DRDY_DATA_POLARITY; + + ipu_di_write(di, reg, DI_POL); + + ipu_put(); + mutex_unlock(&di_mutex); + + return 0; +} +EXPORT_SYMBOL(ipu_di_init_sync_panel); + +int ipu_di_enable(struct ipu_di *di) +{ + u32 reg; + + ipu_get(); + reg = ipu_cm_read(IPU_DISP_GEN); + if (di->id) + reg |= DI1_COUNTER_RELEASE; + else + reg |= DI0_COUNTER_RELEASE; + ipu_cm_write(reg, IPU_DISP_GEN); + + if (di->external_clk) + clk_enable(di->clk); + + ipu_module_enable(di->module); + + return 0; +} +EXPORT_SYMBOL(ipu_di_enable); + +int ipu_di_disable(struct ipu_di *di) +{ + u32 reg; + + ipu_module_disable(di->module); + ipu_put(); + + if (di->external_clk) + clk_disable(di->clk); + + reg = ipu_cm_read(IPU_DISP_GEN); + if (di->id) + reg &= ~DI1_COUNTER_RELEASE; + else + reg &= ~DI0_COUNTER_RELEASE; + ipu_cm_write(reg, IPU_DISP_GEN); + + return 0; +} +EXPORT_SYMBOL(ipu_di_disable); + +static DEFINE_MUTEX(ipu_di_lock); + +struct ipu_di *ipu_di_get(int disp) +{ + struct ipu_di *di; + + if (disp > 1) + return ERR_PTR(-EINVAL); + + di = &dis[disp]; + + mutex_lock(&ipu_di_lock); + + if (!di->initialized) { + di = ERR_PTR(-ENOSYS); + goto out; + } + + if (di->inuse) { + di = ERR_PTR(-EBUSY); + goto out; + } + + di->inuse = true; +out: + mutex_unlock(&ipu_di_lock); + + return di; +} +EXPORT_SYMBOL(ipu_di_get); + +void ipu_di_put(struct ipu_di *di) +{ + mutex_lock(&ipu_di_lock); + + di->inuse = false; + + mutex_unlock(&ipu_di_lock); +} +EXPORT_SYMBOL(ipu_di_put); + +int ipu_di_init(struct platform_device *pdev, int id, unsigned long base, + u32 module, struct clk *ipu_clk) +{ + char *clkid; + + if (id > 1) + return -EINVAL; + + if (id) + clkid = "di1"; + else + clkid = "di0"; + + ipu_dev = &pdev->dev; + + dis[id].clk = clk_get(&pdev->dev, clkid); + dis[id].module = module; + dis[id].id = id; + dis[id].ipu_clk = ipu_clk; + dis[id].base = ioremap(base, PAGE_SIZE); + dis[id].initialized = true; + dis[id].inuse = false; + if (!dis[id].base) + return -ENOMEM; + + /* Set MCU_T to divide MCU access window into 2 */ + ipu_cm_write(0x00400000L | (IPU_MCU_T_DEFAULT << 18), IPU_DISP_GEN); + + return 0; +} + +void ipu_di_exit(struct platform_device *pdev, int id) +{ + clk_put(dis[id].clk); + iounmap(dis[id].base); + dis[id].initialized = false; +} diff --git a/drivers/video/imx-ipu-v3/ipu-dmfc.c b/drivers/video/imx-ipu-v3/ipu-dmfc.c new file mode 100644 index 0000000..3d47a76 --- /dev/null +++ b/drivers/video/imx-ipu-v3/ipu-dmfc.c @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2010 Sascha Hauer + * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * 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 "ipu-prv.h" + +#define DMFC_RD_CHAN 0x0000 +#define DMFC_WR_CHAN 0x0004 +#define DMFC_WR_CHAN_DEF 0x0008 +#define DMFC_DP_CHAN 0x000c +#define DMFC_DP_CHAN_DEF 0x0010 +#define DMFC_GENERAL1 0x0014 +#define DMFC_GENERAL2 0x0018 +#define DMFC_IC_CTRL 0x001c +#define DMFC_STAT 0x0020 + +#define DMFC_WR_CHAN_1_28 0 +#define DMFC_WR_CHAN_2_41 8 +#define DMFC_WR_CHAN_1C_42 16 +#define DMFC_WR_CHAN_2C_43 24 + +#define DMFC_DP_CHAN_5B_23 0 +#define DMFC_DP_CHAN_5F_27 8 +#define DMFC_DP_CHAN_6B_24 16 +#define DMFC_DP_CHAN_6F_29 24 + +#define DMFC_FIFO_SIZE_64 (3 << 3) +#define DMFC_FIFO_SIZE_128 (2 << 3) +#define DMFC_FIFO_SIZE_256 (1 << 3) +#define DMFC_FIFO_SIZE_512 (0 << 3) + +#define DMFC_SEGMENT(x) ((x & 0x7) << 0) +#define DMFC_BURSTSIZE_32 (0 << 6) +#define DMFC_BURSTSIZE_16 (1 << 6) +#define DMFC_BURSTSIZE_8 (2 << 6) +#define DMFC_BURSTSIZE_4 (3 << 6) + +static struct device *ipu_dev; + +struct dmfc_channel { + int ipu_channel; + unsigned long channel_reg; + unsigned long shift; + unsigned eot_shift; + unsigned slots; + unsigned max_fifo_lines; + unsigned slotmask; + unsigned segment; +}; + +static struct dmfc_channel dmfcs[] = { + { + .ipu_channel = 23, + .channel_reg = DMFC_DP_CHAN, + .shift = DMFC_DP_CHAN_5B_23, + .eot_shift = 20, + .max_fifo_lines = 3, + }, { + .ipu_channel = 24, + .channel_reg = DMFC_DP_CHAN, + .shift = DMFC_DP_CHAN_6B_24, + .eot_shift = 22, + .max_fifo_lines = 1, + }, { + .ipu_channel = 27, + .channel_reg = DMFC_DP_CHAN, + .shift = DMFC_DP_CHAN_5F_27, + .eot_shift = 21, + .max_fifo_lines = 2, + }, { + .ipu_channel = 28, + .channel_reg = DMFC_WR_CHAN, + .shift = DMFC_WR_CHAN_1_28, + .eot_shift = 16, + .max_fifo_lines = 2, + }, { + .ipu_channel = 29, + .channel_reg = DMFC_DP_CHAN, + .shift = DMFC_DP_CHAN_6F_29, + .eot_shift = 23, + .max_fifo_lines = 1, + }, +}; + +#define DMFC_NUM_CHANNELS ARRAY_SIZE(dmfcs) + +static int dmfc_use_count; +static void __iomem *dmfc_regs; +static unsigned long dmfc_bandwidth_per_slot; +static DEFINE_MUTEX(dmfc_mutex); + +int ipu_dmfc_enable_channel(struct dmfc_channel *dmfc) +{ + mutex_lock(&dmfc_mutex); + ipu_get(); + + if (!dmfc_use_count) + ipu_module_enable(IPU_CONF_DMFC_EN); + + dmfc_use_count++; + + mutex_unlock(&dmfc_mutex); + + return 0; +} +EXPORT_SYMBOL(ipu_dmfc_enable_channel); + +void ipu_dmfc_disable_channel(struct dmfc_channel *dmfc) +{ + mutex_lock(&dmfc_mutex); + + dmfc_use_count--; + + if (!dmfc_use_count) + ipu_module_disable(IPU_CONF_DMFC_EN); + + if (dmfc_use_count < 0) + dmfc_use_count = 0; + + ipu_put(); + mutex_unlock(&dmfc_mutex); +} +EXPORT_SYMBOL(ipu_dmfc_disable_channel); + +static int ipu_dmfc_setup_channel(struct dmfc_channel *dmfc, int slots, int segment) +{ + u32 val, field; + + dev_dbg(ipu_dev, "dmfc: using %d slots starting from segment %d for IPU channel %d\n", + slots, segment, dmfc->ipu_channel); + + if (!dmfc) + return -EINVAL; + + switch (slots) { + case 1: + field = DMFC_FIFO_SIZE_64; + break; + case 2: + field = DMFC_FIFO_SIZE_128; + break; + case 4: + field = DMFC_FIFO_SIZE_256; + break; + case 8: + field = DMFC_FIFO_SIZE_512; + break; + default: + return -EINVAL; + } + + field |= DMFC_SEGMENT(segment) | DMFC_BURSTSIZE_8; + + val = readl(dmfc_regs + dmfc->channel_reg); + + val &= ~(0xff << dmfc->shift); + val |= field << dmfc->shift; + + writel(val, dmfc_regs + dmfc->channel_reg); + + dmfc->slots = slots; + dmfc->segment = segment; + dmfc->slotmask = ((1 << slots) - 1) << segment; + + return 0; +} + +static int dmfc_bandwidth_to_slots(unsigned long bandwidth) +{ + int slots = 1; + + while (slots * dmfc_bandwidth_per_slot < bandwidth) + slots *= 2; + + return slots; +} + +static int dmfc_find_slots(int slots) +{ + unsigned slotmask_need, slotmask_used = 0; + int i, segment = 0; + + slotmask_need = (1 << slots) - 1; + + for (i = 0; i < DMFC_NUM_CHANNELS; i++) + slotmask_used |= dmfcs[i].slotmask; + + while (slotmask_need <= 0xff) { + if (!(slotmask_used & slotmask_need)) + return segment; + + slotmask_need <<= 1; + segment++; + } + + return -EBUSY; +} + +void ipu_dmfc_free_bandwidth(struct dmfc_channel *dmfc) +{ + int i; + + dev_dbg(ipu_dev, "dmfc: freeing %d slots starting from segment %d\n", + dmfc->slots, dmfc->segment); + + mutex_lock(&dmfc_mutex); + + if (!dmfc->slots) + goto out; + + dmfc->slotmask = 0; + dmfc->slots = 0; + dmfc->segment = 0; + + for (i = 0; i < ARRAY_SIZE(dmfcs); i++) + dmfcs[i].slotmask = 0; + + for (i = 0; i < ARRAY_SIZE(dmfcs); i++) { + if (dmfcs[i].slots > 0) { + dmfcs[i].segment = dmfc_find_slots(dmfcs[i].slots); + dmfcs[i].slotmask = ((1 << dmfcs[i].slots) - 1) << dmfcs[i].segment; + } + } + + for (i = 0; i < ARRAY_SIZE(dmfcs); i++) { + if (dmfcs[i].slots > 0) + ipu_dmfc_setup_channel(&dmfcs[i], dmfcs[i].slots, dmfcs[i].segment); + } +out: + mutex_unlock(&dmfc_mutex); +} +EXPORT_SYMBOL(ipu_dmfc_free_bandwidth); + +int ipu_dmfc_alloc_bandwidth(struct dmfc_channel *dmfc, + unsigned long bandwidth_pixel_per_second) +{ + int slots = dmfc_bandwidth_to_slots(bandwidth_pixel_per_second); + int segment = 0, ret = 0; + + dev_dbg(ipu_dev, "dmfc: trying to allocate %ldMpixel/s for IPU channel %d\n", + bandwidth_pixel_per_second / 1000000, dmfc->ipu_channel); + + ipu_dmfc_free_bandwidth(dmfc); + + ipu_get(); + mutex_lock(&dmfc_mutex); + + if (slots > 8) { + ret = -EBUSY; + goto out; + } + + segment = dmfc_find_slots(slots); + if (segment < 0) { + ret = -EBUSY; + goto out; + } + + ipu_dmfc_setup_channel(dmfc, slots, segment); + +out: + ipu_put(); + mutex_unlock(&dmfc_mutex); + + return ret; +} +EXPORT_SYMBOL(ipu_dmfc_alloc_bandwidth); + +int ipu_dmfc_init_channel(struct dmfc_channel *dmfc, int width) +{ + u32 dmfc_gen1; + + ipu_get(); + + dmfc_gen1 = readl(dmfc_regs + DMFC_GENERAL1); + + if ((dmfc->slots * 64 * 4) / width > dmfc->max_fifo_lines) + dmfc_gen1 |= 1 << dmfc->eot_shift; + else + dmfc_gen1 &= ~(1 << dmfc->eot_shift); + + writel(dmfc_gen1, dmfc_regs + DMFC_GENERAL1); + + ipu_put(); + + return 0; +} +EXPORT_SYMBOL(ipu_dmfc_init_channel); + +struct dmfc_channel *ipu_dmfc_get(int ipu_channel) +{ + int i; + + for (i = 0; i < DMFC_NUM_CHANNELS; i++) + if (dmfcs[i].ipu_channel == ipu_channel) + return &dmfcs[i]; + return NULL; +} +EXPORT_SYMBOL(ipu_dmfc_get); + +void ipu_dmfc_put(struct dmfc_channel *dmfc) +{ + ipu_dmfc_free_bandwidth(dmfc); +} +EXPORT_SYMBOL(ipu_dmfc_put); + +int ipu_dmfc_init(struct platform_device *pdev, unsigned long base, + struct clk *ipu_clk) +{ + dmfc_regs = ioremap(base, PAGE_SIZE); + + if (!dmfc_regs) + return -ENOMEM; + + ipu_dev = &pdev->dev; + + writel(0x0, dmfc_regs + DMFC_WR_CHAN); + writel(0x0, dmfc_regs + DMFC_DP_CHAN); + + /* + * We have a total bandwidth of clkrate * 4pixel divided + * into 8 slots. + */ + dmfc_bandwidth_per_slot = clk_get_rate(ipu_clk) / 4; + + dev_dbg(ipu_dev, "dmfc: 8 slots with %ldMpixel/s bandwidth each\n", + dmfc_bandwidth_per_slot / 1000000); + + writel(0x202020f6, dmfc_regs + DMFC_WR_CHAN_DEF); + writel(0x2020f6f6, dmfc_regs + DMFC_DP_CHAN_DEF); + writel(0x00000003, dmfc_regs + DMFC_GENERAL1); + + return 0; +} + +void ipu_dmfc_exit(struct platform_device *pdev) +{ + iounmap(dmfc_regs); +} diff --git a/drivers/video/imx-ipu-v3/ipu-dp.c b/drivers/video/imx-ipu-v3/ipu-dp.c new file mode 100644 index 0000000..57a7fec --- /dev/null +++ b/drivers/video/imx-ipu-v3/ipu-dp.c @@ -0,0 +1,476 @@ +/* + * Copyright (c) 2010 Sascha Hauer + * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * 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 "ipu-prv.h" + +#define DP_SYNC 0 +#define DP_ASYNC0 0x60 +#define DP_ASYNC1 0xBC + +#define DP_COM_CONF(flow) (ipu_dp_base + flow) +#define DP_GRAPH_WIND_CTRL(flow) (ipu_dp_base + 0x0004 + flow) +#define DP_FG_POS(flow) (ipu_dp_base + 0x0008 + flow) +#define DP_CSC_A_0(flow) (ipu_dp_base + 0x0044 + flow) +#define DP_CSC_A_1(flow) (ipu_dp_base + 0x0048 + flow) +#define DP_CSC_A_2(flow) (ipu_dp_base + 0x004C + flow) +#define DP_CSC_A_3(flow) (ipu_dp_base + 0x0050 + flow) +#define DP_CSC_0(flow) (ipu_dp_base + 0x0054 + flow) +#define DP_CSC_1(flow) (ipu_dp_base + 0x0058 + flow) + +#define DP_COM_CONF_FG_EN (1 << 0) +#define DP_COM_CONF_GWSEL (1 << 1) +#define DP_COM_CONF_GWAM (1 << 2) +#define DP_COM_CONF_GWCKE (1 << 3) +#define DP_COM_CONF_CSC_DEF_MASK (3 << 8) +#define DP_COM_CONF_CSC_DEF_OFFSET 8 +#define DP_COM_CONF_CSC_DEF_FG (3 << 8) +#define DP_COM_CONF_CSC_DEF_BG (2 << 8) +#define DP_COM_CONF_CSC_DEF_BOTH (1 << 8) + +struct ipu_dp { + u32 flow; + bool in_use; +}; + +static struct ipu_dp ipu_dp[3]; +static struct device *ipu_dev; + +static u32 ipu_flows[] = {DP_SYNC, DP_ASYNC0, DP_ASYNC1}; + +enum csc_type_t { + RGB2YUV = 0, + YUV2RGB, + RGB2RGB, + YUV2YUV, + CSC_NONE, + CSC_NUM +}; + +static void __iomem *ipu_dp_base; +static int dp_use_count; +static DEFINE_MUTEX(dp_mutex); + +/* Y = R * .299 + G * .587 + B * .114; + U = R * -.169 + G * -.332 + B * .500 + 128.; + V = R * .500 + G * -.419 + B * -.0813 + 128.;*/ +static const int rgb2ycbcr_coeff[5][3] = { + { 153, 301, 58, }, + { -87, -170, 0x0100, }, + { 0x100, -215, -42, }, + { 0x0000, 0x0200, 0x0200, }, /* B0, B1, B2 */ + { 0x2, 0x2, 0x2, }, /* S0, S1, S2 */ +}; + +/* R = (1.164 * (Y - 16)) + (1.596 * (Cr - 128)); + G = (1.164 * (Y - 16)) - (0.392 * (Cb - 128)) - (0.813 * (Cr - 128)); + B = (1.164 * (Y - 16)) + (2.017 * (Cb - 128); */ +static const int ycbcr2rgb_coeff[5][3] = { + { 0x095, 0x000, 0x0CC, }, + { 0x095, 0x3CE, 0x398, }, + { 0x095, 0x0FF, 0x000, }, + { 0x3E42, 0x010A, 0x3DD6, }, /*B0,B1,B2 */ + { 0x1, 0x1, 0x1, }, /*S0,S1,S2 */ +}; + +/* Please keep S0, S1 and S2 as 0x2 by using this conversion */ +static int _rgb_to_yuv(int n, int red, int green, int blue) +{ + int c; + + c = red * rgb2ycbcr_coeff[n][0]; + c += green * rgb2ycbcr_coeff[n][1]; + c += blue * rgb2ycbcr_coeff[n][2]; + c /= 16; + c += rgb2ycbcr_coeff[3][n] * 4; + c += 8; + c /= 16; + if (c < 0) + c = 0; + if (c > 255) + c = 255; + return c; +} + +struct dp_csc_param_t { + int mode; + void *coeff; +}; + +/* + * Row is for BG: RGB2YUV YUV2RGB RGB2RGB YUV2YUV CSC_NONE + * Column is for FG: RGB2YUV YUV2RGB RGB2RGB YUV2YUV CSC_NONE + */ +static struct dp_csc_param_t dp_csc_array[CSC_NUM][CSC_NUM] = { + { + { DP_COM_CONF_CSC_DEF_BOTH, &rgb2ycbcr_coeff, }, + { 0, 0, }, + { 0, 0, }, + { DP_COM_CONF_CSC_DEF_BG, &rgb2ycbcr_coeff, }, + { DP_COM_CONF_CSC_DEF_BG, &rgb2ycbcr_coeff, }, + }, { + { 0, 0, }, + { DP_COM_CONF_CSC_DEF_BOTH, &ycbcr2rgb_coeff, }, + { DP_COM_CONF_CSC_DEF_BG, &ycbcr2rgb_coeff, }, + { 0, 0, }, + { DP_COM_CONF_CSC_DEF_BG, &ycbcr2rgb_coeff, }, + }, { + { 0, 0, }, + { DP_COM_CONF_CSC_DEF_FG, &ycbcr2rgb_coeff, }, + { 0, 0, }, + { 0, 0, }, + { 0, 0, }, + }, { + { DP_COM_CONF_CSC_DEF_FG, &rgb2ycbcr_coeff, }, + { 0, 0, }, + { 0, 0, }, + { 0, 0, }, + { 0, 0, }, + }, { + { DP_COM_CONF_CSC_DEF_FG, &rgb2ycbcr_coeff, }, + { DP_COM_CONF_CSC_DEF_FG, &ycbcr2rgb_coeff, }, + { 0, 0, }, + { 0, 0, }, + { 0, 0, }, + } +}; + +static enum csc_type_t fg_csc_type = CSC_NONE, bg_csc_type = CSC_NONE; + +static int color_key_4rgb = 1; + +int ipu_dp_set_color_key(struct ipu_dp *dp, bool enable, u32 color_key) +{ + u32 reg; + int y, u, v; + int red, green, blue; + + mutex_lock(&dp_mutex); + + color_key_4rgb = 1; + /* Transform color key from rgb to yuv if CSC is enabled */ + if (((fg_csc_type == RGB2YUV) && (bg_csc_type == YUV2YUV)) || + ((fg_csc_type == YUV2YUV) && (bg_csc_type == RGB2YUV)) || + ((fg_csc_type == YUV2YUV) && (bg_csc_type == YUV2YUV)) || + ((fg_csc_type == YUV2RGB) && (bg_csc_type == YUV2RGB))) { + + dev_dbg(ipu_dev, "color key 0x%x need change to yuv fmt\n", color_key); + + red = (color_key >> 16) & 0xFF; + green = (color_key >> 8) & 0xFF; + blue = color_key & 0xFF; + + y = _rgb_to_yuv(0, red, green, blue); + u = _rgb_to_yuv(1, red, green, blue); + v = _rgb_to_yuv(2, red, green, blue); + color_key = (y << 16) | (u << 8) | v; + + color_key_4rgb = 0; + + dev_dbg(ipu_dev, "color key change to yuv fmt 0x%x\n", color_key); + } + + if (enable) { + reg = __raw_readl(DP_GRAPH_WIND_CTRL(dp->flow)) & 0xFF000000L; + __raw_writel(reg | color_key, DP_GRAPH_WIND_CTRL(dp->flow)); + + reg = __raw_readl(DP_COM_CONF(dp->flow)); + __raw_writel(reg | DP_COM_CONF_GWCKE, DP_COM_CONF(dp->flow)); + } else { + reg = __raw_readl(DP_COM_CONF(dp->flow)); + __raw_writel(reg & ~DP_COM_CONF_GWCKE, DP_COM_CONF(dp->flow)); + } + + reg = ipu_cm_read(IPU_SRM_PRI2) | 0x8; + ipu_cm_write(reg, IPU_SRM_PRI2); + + mutex_unlock(&dp_mutex); + + return 0; +} +EXPORT_SYMBOL(ipu_dp_set_color_key); + +int ipu_dp_set_global_alpha(struct ipu_dp *dp, bool enable, u8 alpha, bool bg_chan) +{ + u32 reg; + + mutex_lock(&dp_mutex); + + reg = __raw_readl(DP_COM_CONF(dp->flow)); + if (bg_chan) + reg &= ~DP_COM_CONF_GWSEL; + else + reg |= DP_COM_CONF_GWSEL; + __raw_writel(reg, DP_COM_CONF(dp->flow)); + + if (enable) { + reg = __raw_readl(DP_GRAPH_WIND_CTRL(dp->flow)) & 0x00FFFFFFL; + __raw_writel(reg | ((u32) alpha << 24), + DP_GRAPH_WIND_CTRL(dp->flow)); + + reg = __raw_readl(DP_COM_CONF(dp->flow)); + __raw_writel(reg | DP_COM_CONF_GWAM, DP_COM_CONF(dp->flow)); + } else { + reg = __raw_readl(DP_COM_CONF(dp->flow)); + __raw_writel(reg & ~DP_COM_CONF_GWAM, DP_COM_CONF(dp->flow)); + } + + reg = ipu_cm_read(IPU_SRM_PRI2) | 0x8; + ipu_cm_write(reg, IPU_SRM_PRI2); + + mutex_unlock(&dp_mutex); + + return 0; +} +EXPORT_SYMBOL(ipu_dp_set_global_alpha); + +int ipu_dp_set_window_pos(struct ipu_dp *dp, u16 x_pos, u16 y_pos) +{ + u32 reg; + + mutex_lock(&dp_mutex); + + __raw_writel((x_pos << 16) | y_pos, DP_FG_POS(dp->flow)); + + reg = ipu_cm_read(IPU_SRM_PRI2); + reg |= 0x8; + ipu_cm_write(reg, IPU_SRM_PRI2); + + mutex_unlock(&dp_mutex); + + return 0; +} +EXPORT_SYMBOL(ipu_dp_set_window_pos); + +#define mask_a(a) ((u32)(a) & 0x3FF) +#define mask_b(b) ((u32)(b) & 0x3FFF) + +void __ipu_dp_csc_setup(int dp, struct dp_csc_param_t dp_csc_param, + bool srm_mode_update) +{ + u32 reg; + const int (*coeff)[5][3]; + + if (dp_csc_param.mode >= 0) { + reg = __raw_readl(DP_COM_CONF(dp)); + reg &= ~DP_COM_CONF_CSC_DEF_MASK; + reg |= dp_csc_param.mode; + __raw_writel(reg, DP_COM_CONF(dp)); + } + + coeff = dp_csc_param.coeff; + + if (coeff) { + __raw_writel(mask_a((*coeff)[0][0]) | + (mask_a((*coeff)[0][1]) << 16), DP_CSC_A_0(dp)); + __raw_writel(mask_a((*coeff)[0][2]) | + (mask_a((*coeff)[1][0]) << 16), DP_CSC_A_1(dp)); + __raw_writel(mask_a((*coeff)[1][1]) | + (mask_a((*coeff)[1][2]) << 16), DP_CSC_A_2(dp)); + __raw_writel(mask_a((*coeff)[2][0]) | + (mask_a((*coeff)[2][1]) << 16), DP_CSC_A_3(dp)); + __raw_writel(mask_a((*coeff)[2][2]) | + (mask_b((*coeff)[3][0]) << 16) | + ((*coeff)[4][0] << 30), DP_CSC_0(dp)); + __raw_writel(mask_b((*coeff)[3][1]) | ((*coeff)[4][1] << 14) | + (mask_b((*coeff)[3][2]) << 16) | + ((*coeff)[4][2] << 30), DP_CSC_1(dp)); + } + + if (srm_mode_update) { + reg = ipu_cm_read(IPU_SRM_PRI2) | 0x8; + ipu_cm_write(reg, IPU_SRM_PRI2); + } +} + +int ipu_dp_setup_channel(struct ipu_dp *dp, u32 in_pixel_fmt, + u32 out_pixel_fmt, int bg) +{ + int in_fmt, out_fmt; + enum csc_type_t *csc_type; + u32 reg; + + ipu_get(); + + if (bg) + csc_type = &bg_csc_type; + else + csc_type = &fg_csc_type; + + in_fmt = format_to_colorspace(in_pixel_fmt); + out_fmt = format_to_colorspace(out_pixel_fmt); + + if (in_fmt == RGB) { + if (out_fmt == RGB) + *csc_type = RGB2RGB; + else + *csc_type = RGB2YUV; + } else { + if (out_fmt == RGB) + *csc_type = YUV2RGB; + else + *csc_type = YUV2YUV; + } + + /* Transform color key from rgb to yuv if CSC is enabled */ + reg = __raw_readl(DP_COM_CONF(dp->flow)); + if (color_key_4rgb && (reg & DP_COM_CONF_GWCKE) && + (((fg_csc_type == RGB2YUV) && (bg_csc_type == YUV2YUV)) || + ((fg_csc_type == YUV2YUV) && (bg_csc_type == RGB2YUV)) || + ((fg_csc_type == YUV2YUV) && (bg_csc_type == YUV2YUV)) || + ((fg_csc_type == YUV2RGB) && (bg_csc_type == YUV2RGB)))) { + int red, green, blue; + int y, u, v; + u32 color_key = __raw_readl(DP_GRAPH_WIND_CTRL(dp->flow)) & 0xFFFFFFL; + + dev_dbg(ipu_dev, "_ipu_dp_init color key 0x%x need change to yuv fmt!\n", color_key); + + red = (color_key >> 16) & 0xFF; + green = (color_key >> 8) & 0xFF; + blue = color_key & 0xFF; + + y = _rgb_to_yuv(0, red, green, blue); + u = _rgb_to_yuv(1, red, green, blue); + v = _rgb_to_yuv(2, red, green, blue); + color_key = (y << 16) | (u << 8) | v; + + reg = __raw_readl(DP_GRAPH_WIND_CTRL(dp->flow)) & 0xFF000000L; + __raw_writel(reg | color_key, DP_GRAPH_WIND_CTRL(dp->flow)); + color_key_4rgb = 0; + + dev_dbg(ipu_dev, "_ipu_dp_init color key change to yuv fmt 0x%x!\n", color_key); + } + + __ipu_dp_csc_setup(dp->flow, dp_csc_array[bg_csc_type][fg_csc_type], true); + + ipu_put(); + + return 0; +} +EXPORT_SYMBOL(ipu_dp_setup_channel); + +int ipu_dp_enable_channel(struct ipu_dp *dp) +{ + mutex_lock(&dp_mutex); + ipu_get(); + + if (!dp_use_count) + ipu_module_enable(IPU_CONF_DP_EN); + + dp_use_count++; + + mutex_unlock(&dp_mutex); + + return 0; +} +EXPORT_SYMBOL(ipu_dp_enable_channel); + +void ipu_dp_disable_channel(struct ipu_dp *dp) +{ + mutex_lock(&dp_mutex); + + dp_use_count--; + + if (!dp_use_count) + ipu_module_disable(IPU_CONF_DP_EN); + + if (dp_use_count < 0) + dp_use_count = 0; + + ipu_put(); + mutex_unlock(&dp_mutex); +} +EXPORT_SYMBOL(ipu_dp_disable_channel); + +void ipu_dp_enable_fg(struct ipu_dp *dp) +{ + u32 reg; + + /* Enable FG channel */ + reg = __raw_readl(DP_COM_CONF(DP_SYNC)); + __raw_writel(reg | DP_COM_CONF_FG_EN, DP_COM_CONF(DP_SYNC)); + + reg = ipu_cm_read(IPU_SRM_PRI2); + reg |= 0x8; + ipu_cm_write(reg, IPU_SRM_PRI2); +} +EXPORT_SYMBOL(ipu_dp_enable_fg); + +void ipu_dp_disable_fg(struct ipu_dp *dp) +{ + u32 reg, csc; + + ipu_get(); + + reg = __raw_readl(DP_COM_CONF(DP_SYNC)); + csc = reg & DP_COM_CONF_CSC_DEF_MASK; + if (csc == DP_COM_CONF_CSC_DEF_FG) + reg &= ~DP_COM_CONF_CSC_DEF_MASK; + + reg &= ~DP_COM_CONF_FG_EN; + __raw_writel(reg, DP_COM_CONF(DP_SYNC)); + + reg = ipu_cm_read(IPU_SRM_PRI2) | 0x8; + ipu_cm_write(reg, IPU_SRM_PRI2); + + ipu_put(); +} +EXPORT_SYMBOL(ipu_dp_disable_fg); + +struct ipu_dp *ipu_dp_get(unsigned int flow) +{ + struct ipu_dp *dp; + + if (flow > 2) + return ERR_PTR(-EINVAL); + + dp = &ipu_dp[flow]; + + if (dp->in_use) + return ERR_PTR(-EBUSY); + + dp->in_use = true; + dp->flow = ipu_flows[flow]; + + return dp; +} +EXPORT_SYMBOL(ipu_dp_get); + +void ipu_dp_put(struct ipu_dp *dp) +{ + dp->in_use = false; +} +EXPORT_SYMBOL(ipu_dp_put); + +int ipu_dp_init(struct platform_device *pdev, unsigned long base) +{ + ipu_dp_base = ioremap(base, PAGE_SIZE); + if (!ipu_dp_base) + return -ENOMEM; + + ipu_dev = &pdev->dev; + + return 0; +} + +void ipu_dp_exit(struct platform_device *pdev) +{ + iounmap(ipu_dp_base); +} diff --git a/drivers/video/imx-ipu-v3/ipu-prv.h b/drivers/video/imx-ipu-v3/ipu-prv.h new file mode 100644 index 0000000..339b554 --- /dev/null +++ b/drivers/video/imx-ipu-v3/ipu-prv.h @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2010 Sascha Hauer + * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ +#ifndef __IPU_PRV_H__ +#define __IPU_PRV_H__ + +#include +#include +#include +#include +#include + +#define MX51_IPU_CHANNEL_CSI0 0 +#define MX51_IPU_CHANNEL_CSI1 1 +#define MX51_IPU_CHANNEL_CSI2 2 +#define MX51_IPU_CHANNEL_CSI3 3 +#define MX51_IPU_CHANNEL_MEM_BG_SYNC 23 +#define MX51_IPU_CHANNEL_MEM_FG_SYNC 27 +#define MX51_IPU_CHANNEL_MEM_DC_SYNC 28 +#define MX51_IPU_CHANNEL_MEM_FG_SYNC_ALPHA 31 +#define MX51_IPU_CHANNEL_MEM_DC_ASYNC 41 +#define MX51_IPU_CHANNEL_ROT_ENC_MEM 45 +#define MX51_IPU_CHANNEL_ROT_VF_MEM 46 +#define MX51_IPU_CHANNEL_ROT_PP_MEM 47 +#define MX51_IPU_CHANNEL_ROT_ENC_MEM_OUT 48 +#define MX51_IPU_CHANNEL_ROT_VF_MEM_OUT 49 +#define MX51_IPU_CHANNEL_ROT_PP_MEM_OUT 50 +#define MX51_IPU_CHANNEL_MEM_BG_SYNC_ALPHA 51 + +#define IPU_DISP0_BASE 0x00000000 +#define IPU_MCU_T_DEFAULT 8 +#define IPU_DISP1_BASE (IPU_MCU_T_DEFAULT << 25) +#define IPU_CM_REG_BASE 0x1e000000 +#define IPU_IDMAC_REG_BASE 0x1e008000 +#define IPU_ISP_REG_BASE 0x1e010000 +#define IPU_DP_REG_BASE 0x1e018000 +#define IPU_IC_REG_BASE 0x1e020000 +#define IPU_IRT_REG_BASE 0x1e028000 +#define IPU_CSI0_REG_BASE 0x1e030000 +#define IPU_CSI1_REG_BASE 0x1e038000 +#define IPU_DI0_REG_BASE 0x1e040000 +#define IPU_DI1_REG_BASE 0x1e048000 +#define IPU_SMFC_REG_BASE 0x1e050000 +#define IPU_DC_REG_BASE 0x1e058000 +#define IPU_DMFC_REG_BASE 0x1e060000 +#define IPU_CPMEM_REG_BASE 0x1f000000 +#define IPU_LUT_REG_BASE 0x1f020000 +#define IPU_SRM_REG_BASE 0x1f040000 +#define IPU_TPM_REG_BASE 0x1f060000 +#define IPU_DC_TMPL_REG_BASE 0x1f080000 +#define IPU_ISP_TBPR_REG_BASE 0x1f0c0000 +#define IPU_VDI_REG_BASE 0x1e068000 + +/* Register addresses */ +/* IPU Common registers */ +#define IPU_CM_REG(offset) (offset) + +#define IPU_CONF IPU_CM_REG(0) + +#define IPU_SRM_PRI1 IPU_CM_REG(0x00a0) +#define IPU_SRM_PRI2 IPU_CM_REG(0x00a4) +#define IPU_FS_PROC_FLOW1 IPU_CM_REG(0x00a8) +#define IPU_FS_PROC_FLOW2 IPU_CM_REG(0x00ac) +#define IPU_FS_PROC_FLOW3 IPU_CM_REG(0x00b0) +#define IPU_FS_DISP_FLOW1 IPU_CM_REG(0x00b4) +#define IPU_FS_DISP_FLOW2 IPU_CM_REG(0x00b8) +#define IPU_SKIP IPU_CM_REG(0x00bc) +#define IPU_DISP_ALT_CONF IPU_CM_REG(0x00c0) +#define IPU_DISP_GEN IPU_CM_REG(0x00c4) +#define IPU_DISP_ALT1 IPU_CM_REG(0x00c8) +#define IPU_DISP_ALT2 IPU_CM_REG(0x00cc) +#define IPU_DISP_ALT3 IPU_CM_REG(0x00d0) +#define IPU_DISP_ALT4 IPU_CM_REG(0x00d4) +#define IPU_SNOOP IPU_CM_REG(0x00d8) +#define IPU_MEM_RST IPU_CM_REG(0x00dc) +#define IPU_PM IPU_CM_REG(0x00e0) +#define IPU_GPR IPU_CM_REG(0x00e4) +#define IPU_CHA_DB_MODE_SEL(ch) IPU_CM_REG(0x0150 + 4 * ((ch) / 32)) +#define IPU_ALT_CHA_DB_MODE_SEL(ch) IPU_CM_REG(0x0168 + 4 * ((ch) / 32)) +#define IPU_CHA_CUR_BUF(ch) IPU_CM_REG(0x023C + 4 * ((ch) / 32)) +#define IPU_ALT_CUR_BUF0 IPU_CM_REG(0x0244) +#define IPU_ALT_CUR_BUF1 IPU_CM_REG(0x0248) +#define IPU_SRM_STAT IPU_CM_REG(0x024C) +#define IPU_PROC_TASK_STAT IPU_CM_REG(0x0250) +#define IPU_DISP_TASK_STAT IPU_CM_REG(0x0254) +#define IPU_CHA_BUF0_RDY(ch) IPU_CM_REG(0x0268 + 4 * ((ch) / 32)) +#define IPU_CHA_BUF1_RDY(ch) IPU_CM_REG(0x0270 + 4 * ((ch) / 32)) +#define IPU_ALT_CHA_BUF0_RDY(ch) IPU_CM_REG(0x0278 + 4 * ((ch) / 32)) +#define IPU_ALT_CHA_BUF1_RDY(ch) IPU_CM_REG(0x0280 + 4 * ((ch) / 32)) + +#define IPU_INT_CTRL(n) IPU_CM_REG(0x003C + 4 * ((n) - 1)) +#define IPU_INT_CTRL_IRQ(irq) IPU_INT_CTRL(((irq) / 32)) +#define IPU_INT_STAT_IRQ(irq) IPU_INT_STAT(((irq) / 32)) +#define IPU_INT_STAT(n) IPU_CM_REG(0x0200 + 4 * ((n) - 1)) + +extern void __iomem *ipu_cm_reg; + +static inline u32 ipu_cm_read(unsigned offset) +{ + return __raw_readl(ipu_cm_reg + offset); +} + +static inline void ipu_cm_write(u32 value, unsigned offset) +{ + __raw_writel(value, ipu_cm_reg + offset); +} + +#define IPU_IDMAC_REG(offset) (offset) + +#define IDMAC_CONF IPU_IDMAC_REG(0x0000) +#define IDMAC_CHA_EN(ch) IPU_IDMAC_REG(0x0004 + 4 * ((ch) / 32)) +#define IDMAC_SEP_ALPHA IPU_IDMAC_REG(0x000c) +#define IDMAC_ALT_SEP_ALPHA IPU_IDMAC_REG(0x0010) +#define IDMAC_CHA_PRI(ch) IPU_IDMAC_REG(0x0014 + 4 * ((ch) / 32)) +#define IDMAC_WM_EN(ch) IPU_IDMAC_REG(0x001c + 4 * ((ch) / 32)) +#define IDMAC_CH_LOCK_EN_1 IPU_IDMAC_REG(0x0024) +#define IDMAC_CH_LOCK_EN_2 IPU_IDMAC_REG(0x0028) +#define IDMAC_SUB_ADDR_0 IPU_IDMAC_REG(0x002c) +#define IDMAC_SUB_ADDR_1 IPU_IDMAC_REG(0x0030) +#define IDMAC_SUB_ADDR_2 IPU_IDMAC_REG(0x0034) +#define IDMAC_BAND_EN(ch) IPU_IDMAC_REG(0x0040 + 4 * ((ch) / 32)) +#define IDMAC_CHA_BUSY(ch) IPU_IDMAC_REG(0x0100 + 4 * ((ch) / 32)) + +extern void __iomem *ipu_idmac_reg; + +static inline u32 ipu_idmac_read(unsigned offset) +{ + return __raw_readl(ipu_idmac_reg + offset); +} + +static inline void ipu_idmac_write(u32 value, unsigned offset) +{ + __raw_writel(value, ipu_idmac_reg + offset); +} + +#define idmac_idma_is_set(reg, dma) (ipu_idmac_read(reg(dma)) & idma_mask(dma)) +#define idma_mask(ch) (1 << (ch & 0x1f)) +#define ipu_idma_is_set(reg, dma) (ipu_cm_read(reg(dma)) & idma_mask(dma)) + +enum ipu_modules { + IPU_CONF_CSI0_EN = (1 << 0), + IPU_CONF_CSI1_EN = (1 << 1), + IPU_CONF_IC_EN = (1 << 2), + IPU_CONF_ROT_EN = (1 << 3), + IPU_CONF_ISP_EN = (1 << 4), + IPU_CONF_DP_EN = (1 << 5), + IPU_CONF_DI0_EN = (1 << 6), + IPU_CONF_DI1_EN = (1 << 7), + IPU_CONF_SMFC_EN = (1 << 8), + IPU_CONF_DC_EN = (1 << 9), + IPU_CONF_DMFC_EN = (1 << 10), + + IPU_CONF_VDI_EN = (1 << 12), + + IPU_CONF_IDMAC_DIS = (1 << 22), + + IPU_CONF_IC_DMFC_SEL = (1 << 25), + IPU_CONF_IC_DMFC_SYNC = (1 << 26), + IPU_CONF_VDI_DMFC_SYNC = (1 << 27), + + IPU_CONF_CSI0_DATA_SOURCE = (1 << 28), + IPU_CONF_CSI1_DATA_SOURCE = (1 << 29), + IPU_CONF_IC_INPUT = (1 << 30), + IPU_CONF_CSI_SEL = (1 << 31), +}; + +struct ipu_channel { + unsigned int num; + + bool enabled; + bool busy; +}; + +ipu_color_space_t format_to_colorspace(u32 fmt); +bool ipu_pixel_format_has_alpha(u32 fmt); + +u32 _ipu_channel_status(struct ipu_channel *channel); + +int _ipu_chan_is_interlaced(struct ipu_channel *channel); + +int ipu_module_enable(u32 mask); +int ipu_module_disable(u32 mask); + +int ipu_di_init(struct platform_device *pdev, int id, unsigned long base, + u32 module, struct clk *ipu_clk); +void ipu_di_exit(struct platform_device *pdev, int id); + +int ipu_dmfc_init(struct platform_device *pdev, unsigned long base, + struct clk *ipu_clk); +void ipu_dmfc_exit(struct platform_device *pdev); + +int ipu_dp_init(struct platform_device *pdev, unsigned long base); +void ipu_dp_exit(struct platform_device *pdev); + +int ipu_dc_init(struct platform_device *pdev, unsigned long base, + unsigned long template_base); +void ipu_dc_exit(struct platform_device *pdev); + +int ipu_cpmem_init(struct platform_device *pdev, unsigned long base); +void ipu_cpmem_exit(struct platform_device *pdev); + +void ipu_get(void); +void ipu_put(void); + +#endif /* __IPU_PRV_H__ */ diff --git a/include/linux/mfd/imx-ipu-v3.h b/include/linux/mfd/imx-ipu-v3.h new file mode 100644 index 0000000..7243360 --- /dev/null +++ b/include/linux/mfd/imx-ipu-v3.h @@ -0,0 +1,219 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +#ifndef __ASM_ARCH_IPU_H__ +#define __ASM_ARCH_IPU_H__ + +#include +#include +#include + +/* + * Enumeration of IPU rotation modes + */ +typedef enum { + /* Note the enum values correspond to BAM value */ + IPU_ROTATE_NONE = 0, + IPU_ROTATE_VERT_FLIP = 1, + IPU_ROTATE_HORIZ_FLIP = 2, + IPU_ROTATE_180 = 3, + IPU_ROTATE_90_RIGHT = 4, + IPU_ROTATE_90_RIGHT_VFLIP = 5, + IPU_ROTATE_90_RIGHT_HFLIP = 6, + IPU_ROTATE_90_LEFT = 7, +} ipu_rotate_mode_t; + +/* + * IPU Pixel Formats + * + * Pixel formats are defined with ASCII FOURCC code. The pixel format codes are + * the same used by V4L2 API. + */ + +/* Generic or Raw Data Formats */ +#define IPU_PIX_FMT_GENERIC v4l2_fourcc('I', 'P', 'U', '0') /* IPU Generic Data */ +#define IPU_PIX_FMT_GENERIC_32 v4l2_fourcc('I', 'P', 'U', '1') /* IPU Generic Data */ +#define IPU_PIX_FMT_LVDS666 v4l2_fourcc('L', 'V', 'D', '6') /* IPU Generic Data */ +#define IPU_PIX_FMT_LVDS888 v4l2_fourcc('L', 'V', 'D', '8') /* IPU Generic Data */ +/* RGB Formats */ +#define IPU_PIX_FMT_RGB332 V4L2_PIX_FMT_RGB332 /* 8 RGB-3-3-2 */ +#define IPU_PIX_FMT_RGB555 V4L2_PIX_FMT_RGB555 /* 16 RGB-5-5-5 */ +#define IPU_PIX_FMT_RGB565 V4L2_PIX_FMT_RGB565 /* 1 6 RGB-5-6-5 */ +#define IPU_PIX_FMT_RGB666 v4l2_fourcc('R', 'G', 'B', '6') /* 18 RGB-6-6-6 */ +#define IPU_PIX_FMT_BGR666 v4l2_fourcc('B', 'G', 'R', '6') /* 18 BGR-6-6-6 */ +#define IPU_PIX_FMT_BGR24 V4L2_PIX_FMT_BGR24 /* 24 BGR-8-8-8 */ +#define IPU_PIX_FMT_RGB24 V4L2_PIX_FMT_RGB24 /* 24 RGB-8-8-8 */ +#define IPU_PIX_FMT_BGR32 V4L2_PIX_FMT_BGR32 /* 32 BGR-8-8-8-8 */ +#define IPU_PIX_FMT_BGRA32 v4l2_fourcc('B', 'G', 'R', 'A') /* 32 BGR-8-8-8-8 */ +#define IPU_PIX_FMT_RGB32 V4L2_PIX_FMT_RGB32 /* 32 RGB-8-8-8-8 */ +#define IPU_PIX_FMT_RGBA32 v4l2_fourcc('R', 'G', 'B', 'A') /* 32 RGB-8-8-8-8 */ +#define IPU_PIX_FMT_ABGR32 v4l2_fourcc('A', 'B', 'G', 'R') /* 32 ABGR-8-8-8-8 */ +/* YUV Interleaved Formats */ +#define IPU_PIX_FMT_YUYV V4L2_PIX_FMT_YUYV /* 16 YUV 4:2:2 */ +#define IPU_PIX_FMT_UYVY V4L2_PIX_FMT_UYVY /* 16 YUV 4:2:2 */ +#define IPU_PIX_FMT_Y41P V4L2_PIX_FMT_Y41P /* 12 YUV 4:1:1 */ +#define IPU_PIX_FMT_YUV444 V4L2_PIX_FMT_YUV444 /* 24 YUV 4:4:4 */ +/* two planes -- one Y, one Cb + Cr interleaved */ +#define IPU_PIX_FMT_NV12 V4L2_PIX_FMT_NV12 /* 12 Y/CbCr 4:2:0 */ +/* YUV Planar Formats */ +#define IPU_PIX_FMT_GREY V4L2_PIX_FMT_GREY /* 8 Greyscale */ +#define IPU_PIX_FMT_YVU410P V4L2_PIX_FMT_YVU410P /* 9 YVU 4:1:0 */ +#define IPU_PIX_FMT_YUV410P V4L2_PIX_FMT_YUV410P /* 9 YUV 4:1:0 */ +#define IPU_PIX_FMT_YVU420P v4l2_fourcc('Y', 'V', '1', '2') /* 12 YVU 4:2:0 */ +#define IPU_PIX_FMT_YUV420P v4l2_fourcc('I', '4', '2', '0') /* 12 YUV 4:2:0 */ +#define IPU_PIX_FMT_YUV420P2 v4l2_fourcc('Y', 'U', '1', '2') /* 12 YUV 4:2:0 */ +#define IPU_PIX_FMT_YVU422P v4l2_fourcc('Y', 'V', '1', '6') /* 16 YVU 4:2:2 */ +#define IPU_PIX_FMT_YUV422P V4L2_PIX_FMT_YUV422P /* 16 YUV 4:2:2 */ + +/* + * Bitfield of Display Interface signal polarities. + */ +struct ipu_di_signal_cfg { + unsigned datamask_en:1; + unsigned ext_clk:1; + unsigned interlaced:1; + unsigned odd_field_first:1; + unsigned clksel_en:1; + unsigned clkidle_en:1; + unsigned data_pol:1; /* true = inverted */ + unsigned clk_pol:1; /* true = rising edge */ + unsigned enable_pol:1; + unsigned Hsync_pol:1; /* true = active high */ + unsigned Vsync_pol:1; + + u16 width; + u16 height; + u32 pixel_fmt; + u16 h_start_width; + u16 h_sync_width; + u16 h_end_width; + u16 v_start_width; + u16 v_sync_width; + u16 v_end_width; + u32 v_to_h_sync; +}; + +typedef enum { + RGB, + YCbCr, + YUV +} ipu_color_space_t; + +#define IPU_IRQ_EOF(channel) (channel) /* 0 .. 63 */ +#define IPU_IRQ_NFACK(channel) ((channel) + 64) /* 64 .. 127 */ +#define IPU_IRQ_NFB4EOF(channel) ((channel) + 128) /* 128 .. 191 */ +#define IPU_IRQ_EOS(channel) ((channel) + 192) /* 192 .. 255 */ + +#define IPU_IRQ_DP_SF_START (448 + 2) +#define IPU_IRQ_DP_SF_END (448 + 3) +#define IPU_IRQ_BG_SF_END IPU_IRQ_DP_SF_END, +#define IPU_IRQ_DC_FC_0 (448 + 8) +#define IPU_IRQ_DC_FC_1 (448 + 9) +#define IPU_IRQ_DC_FC_2 (448 + 10) +#define IPU_IRQ_DC_FC_3 (448 + 11) +#define IPU_IRQ_DC_FC_4 (448 + 12) +#define IPU_IRQ_DC_FC_6 (448 + 13) +#define IPU_IRQ_VSYNC_PRE_0 (448 + 14) +#define IPU_IRQ_VSYNC_PRE_1 (448 + 15) + +#define IPU_IRQ_COUNT (15 * 32) + +#define DECLARE_IPU_IRQ_BITMAP(name) DECLARE_BITMAP(name, IPU_IRQ_COUNT) + +struct ipu_irq_handler { + struct list_head list; + void (*handler)(unsigned long *, void *); + void *context; + DECLARE_IPU_IRQ_BITMAP(ipu_irqs); +}; + +int ipu_irq_add_handler(struct ipu_irq_handler *ipuirq); +void ipu_irq_remove_handler(struct ipu_irq_handler *handler); +int ipu_irq_update_handler(struct ipu_irq_handler *handler, + unsigned long *bitmap); +int ipu_wait_for_interrupt(int interrupt, int timeout_ms); + +struct ipu_channel; + +/* + * IPU Image DMA Controller (idmac) functions + */ +struct ipu_channel *ipu_idmac_get(unsigned channel); +void ipu_idmac_put(struct ipu_channel *); +int ipu_idmac_init_channel_buffer(struct ipu_channel *channel, + u32 pixel_fmt, + u16 width, u16 height, + u32 stride, + ipu_rotate_mode_t rot_mode, + dma_addr_t phyaddr_0, dma_addr_t phyaddr_1, + u32 u_offset, u32 v_offset, bool interlaced); + +int ipu_idmac_update_channel_buffer(struct ipu_channel *channel, + u32 bufNum, dma_addr_t phyaddr); + +int ipu_idmac_enable_channel(struct ipu_channel *channel); +int ipu_idmac_disable_channel(struct ipu_channel *channel); + +void ipu_idmac_set_double_buffer(struct ipu_channel *channel, bool doublebuffer); +void ipu_idmac_select_buffer(struct ipu_channel *channel, u32 buf_num); + +/* + * IPU Display Controller (dc) functions + */ +int ipu_dc_init_sync(int dc_chan, int di, bool interlaced, u32 pixel_fmt, u32 width); +void ipu_dc_init_async(int dc_chan, int di, bool interlaced); +void ipu_dc_enable_channel(u32 dc_chan); +void ipu_dc_disable_channel(u32 dc_chan); + +/* + * IPU Display Interface (di) functions + */ +struct ipu_di; +struct ipu_di *ipu_di_get(int disp); +void ipu_di_put(struct ipu_di *); +int ipu_di_disable(struct ipu_di *); +int ipu_di_enable(struct ipu_di *); +int ipu_di_init_sync_panel(struct ipu_di *, u32 pixel_clk, + struct ipu_di_signal_cfg *sig); + +/* + * IPU Display Multi FIFO Controller (dmfc) functions + */ +struct dmfc_channel; +int ipu_dmfc_enable_channel(struct dmfc_channel *dmfc); +void ipu_dmfc_disable_channel(struct dmfc_channel *dmfc); +int ipu_dmfc_alloc_bandwidth(struct dmfc_channel *dmfc, unsigned long bandwidth_mbs); +void ipu_dmfc_free_bandwidth(struct dmfc_channel *dmfc); +int ipu_dmfc_init_channel(struct dmfc_channel *dmfc, int width); +struct dmfc_channel *ipu_dmfc_get(int ipu_channel); +void ipu_dmfc_put(struct dmfc_channel *dmfc); + +/* + * IPU Display Processor (dp) functions + */ +#define IPU_DP_FLOW_SYNC 0 +#define IPU_DP_FLOW_ASYNC0 1 +#define IPU_DP_FLOW_ASYNC1 2 + +struct ipu_dp *ipu_dp_get(unsigned int flow); +void ipu_dp_put(struct ipu_dp *); +int ipu_dp_enable_channel(struct ipu_dp *dp); +void ipu_dp_disable_channel(struct ipu_dp *dp); +void ipu_dp_enable_fg(struct ipu_dp *dp); +void ipu_dp_disable_fg(struct ipu_dp *dp); +int ipu_dp_setup_channel(struct ipu_dp *dp, u32 in_pixel_fmt, + u32 out_pixel_fmt, int bg); +int ipu_dp_set_window_pos(struct ipu_dp *, u16 x_pos, u16 y_pos); +int ipu_dp_set_color_key(struct ipu_dp *dp, bool enable, u32 colorKey); +int ipu_dp_set_global_alpha(struct ipu_dp *dp, bool enable, u8 alpha, + bool bg_chan); + +#endif -- 1.7.4.4 From f09da4b26739e6932ef8ecd9e3eea9493b2653d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:26:08 +0200 Subject: [PATCH 10/28] ipuv3_fb2 Author: Sascha Hauer Email: s.hauer@pengutronix.de Subject: fb: export fb mode db table Date: Wed, 16 Feb 2011 15:10:47 +0100 The different modes can be useful for drivers. Currently there is no way to expose the modes to sysfs, so export them. Signed-off-by: Sascha Hauer Cc: linux-kernel@vger.kernel.org Cc: linux-fbdev@vger.kernel.org Cc: Paul Mundt --- drivers/video/modedb.c | 8 ++++++-- include/linux/fb.h | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/video/modedb.c b/drivers/video/modedb.c index cb175fe..1dd81a7 100644 --- a/drivers/video/modedb.c +++ b/drivers/video/modedb.c @@ -36,8 +36,7 @@ EXPORT_SYMBOL_GPL(fb_mode_option); * Standard video mode definitions (taken from XFree86) */ -static const struct fb_videomode modedb[] = { - +const struct fb_videomode modedb[] = { /* 640x400 @ 70 Hz, 31.5 kHz hsync */ { NULL, 70, 640, 400, 39721, 40, 24, 39, 9, 96, 2, 0, FB_VMODE_NONINTERLACED }, @@ -291,6 +290,11 @@ static const struct fb_videomode modedb[] = { 0, FB_VMODE_NONINTERLACED }, }; +const struct fb_videomode *fb_modes = modedb; +EXPORT_SYMBOL(fb_modes); +const int num_fb_modes = ARRAY_SIZE(modedb); +EXPORT_SYMBOL(num_fb_modes); + #ifdef CONFIG_FB_MODE_HELPERS const struct fb_videomode cea_modes[64] = { /* #1: 640x480p@59.94/60Hz */ diff --git a/include/linux/fb.h b/include/linux/fb.h index 1d6836c..af95e9c 100644 --- a/include/linux/fb.h +++ b/include/linux/fb.h @@ -1158,6 +1158,9 @@ extern const char *fb_mode_option; extern const struct fb_videomode vesa_modes[]; extern const struct fb_videomode cea_modes[64]; +extern const struct fb_videomode *fb_modes; +extern const int num_fb_modes; + struct fb_modelist { struct list_head list; struct fb_videomode mode; -- 1.7.4.4 From d01d41715fdb43618338096e0d3d76dce9b4764c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:26:55 +0200 Subject: [PATCH 11/28] ipuv3_fb3 uthor: Sascha Hauer Email: s.hauer@pengutronix.de Subject: Add i.MX5 framebuffer driver Date: Wed, 16 Feb 2011 15:10:48 +0100 This patch adds framebuffer support to the Freescale i.MX SoCs equipped with an IPU v3, so far these are the i.MX51/53. This driver has been tested on the i.MX51 babbage board with both DVI and analog VGA in different resolutions and color depths. It has also been tested on a custom i.MX51 board using a fixed resolution panel. Signed-off-by: Sascha Hauer Cc: linux-kernel@vger.kernel.org Cc: linux-fbdev@vger.kernel.org Cc: Paul Mundt Cc: Samuel Ortiz --- drivers/video/Kconfig | 11 + drivers/video/Makefile | 1 + drivers/video/mx5fb.c | 926 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 938 insertions(+), 0 deletions(-) create mode 100644 drivers/video/mx5fb.c diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 3ba3a12..87a05c0 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -2342,6 +2342,17 @@ config FB_MX3 far only synchronous displays are supported. If you plan to use an LCD display with your i.MX31 system, say Y here. +config FB_MX5 + tristate "MX5 Framebuffer support" + depends on FB && MFD_IMX_IPU_V3 + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_MODE_HELPERS + help + This is a framebuffer device for the i.MX51 LCD Controller. If you + plan to use an LCD display with your i.MX51 system, say Y here. + config FB_BROADSHEET tristate "E-Ink Broadsheet/Epson S1D13521 controller support" depends on FB diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 0b9db1e..49102f5 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -154,6 +154,7 @@ obj-$(CONFIG_FB_BFIN_LQ035Q1) += bfin-lq035q1-fb.o obj-$(CONFIG_FB_BFIN_T350MCQB) += bfin-t350mcqb-fb.o obj-$(CONFIG_FB_BFIN_7393) += bfin_adv7393fb.o obj-$(CONFIG_FB_MX3) += mx3fb.o +obj-$(CONFIG_FB_MX5) += mx5fb.o obj-$(CONFIG_FB_DA8XX) += da8xx-fb.o obj-$(CONFIG_FB_MXS) += mxsfb.o obj-$(CONFIG_MFD_IMX_IPU_V3) += imx-ipu-v3/ diff --git a/drivers/video/mx5fb.c b/drivers/video/mx5fb.c new file mode 100644 index 0000000..d899b20 --- /dev/null +++ b/drivers/video/mx5fb.c @@ -0,0 +1,926 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * + * Framebuffer Framebuffer Driver for SDC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "imx-ipuv3-fb" + +struct imx_ipu_fb_info { + int ipu_channel_num; + struct ipu_channel *ipu_ch; + int dc; + int di_no; + u32 ipu_di_pix_fmt; + u32 ipu_in_pix_fmt; + + u32 pseudo_palette[16]; + + struct ipu_dp *dp; + struct dmfc_channel *dmfc; + struct ipu_di *di; + struct fb_info *slave; + struct fb_info *master; + bool enabled; + + /* overlay specific fields */ + int ovlxres, ovlyres; + int usage; +}; + +static int imx_ipu_fb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + + return 0; +} + +static int imx_ipu_fb_map_video_memory(struct fb_info *fbi) +{ + int size; + + size = fbi->var.yres_virtual * fbi->fix.line_length; + + if (fbi->screen_base) { + if (fbi->fix.smem_len >= size) + return 0; + else + return -ENOMEM; + } + + fbi->screen_base = dma_alloc_writecombine(fbi->device, + size, + (dma_addr_t *)&fbi->fix.smem_start, + GFP_DMA); + if (fbi->screen_base == 0) { + dev_err(fbi->device, "Unable to allocate framebuffer memory (%d)\n", + fbi->fix.smem_len); + fbi->fix.smem_len = 0; + fbi->fix.smem_start = 0; + return -ENOMEM; + } + + fbi->fix.smem_len = size; + fbi->screen_size = fbi->fix.smem_len; + + dev_dbg(fbi->device, "allocated fb @ paddr=0x%08lx, size=%d\n", + fbi->fix.smem_start, fbi->fix.smem_len); + + /* Clear the screen */ + memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); + + return 0; +} + +static void imx_ipu_fb_enable(struct fb_info *fbi) +{ + struct imx_ipu_fb_info *mxc_fbi = fbi->par; + + if (mxc_fbi->enabled) + return; + + ipu_di_enable(mxc_fbi->di); + ipu_dmfc_enable_channel(mxc_fbi->dmfc); + ipu_idmac_enable_channel(mxc_fbi->ipu_ch); + ipu_dc_enable_channel(mxc_fbi->dc); + ipu_dp_enable_channel(mxc_fbi->dp); + mxc_fbi->enabled = 1; +} + +static void imx_ipu_fb_disable(struct fb_info *fbi) +{ + struct imx_ipu_fb_info *mxc_fbi = fbi->par; + + if (!mxc_fbi->enabled) + return; + + ipu_dp_disable_channel(mxc_fbi->dp); + ipu_dc_disable_channel(mxc_fbi->dc); + ipu_idmac_disable_channel(mxc_fbi->ipu_ch); + ipu_dmfc_disable_channel(mxc_fbi->dmfc); + ipu_di_disable(mxc_fbi->di); + + mxc_fbi->enabled = 0; +} + +static int calc_vref(struct fb_var_screeninfo *var) +{ + unsigned long htotal, vtotal; + + htotal = var->xres + var->right_margin + var->hsync_len + var->left_margin; + vtotal = var->yres + var->lower_margin + var->vsync_len + var->upper_margin; + + if (!htotal || !vtotal) + return 60; + + return PICOS2KHZ(var->pixclock) * 1000 / vtotal / htotal; +} + +static int calc_bandwidth(struct fb_var_screeninfo *var, unsigned int vref) +{ + return var->xres * var->yres * vref; +} + +static int imx_ipu_fb_set_par(struct fb_info *fbi) +{ + int ret; + struct ipu_di_signal_cfg sig_cfg; + struct imx_ipu_fb_info *mxc_fbi = fbi->par; + u32 out_pixel_fmt; + int interlaced = 0; + struct fb_var_screeninfo *var = &fbi->var; + int enabled = mxc_fbi->enabled; + + dev_dbg(fbi->device, "Reconfiguring framebuffer %dx%d-%d\n", + fbi->var.xres, fbi->var.yres, fbi->var.bits_per_pixel); + + if (enabled) + imx_ipu_fb_disable(fbi); + + fbi->fix.line_length = var->xres_virtual * var->bits_per_pixel / 8; + + ret = imx_ipu_fb_map_video_memory(fbi); + if (ret) + return ret; + + if (var->vmode & FB_VMODE_INTERLACED) + interlaced = 1; + + memset(&sig_cfg, 0, sizeof(sig_cfg)); + out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt; + + if (var->vmode & FB_VMODE_INTERLACED) + sig_cfg.interlaced = 1; + if (var->vmode & FB_VMODE_ODD_FLD_FIRST) /* PAL */ + sig_cfg.odd_field_first = 1; + if (var->sync & FB_SYNC_EXT) + sig_cfg.ext_clk = 1; + if (var->sync & FB_SYNC_HOR_HIGH_ACT) + sig_cfg.Hsync_pol = 1; + if (var->sync & FB_SYNC_VERT_HIGH_ACT) + sig_cfg.Vsync_pol = 1; + if (!(var->sync & FB_SYNC_CLK_LAT_FALL)) + sig_cfg.clk_pol = 1; + if (var->sync & FB_SYNC_DATA_INVERT) + sig_cfg.data_pol = 1; + if (!(var->sync & FB_SYNC_OE_LOW_ACT)) + sig_cfg.enable_pol = 1; + if (var->sync & FB_SYNC_CLK_IDLE_EN) + sig_cfg.clkidle_en = 1; + + dev_dbg(fbi->device, "pixclock = %lu.%03lu MHz\n", + PICOS2KHZ(var->pixclock) / 1000, + PICOS2KHZ(var->pixclock) % 1000); + + sig_cfg.width = var->xres; + sig_cfg.height = var->yres; + sig_cfg.pixel_fmt = out_pixel_fmt; + sig_cfg.h_start_width = var->left_margin; + sig_cfg.h_sync_width = var->hsync_len; + sig_cfg.h_end_width = var->right_margin; + sig_cfg.v_start_width = var->upper_margin; + sig_cfg.v_sync_width = var->vsync_len; + sig_cfg.v_end_width = var->lower_margin; + sig_cfg.v_to_h_sync = 0; + + if (mxc_fbi->dp) { + ret = ipu_dp_setup_channel(mxc_fbi->dp, mxc_fbi->ipu_in_pix_fmt, + out_pixel_fmt, 1); + if (ret) { + dev_dbg(fbi->device, "initializing display processor failed with %d\n", + ret); + return ret; + } + } + + ret = ipu_dc_init_sync(mxc_fbi->dc, mxc_fbi->di_no, interlaced, + out_pixel_fmt, fbi->var.xres); + if (ret) { + dev_dbg(fbi->device, "initializing display controller failed with %d\n", + ret); + return ret; + } + + ret = ipu_di_init_sync_panel(mxc_fbi->di, + PICOS2KHZ(var->pixclock) * 1000UL, + &sig_cfg); + if (ret) { + dev_dbg(fbi->device, "initializing panel failed with %d\n", + ret); + return ret; + } + + fbi->mode = (struct fb_videomode *)fb_match_mode(var, &fbi->modelist); + var->xoffset = var->yoffset = 0; + + if (fbi->var.vmode & FB_VMODE_INTERLACED) + interlaced = 1; + + ret = ipu_idmac_init_channel_buffer(mxc_fbi->ipu_ch, + mxc_fbi->ipu_in_pix_fmt, + var->xres, var->yres, + fbi->fix.line_length, + IPU_ROTATE_NONE, + fbi->fix.smem_start, + 0, + 0, 0, interlaced); + if (ret) { + dev_dbg(fbi->device, "init channel buffer failed with %d\n", + ret); + return ret; + } + + ret = ipu_dmfc_init_channel(mxc_fbi->dmfc, var->xres); + if (ret) { + dev_dbg(fbi->device, "initializing dmfc channel failed with %d\n", + ret); + return ret; + } + + ret = ipu_dmfc_alloc_bandwidth(mxc_fbi->dmfc, calc_bandwidth(var, calc_vref(var))); + if (ret) { + dev_dbg(fbi->device, "allocating dmfc bandwidth failed with %d\n", + ret); + return ret; + } + + if (enabled) + imx_ipu_fb_enable(fbi); + + return ret; +} + +/* + * These are the bitfields for each + * display depth that we support. + */ +struct imxfb_rgb { + struct fb_bitfield red; + struct fb_bitfield green; + struct fb_bitfield blue; + struct fb_bitfield transp; +}; + +static struct imxfb_rgb def_rgb_8 = { + .red = { .offset = 5, .length = 3, }, + .green = { .offset = 2, .length = 3, }, + .blue = { .offset = 0, .length = 2, }, + .transp = { .offset = 0, .length = 0, }, +}; + +static struct imxfb_rgb def_rgb_16 = { + .red = { .offset = 11, .length = 5, }, + .green = { .offset = 5, .length = 6, }, + .blue = { .offset = 0, .length = 5, }, + .transp = { .offset = 0, .length = 0, }, +}; + +static struct imxfb_rgb def_rgb_24 = { + .red = { .offset = 16, .length = 8, }, + .green = { .offset = 8, .length = 8, }, + .blue = { .offset = 0, .length = 8, }, + .transp = { .offset = 0, .length = 0, }, +}; + +static struct imxfb_rgb def_rgb_32 = { + .red = { .offset = 16, .length = 8, }, + .green = { .offset = 8, .length = 8, }, + .blue = { .offset = 0, .length = 8, }, + .transp = { .offset = 24, .length = 8, }, +}; + +/* + * Check framebuffer variable parameters and adjust to valid values. + * + * @param var framebuffer variable parameters + * + * @param info framebuffer information pointer + */ +static int imx_ipu_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct imx_ipu_fb_info *mxc_fbi = info->par; + struct imxfb_rgb *rgb; + + /* we don't support xpan, force xres_virtual to be equal to xres */ + var->xres_virtual = var->xres; + + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + switch (var->bits_per_pixel) { + case 8: + rgb = &def_rgb_8; + break; + case 16: + rgb = &def_rgb_16; + mxc_fbi->ipu_in_pix_fmt = IPU_PIX_FMT_RGB565; + break; + case 24: + rgb = &def_rgb_24; + mxc_fbi->ipu_in_pix_fmt = IPU_PIX_FMT_BGR24; + break; + case 32: + rgb = &def_rgb_32; + mxc_fbi->ipu_in_pix_fmt = IPU_PIX_FMT_BGR32; + break; + default: + var->bits_per_pixel = 24; + rgb = &def_rgb_24; + mxc_fbi->ipu_in_pix_fmt = IPU_PIX_FMT_BGR24; + } + + var->red = rgb->red; + var->green = rgb->green; + var->blue = rgb->blue; + var->transp = rgb->transp; + + return 0; +} + +static inline unsigned int chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int imx_ipu_fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *fbi) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = chan_to_field(red, &fbi->var.red); + val |= chan_to_field(green, &fbi->var.green); + val |= chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +static void imx_ipu_fb_enable_overlay(struct fb_info *fbi); +static void imx_ipu_fb_disable_overlay(struct fb_info *fbi); + +static int imx_ipu_fb_blank(int blank, struct fb_info *info) +{ + struct imx_ipu_fb_info *mxc_fbi = info->par; + + dev_dbg(info->device, "blank = %d\n", blank); + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + if (mxc_fbi->slave) + imx_ipu_fb_disable_overlay(mxc_fbi->slave); + imx_ipu_fb_disable(info); + break; + case FB_BLANK_UNBLANK: + imx_ipu_fb_enable(info); + if (mxc_fbi->slave) + imx_ipu_fb_enable_overlay(mxc_fbi->slave); + break; + } + + return 0; +} + +static int imx_ipu_fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct imx_ipu_fb_info *mxc_fbi = info->par; + unsigned long base; + int ret; + + if (info->var.yoffset == var->yoffset) + return 0; /* No change, do nothing */ + + base = var->yoffset * var->xres_virtual * var->bits_per_pixel / 8; + base += info->fix.smem_start; + + ret = ipu_wait_for_interrupt(IPU_IRQ_EOF(mxc_fbi->ipu_channel_num), 100); + if (ret) + return ret; + + if (ipu_idmac_update_channel_buffer(mxc_fbi->ipu_ch, 0, base)) { + dev_err(info->device, + "Error updating SDC buf to address=0x%08lX\n", base); + } + + info->var.yoffset = var->yoffset; + + return 0; +} + +static struct fb_ops imx_ipu_fb_ops = { + .owner = THIS_MODULE, + .fb_set_par = imx_ipu_fb_set_par, + .fb_check_var = imx_ipu_fb_check_var, + .fb_setcolreg = imx_ipu_fb_setcolreg, + .fb_pan_display = imx_ipu_fb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = imx_ipu_fb_blank, +}; + +/* + * Overlay functions + */ +static void imx_ipu_fb_enable_overlay(struct fb_info *ovl) +{ + struct imx_ipu_fb_info *mxc_ovl = ovl->par; + + if (mxc_ovl->enabled) + return; + + ipu_dmfc_enable_channel(mxc_ovl->dmfc); + ipu_idmac_enable_channel(mxc_ovl->ipu_ch); + ipu_dp_enable_fg(mxc_ovl->dp); + mxc_ovl->enabled = 1; +} + +static void imx_ipu_fb_disable_overlay(struct fb_info *ovl) +{ + struct imx_ipu_fb_info *mxc_ovl = ovl->par; + + if (!mxc_ovl->enabled) + return; + + ipu_dp_disable_fg(mxc_ovl->dp); + ipu_wait_for_interrupt(451, 100); + ipu_idmac_disable_channel(mxc_ovl->ipu_ch); + ipu_dmfc_disable_channel(mxc_ovl->dmfc); + mxc_ovl->enabled = 0; +} + +#define NONSTD_TO_XPOS(x) (((x) >> 0) & 0xfff) +#define NONSTD_TO_YPOS(x) (((x) >> 12) & 0xfff) +#define NONSTD_TO_ALPHA(x) (((x) >> 24) & 0xff) + +static int imx_ipu_fb_check_var_overlay(struct fb_var_screeninfo *var, + struct fb_info *ovl) +{ + struct imx_ipu_fb_info *mxc_ovl = ovl->par; + struct fb_info *fbi_master = mxc_ovl->master; + struct fb_var_screeninfo *var_master = &fbi_master->var; + int ret; + static int xpos, ypos; + + xpos = NONSTD_TO_XPOS(var->nonstd); + ypos = NONSTD_TO_YPOS(var->nonstd); + + ret = imx_ipu_fb_check_var(var, ovl); + if (ret) + return ret; + + if (var->xres + xpos > var_master->xres) + return -EINVAL; + if (var->yres + ypos > var_master->yres) + return -EINVAL; + + return 0; +} + +static int imx_ipu_fb_set_par_overlay(struct fb_info *ovl) +{ + struct imx_ipu_fb_info *mxc_ovl = ovl->par; + struct fb_var_screeninfo *var = &ovl->var; + struct fb_info *fbi_master = mxc_ovl->master; + struct imx_ipu_fb_info *mxc_fbi_master = fbi_master->par; + struct fb_var_screeninfo *var_master = &fbi_master->var; + int ret; + int interlaced = 0; + int xpos, ypos, alpha; + int resolution_change; + + dev_dbg(ovl->device, "Reconfiguring framebuffer %dx%d-%d\n", + ovl->var.xres, ovl->var.yres, ovl->var.bits_per_pixel); + + resolution_change = mxc_ovl->ovlxres != var->xres || + mxc_ovl->ovlyres != var->yres; + + if (mxc_ovl->enabled && resolution_change) + imx_ipu_fb_disable_overlay(ovl); + + ovl->fix.line_length = var->xres_virtual * + var->bits_per_pixel / 8; + + xpos = NONSTD_TO_XPOS(var->nonstd); + ypos = NONSTD_TO_YPOS(var->nonstd); + alpha = NONSTD_TO_ALPHA(var->nonstd); + + if (resolution_change) { + ret = imx_ipu_fb_map_video_memory(ovl); + if (ret) + return ret; + } + + if (!resolution_change && mxc_ovl->enabled) + ipu_wait_for_interrupt(IPU_IRQ_EOF(mxc_fbi_master->ipu_channel_num), 100); + + ipu_dp_set_window_pos(mxc_ovl->dp, xpos, ypos); + ipu_dp_set_global_alpha(mxc_ovl->dp, 1, alpha, 1); + + var->xoffset = var->yoffset = 0; + + if (resolution_change) { + if (var->vmode & FB_VMODE_INTERLACED) + interlaced = 1; + + ret = ipu_idmac_init_channel_buffer(mxc_ovl->ipu_ch, + mxc_ovl->ipu_in_pix_fmt, + var->xres, var->yres, + ovl->fix.line_length, + IPU_ROTATE_NONE, + ovl->fix.smem_start, + 0, + 0, 0, interlaced); + if (ret) { + dev_dbg(ovl->device, "init channel buffer failed with %d\n", + ret); + return ret; + } + + ret = ipu_dmfc_init_channel(mxc_ovl->dmfc, var->xres); + if (ret) { + dev_dbg(ovl->device, "initializing dmfc channel failed with %d\n", + ret); + return ret; + } + + ret = ipu_dmfc_alloc_bandwidth(mxc_ovl->dmfc, calc_bandwidth(var, calc_vref(var_master))); + if (ret) { + dev_dbg(ovl->device, "allocating dmfc bandwidth failed with %d\n", + ret); + return ret; + } + mxc_ovl->ovlxres = var->xres; + mxc_ovl->ovlyres = var->yres; + } + + if (mxc_fbi_master->enabled) + imx_ipu_fb_enable_overlay(ovl); + + return ret; +} + +static int imx_overlayfb_open(struct fb_info *ovl, int user) +{ + struct imx_ipu_fb_info *mxc_ovl = ovl->par; + + if (mxc_ovl->usage++ == 0) + printk("enable ovl\n"); + + return 0; +} + +static int imx_overlayfb_release(struct fb_info *ovl, int user) +{ + struct imx_ipu_fb_info *mxc_ovl = ovl->par; + + if (--mxc_ovl->usage == 0) { + printk("disable ovl\n"); + + if (ovl->screen_base) + dma_free_writecombine(ovl->device, ovl->fix.smem_len, + ovl->screen_base, ovl->fix.smem_start); + ovl->fix.smem_len = 0; + ovl->fix.smem_start = 0; + ovl->screen_base = NULL; + mxc_ovl->ovlxres = 0; + mxc_ovl->ovlyres = 0; + } + + return 0; +} + +static struct fb_ops imx_ipu_fb_overlay_ops = { + .owner = THIS_MODULE, + .fb_set_par = imx_ipu_fb_set_par_overlay, + .fb_check_var = imx_ipu_fb_check_var_overlay, + .fb_setcolreg = imx_ipu_fb_setcolreg, + .fb_pan_display = imx_ipu_fb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_open = imx_overlayfb_open, + .fb_release = imx_overlayfb_release, +}; + +static struct fb_info *imx_ipu_fb_init_fbinfo(struct device *dev, struct fb_ops *ops) +{ + struct fb_info *fbi; + struct imx_ipu_fb_info *mxc_fbi; + + fbi = framebuffer_alloc(sizeof(struct imx_ipu_fb_info), dev); + if (!fbi) + return NULL; + + BUG_ON(fbi->par == NULL); + mxc_fbi = fbi->par; + + fbi->var.activate = FB_ACTIVATE_NOW; + + fbi->fbops = ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = mxc_fbi->pseudo_palette; + + fb_alloc_cmap(&fbi->cmap, 16, 0); + + return fbi; +} + +static int imx_ipu_fb_init_overlay(struct platform_device *pdev, + struct fb_info *fbi_master, int ipu_channel) +{ + struct imx_ipu_fb_info *mxc_fbi_master = fbi_master->par; + struct fb_info *ovl; + struct imx_ipu_fb_info *mxc_ovl; + int ret; + + ovl = imx_ipu_fb_init_fbinfo(&pdev->dev, &imx_ipu_fb_overlay_ops); + if (!ovl) + return -ENOMEM; + + mxc_ovl = ovl->par; + mxc_ovl->ipu_ch = ipu_idmac_get(ipu_channel); + mxc_ovl->dmfc = ipu_dmfc_get(ipu_channel); + mxc_ovl->di = NULL; + mxc_ovl->dp = mxc_fbi_master->dp; + mxc_ovl->master = fbi_master; + mxc_fbi_master->slave = ovl; + + imx_ipu_fb_check_var(&ovl->var, ovl); + imx_ipu_fb_set_fix(ovl); + + ret = register_framebuffer(ovl); + if (ret) { + framebuffer_release(ovl); + return ret; + } + + ipu_dp_set_global_alpha(mxc_ovl->dp, 0, 0, 1); + ipu_dp_set_color_key(mxc_ovl->dp, 1, 0x434343); + + return 0; +} + +static void imx_ipu_fb_exit_overlay(struct platform_device *pdev, + struct fb_info *fbi_master, int ipu_channel) +{ + struct imx_ipu_fb_info *mxc_fbi_master = fbi_master->par; + struct fb_info *ovl = mxc_fbi_master->slave; + struct imx_ipu_fb_info *mxc_ovl = ovl->par; + + if (mxc_ovl->enabled) + imx_ipu_fb_disable_overlay(ovl); + + unregister_framebuffer(ovl); + + ipu_idmac_put(mxc_ovl->ipu_ch); + ipu_dmfc_free_bandwidth(mxc_ovl->dmfc); + ipu_dmfc_put(mxc_ovl->dmfc); + + framebuffer_release(ovl); +} + +static int imx_ipu_fb_find_mode(struct fb_info *fbi) +{ + int ret; + struct fb_videomode *mode_array; + struct fb_modelist *modelist; + struct fb_var_screeninfo *var = &fbi->var; + int i = 0; + + list_for_each_entry(modelist, &fbi->modelist, list) + i++; + + mode_array = kmalloc(sizeof (struct fb_modelist) * i, GFP_KERNEL); + if (!mode_array) + return -ENOMEM; + + i = 0; + list_for_each_entry(modelist, &fbi->modelist, list) + mode_array[i++] = modelist->mode; + + ret = fb_find_mode(&fbi->var, fbi, NULL, mode_array, i, NULL, 16); + if (ret == 0) + return -EINVAL; + + dev_dbg(fbi->device, "found %dx%d-%d hs:%d:%d:%d vs:%d:%d:%d\n", + var->xres, var->yres, var->bits_per_pixel, + var->hsync_len, var->left_margin, var->right_margin, + var->vsync_len, var->upper_margin, var->lower_margin); + + kfree(mode_array); + + return 0; +} + +static int __devinit imx_ipu_fb_probe(struct platform_device *pdev) +{ + struct fb_info *fbi; + struct imx_ipu_fb_info *mxc_fbi; + struct ipuv3_fb_platform_data *plat_data = mfd_get_data(pdev); + int ret = 0, i; + + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + fbi = imx_ipu_fb_init_fbinfo(&pdev->dev, &imx_ipu_fb_ops); + if (!fbi) + return -ENOMEM; + + mxc_fbi = fbi->par; + + mxc_fbi->ipu_channel_num = plat_data->ipu_channel_bg; + mxc_fbi->dc = plat_data->dc_channel; + mxc_fbi->ipu_di_pix_fmt = plat_data->interface_pix_fmt; + mxc_fbi->di_no = plat_data->display; + + mxc_fbi->ipu_ch = ipu_idmac_get(plat_data->ipu_channel_bg); + if (IS_ERR(mxc_fbi->ipu_ch)) { + ret = PTR_ERR(mxc_fbi->ipu_ch); + goto failed_request_ipu; + } + + mxc_fbi->dmfc = ipu_dmfc_get(plat_data->ipu_channel_bg); + if (IS_ERR(mxc_fbi->ipu_ch)) { + ret = PTR_ERR(mxc_fbi->ipu_ch); + goto failed_request_dmfc; + } + + if (plat_data->dp_channel >= 0) { + mxc_fbi->dp = ipu_dp_get(plat_data->dp_channel); + if (IS_ERR(mxc_fbi->dp)) { + ret = PTR_ERR(mxc_fbi->ipu_ch); + goto failed_request_dp; + } + } + + mxc_fbi->di = ipu_di_get(plat_data->display); + if (IS_ERR(mxc_fbi->di)) { + ret = PTR_ERR(mxc_fbi->di); + goto failed_request_di; + } + + fbi->var.yres_virtual = fbi->var.yres; + + INIT_LIST_HEAD(&fbi->modelist); + for (i = 0; i < plat_data->num_modes; i++) + fb_add_videomode(&plat_data->modes[i], &fbi->modelist); + + if (plat_data->flags & IMX_IPU_FB_USE_MODEDB) { + for (i = 0; i < num_fb_modes; i++) + fb_add_videomode(&fb_modes[i], &fbi->modelist); + } + + imx_ipu_fb_find_mode(fbi); + + imx_ipu_fb_check_var(&fbi->var, fbi); + imx_ipu_fb_set_fix(fbi); + ret = register_framebuffer(fbi); + if (ret < 0) + goto failed_register; + + imx_ipu_fb_set_par(fbi); + imx_ipu_fb_blank(FB_BLANK_UNBLANK, fbi); + + if (plat_data->ipu_channel_fg >= 0 && plat_data->flags & IMX_IPU_FB_USE_OVERLAY) + imx_ipu_fb_init_overlay(pdev, fbi, plat_data->ipu_channel_fg); + + platform_set_drvdata(pdev, fbi); + + return 0; + +failed_register: + ipu_di_put(mxc_fbi->di); +failed_request_di: + if (plat_data->dp_channel >= 0) + ipu_dp_put(mxc_fbi->dp); +failed_request_dp: + ipu_dmfc_put(mxc_fbi->dmfc); +failed_request_dmfc: + ipu_idmac_put(mxc_fbi->ipu_ch); +failed_request_ipu: + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); + + return ret; +} + +static int __devexit imx_ipu_fb_remove(struct platform_device *pdev) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct imx_ipu_fb_info *mxc_fbi = fbi->par; + struct ipuv3_fb_platform_data *plat_data = mfd_get_data(pdev); + + if (plat_data->ipu_channel_fg >= 0 && plat_data->flags & IMX_IPU_FB_USE_OVERLAY) + imx_ipu_fb_exit_overlay(pdev, fbi, plat_data->ipu_channel_fg); + + imx_ipu_fb_blank(FB_BLANK_POWERDOWN, fbi); + + dma_free_writecombine(fbi->device, fbi->fix.smem_len, + fbi->screen_base, fbi->fix.smem_start); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + + if (plat_data->dp_channel >= 0) + ipu_dp_put(mxc_fbi->dp); + ipu_dmfc_free_bandwidth(mxc_fbi->dmfc); + ipu_dmfc_put(mxc_fbi->dmfc); + ipu_di_put(mxc_fbi->di); + ipu_idmac_put(mxc_fbi->ipu_ch); + + framebuffer_release(fbi); + + return 0; +} + +static struct platform_driver imx_ipu_fb_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .probe = imx_ipu_fb_probe, + .remove = __devexit_p(imx_ipu_fb_remove), +}; + +static int __init imx_ipu_fb_init(void) +{ + return platform_driver_register(&imx_ipu_fb_driver); +} + +static void __exit imx_ipu_fb_exit(void) +{ + platform_driver_unregister(&imx_ipu_fb_driver); +} + +module_init(imx_ipu_fb_init); +module_exit(imx_ipu_fb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX framebuffer driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("fb"); -- 1.7.4.4 From fb863903a0017374ffc35962ac9e203e449c1fde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:27:37 +0200 Subject: [PATCH 12/28] ipuv3_fb4 Author: Sascha Hauer Email: s.hauer@pengutronix.de Subject: ARM i.MX51: Add IPU device support Date: Wed, 16 Feb 2011 15:10:49 +0100 Signed-off-by: Sascha Hauer --- arch/arm/mach-mx5/devices-imx51.h | 4 ++ arch/arm/plat-mxc/devices/Kconfig | 4 ++ arch/arm/plat-mxc/devices/Makefile | 1 + arch/arm/plat-mxc/devices/platform-imx_ipuv3.c | 47 +++++++++++++++++++++++ arch/arm/plat-mxc/include/mach/devices-common.h | 10 +++++ 5 files changed, 66 insertions(+), 0 deletions(-) create mode 100644 arch/arm/plat-mxc/devices/platform-imx_ipuv3.c diff --git a/arch/arm/mach-mx5/devices-imx51.h b/arch/arm/mach-mx5/devices-imx51.h index 89fe77f..0cc1e91 100644 --- a/arch/arm/mach-mx5/devices-imx51.h +++ b/arch/arm/mach-mx5/devices-imx51.h @@ -56,3 +56,7 @@ extern const struct imx_imx_keypad_data imx51_imx_keypad_data; extern const struct imx_pata_imx_data imx51_pata_imx_data; #define imx51_add_pata_imx() \ imx_add_pata_imx(&imx51_pata_imx_data) + +extern const struct imx_ipuv3_data imx51_ipuv3_data __initconst; +#define imx51_add_ipuv3(pdata) \ + imx_add_ipuv3(&imx51_ipuv3_data, pdata) diff --git a/arch/arm/plat-mxc/devices/Kconfig b/arch/arm/plat-mxc/devices/Kconfig index c55916c..26110b0 100644 --- a/arch/arm/plat-mxc/devices/Kconfig +++ b/arch/arm/plat-mxc/devices/Kconfig @@ -79,3 +79,7 @@ config IMX_HAVE_PLATFORM_SDHCI_ESDHC_IMX config IMX_HAVE_PLATFORM_SPI_IMX bool + +config IMX_HAVE_PLATFORM_IMX_IPUV3 + bool + diff --git a/arch/arm/plat-mxc/devices/Makefile b/arch/arm/plat-mxc/devices/Makefile index a093b45..532b3f3 100644 --- a/arch/arm/plat-mxc/devices/Makefile +++ b/arch/arm/plat-mxc/devices/Makefile @@ -26,3 +26,4 @@ obj-$(CONFIG_IMX_HAVE_PLATFORM_MXC_RTC) += platform-mxc_rtc.o obj-$(CONFIG_IMX_HAVE_PLATFORM_MXC_W1) += platform-mxc_w1.o obj-$(CONFIG_IMX_HAVE_PLATFORM_SDHCI_ESDHC_IMX) += platform-sdhci-esdhc-imx.o obj-$(CONFIG_IMX_HAVE_PLATFORM_SPI_IMX) += platform-spi_imx.o +obj-$(CONFIG_IMX_HAVE_PLATFORM_IMX_IPUV3) += platform-imx_ipuv3.o diff --git a/arch/arm/plat-mxc/devices/platform-imx_ipuv3.c b/arch/arm/plat-mxc/devices/platform-imx_ipuv3.c new file mode 100644 index 0000000..2c6b913 --- /dev/null +++ b/arch/arm/plat-mxc/devices/platform-imx_ipuv3.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2010 Pengutronix + * Uwe Kleine-Koenig + * + * 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 + +#define imx51_ipuv3_data_entry_single(soc) \ + { \ + .iobase = soc ## _IPU_CTRL_BASE_ADDR, \ + .irq_err = soc ## _INT_IPU_ERR, \ + .irq = soc ## _INT_IPU_SYN, \ + } + +#ifdef CONFIG_SOC_IMX51 +const struct imx_ipuv3_data imx51_ipuv3_data __initconst = + imx51_ipuv3_data_entry_single(MX51); +#endif /* ifdef CONFIG_SOC_IMX35 */ + +struct platform_device *__init imx_add_ipuv3( + const struct imx_ipuv3_data *data, + const struct imx_ipuv3_platform_data *pdata) +{ + struct resource res[] = { + { + .start = data->iobase, + .end = data->iobase + SZ_512M - 1, + .flags = IORESOURCE_MEM, + }, { + .start = data->irq_err, + .end = data->irq_err, + .flags = IORESOURCE_IRQ, + }, { + .start = data->irq, + .end = data->irq, + .flags = IORESOURCE_IRQ, + }, + }; + + return imx_add_platform_device("imx-ipuv3", -1, + res, ARRAY_SIZE(res), pdata, sizeof(*pdata)); +} + diff --git a/arch/arm/plat-mxc/include/mach/devices-common.h b/arch/arm/plat-mxc/include/mach/devices-common.h index 1b76c67..e28c6bb 100644 --- a/arch/arm/plat-mxc/include/mach/devices-common.h +++ b/arch/arm/plat-mxc/include/mach/devices-common.h @@ -308,3 +308,13 @@ struct platform_device *__init imx_add_spi_imx( struct platform_device *imx_add_imx_dma(void); struct platform_device *imx_add_imx_sdma(char *name, resource_size_t iobase, int irq, struct sdma_platform_data *pdata); + +#include +struct imx_ipuv3_data { + resource_size_t iobase; + resource_size_t irq_err; + resource_size_t irq; +}; +struct platform_device *__init imx_add_ipuv3( + const struct imx_ipuv3_data *data, + const struct imx_ipuv3_platform_data *pdata); -- 1.7.4.4 From c04da9479146bfbaaab2f37089192bf90fd4ae8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:28:40 +0200 Subject: [PATCH 13/28] ipuv3_fb5 Author: Sascha Hauer Email: s.hauer@pengutronix.de Subject: ARM i.MX5: Allow to increase max zone order Date: Wed, 16 Feb 2011 15:10:50 +0100 default setting of 11 allows us to allocate at maximum 2MB chunks of contiguous memory. For resolutions up to 1920x1080 32bpp we need much more memory, so make zone order configurable Signed-off-by: Sascha Hauer --- arch/arm/Kconfig | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 3269576..af9f8ec 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1588,8 +1588,8 @@ config HW_PERF_EVENTS source "mm/Kconfig" config FORCE_MAX_ZONEORDER - int "Maximum zone order" if ARCH_SHMOBILE - range 11 64 if ARCH_SHMOBILE + int "Maximum zone order" if ARCH_SHMOBILE || ARCH_MX5 + range 11 64 if ARCH_SHMOBILE || ARCH_MX5 default "9" if SA1111 default "11" help -- 1.7.4.4 From ea38327b29eb535ef6b052865de3cdaa415a4ba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:29:06 +0200 Subject: [PATCH 14/28] ipuv3_fb6 Author: Sascha Hauer Email: s.hauer@pengutronix.de Subject: ARM i.MX5: increase dma consistent size for IPU support Date: Wed, 16 Feb 2011 15:10:51 +0100 Signed-off-by: Sascha Hauer --- arch/arm/plat-mxc/include/mach/memory.h | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diff --git a/arch/arm/plat-mxc/include/mach/memory.h b/arch/arm/plat-mxc/include/mach/memory.h index 11be5cd..aace832 100644 --- a/arch/arm/plat-mxc/include/mach/memory.h +++ b/arch/arm/plat-mxc/include/mach/memory.h @@ -40,7 +40,8 @@ # endif #endif -#if defined(CONFIG_MX3_VIDEO) +#if defined(CONFIG_MX3_VIDEO) || defined(CONFIG_MFD_IMX_IPU_V3) || \ + defined(CONFIG_MFD_IMX_IPU_V3_MODULE) /* * Increase size of DMA-consistent memory region. * This is required for mx3 camera driver to capture at least two QXGA frames. -- 1.7.4.4 From 6e73463d49b5af43da7f5114e8094e9e98f69d4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:29:56 +0200 Subject: [PATCH 15/28] ipuv3_fixlet --- drivers/video/imx-ipu-v3/Kconfig | 7 +++++++ 1 files changed, 7 insertions(+), 0 deletions(-) create mode 100644 drivers/video/imx-ipu-v3/Kconfig diff --git a/drivers/video/imx-ipu-v3/Kconfig b/drivers/video/imx-ipu-v3/Kconfig new file mode 100644 index 0000000..434445d --- /dev/null +++ b/drivers/video/imx-ipu-v3/Kconfig @@ -0,0 +1,7 @@ +config MFD_IMX_IPU_V3 + tristate "Support the Image Processing Unit (IPU) found on the i.MX51" + depends on ARCH_MX51 + select MFD_CORE + help + Say yes here to support the IPU on i.MX51. + -- 1.7.4.4 From 3d6c6d346016f5a6c05f8ce2c4e51df4d9e095c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:30:52 +0200 Subject: [PATCH 16/28] hack_ipu_imx3_vs_imx5 --- arch/arm/plat-mxc/include/mach/devices-common.h | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-) diff --git a/arch/arm/plat-mxc/include/mach/devices-common.h b/arch/arm/plat-mxc/include/mach/devices-common.h index e28c6bb..4ef3a46 100644 --- a/arch/arm/plat-mxc/include/mach/devices-common.h +++ b/arch/arm/plat-mxc/include/mach/devices-common.h @@ -171,6 +171,7 @@ struct platform_device *__init imx_add_imx_udc( const struct imx_imx_udc_data *data, const struct imxusb_platform_data *pdata); +#if 0 #include #include #include @@ -188,6 +189,7 @@ struct platform_device *__init imx_alloc_mx3_camera( struct platform_device *__init imx_add_mx3_sdc_fb( const struct imx_ipu_core_data *data, struct mx3fb_platform_data *pdata); +#endif #include struct imx_mx1_camera_data { -- 1.7.4.4 From 9065fb207194c345381c1280b1a829f609a272db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:31:28 +0200 Subject: [PATCH 17/28] fb_mfd Fix fb driver regarding latest mfd changes The driver is ftbfs'ing to due changes in mfd stuff. Fix that. Signed-off-by: Arnaud Patard --- drivers/video/imx-ipu-v3/ipu-common.c | 3 ++- drivers/video/mx5fb.c | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/video/imx-ipu-v3/ipu-common.c b/drivers/video/imx-ipu-v3/ipu-common.c index 19140e5..6d59328 100644 --- a/drivers/video/imx-ipu-v3/ipu-common.c +++ b/drivers/video/imx-ipu-v3/ipu-common.c @@ -509,7 +509,8 @@ static int ipu_add_subdevice_pdata(struct platform_device *pdev, const char *name, int id, void *pdata) { struct mfd_cell cell = { - .mfd_data = pdata, + .platform_data = pdata, + .pdata_size = sizeof(struct ipuv3_fb_platform_data), }; cell.name = name; diff --git a/drivers/video/mx5fb.c b/drivers/video/mx5fb.c index d899b20..311c0fc 100644 --- a/drivers/video/mx5fb.c +++ b/drivers/video/mx5fb.c @@ -781,7 +781,7 @@ static int __devinit imx_ipu_fb_probe(struct platform_device *pdev) { struct fb_info *fbi; struct imx_ipu_fb_info *mxc_fbi; - struct ipuv3_fb_platform_data *plat_data = mfd_get_data(pdev); + struct ipuv3_fb_platform_data *plat_data = dev_get_platdata(&pdev->dev); int ret = 0, i; pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); @@ -872,7 +872,7 @@ static int __devexit imx_ipu_fb_remove(struct platform_device *pdev) { struct fb_info *fbi = platform_get_drvdata(pdev); struct imx_ipu_fb_info *mxc_fbi = fbi->par; - struct ipuv3_fb_platform_data *plat_data = mfd_get_data(pdev); + struct ipuv3_fb_platform_data *plat_data = dev_get_platdata(&pdev->dev); if (plat_data->ipu_channel_fg >= 0 && plat_data->flags & IMX_IPU_FB_USE_OVERLAY) imx_ipu_fb_exit_overlay(pdev, fbi, plat_data->ipu_channel_fg); -- 1.7.4.4 From 61f1a9f37ce7ae093853babbbf101baed34575d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:33:23 +0200 Subject: [PATCH 18/28] mtl017 --- drivers/video/backlight/Kconfig | 7 + drivers/video/backlight/Makefile | 1 + drivers/video/backlight/mtl017.c | 349 ++++++++++++++++++++++++++++++++++++++ include/linux/mtl017.h | 27 +++ 4 files changed, 384 insertions(+), 0 deletions(-) create mode 100644 drivers/video/backlight/mtl017.c create mode 100644 include/linux/mtl017.h diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 278aeaa..d55caf8 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -125,6 +125,13 @@ config LCD_AMS369FG06 If you have an AMS369FG06 AMOLED Panel, say Y to enable its LCD control driver. +config LCD_MTL017 + tristate "MTL017 LCD Driver" + depends on I2C && GENERIC_GPIO + help + If you have a system with a LCD connected to a MTL017, say Y + to enable support for it. + endif # LCD_CLASS_DEVICE # diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index fdd1fc4..b654287 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_LCD_TOSA) += tosa_lcd.o obj-$(CONFIG_LCD_S6E63M0) += s6e63m0.o obj-$(CONFIG_LCD_LD9040) += ld9040.o obj-$(CONFIG_LCD_AMS369FG06) += ams369fg06.o +obj-$(CONFIG_LCD_MTL017) += mtl017.o obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o obj-$(CONFIG_BACKLIGHT_ATMEL_PWM) += atmel-pwm-bl.o diff --git a/drivers/video/backlight/mtl017.c b/drivers/video/backlight/mtl017.c new file mode 100644 index 0000000..c128e37 --- /dev/null +++ b/drivers/video/backlight/mtl017.c @@ -0,0 +1,349 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../edid.h" + +#define BLOCK_TX_SIZE 32 +#define MAX_TRIES 5 + +struct mtl017_private { + struct i2c_client *i2c_client; + struct lcd_device *lcd_dev; + + /* i2c bus where is connected display */ + int disp_i2c_bus_id; + char edid[EDID_LENGTH]; + + /* gpios */ + int lvds_reset_gpio; + int lvds_pwrctl_gpio; + int lcd_pwr_gpio; + int lcd_en_gpio; + enum lcd_init_mode init_mode; + + /* values to write when initializing mtl017 */ + char *regs; + int regs_size; + + /* power related stuff */ + int pwr; + int suspended; +}; + +static int mtl017_probe_edid(struct mtl017_private *mtl017) +{ + struct i2c_adapter *adapter; + unsigned char start = 0x0; + unsigned char *buf = mtl017->edid; + int ret = -EIO; + struct i2c_msg msgs[] = { + { + .addr = 0x50, + .flags = 0, + .len = 1, + .buf = &start, + }, { + .addr = 0x50, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = buf, + } + }; + + adapter = i2c_get_adapter(mtl017->disp_i2c_bus_id); + if (!adapter) { + dev_err(&mtl017->i2c_client->dev, "can't get i2c adapter %d\n", + mtl017->disp_i2c_bus_id); + return ret; + } + + if (i2c_transfer(adapter, msgs, 2) == 2) + ret = 0; + else + dev_warn(&adapter->dev, "unable to read EDID block.\n"); + + i2c_put_adapter(adapter); + + return ret; +} + +static int mtl017_find_reg_settings(struct mtl017_pdata *pdata, + struct mtl017_private *mtl017) +{ + struct lcd_panel_info *infos; + int ret, i; + + /* get edid */ + ret = mtl017_probe_edid(mtl017); + if (ret) + return ret; + + /* try to find lcd registers settings thanks to edid man infos */ + for (i = 0; i < pdata->nr_lcd_infos; i++) { + infos = &pdata->lcd_infos[i]; + if (memcmp(mtl017->edid + 0x71, infos->product_name, + strlen(infos->product_name)) == 0) { + dev_info(&mtl017->i2c_client->dev, "found %s %s\n", + infos->manufacture, infos->product_name); + mtl017->regs = kmemdup(infos->regs, + infos->regs_size, GFP_KERNEL); + mtl017->regs_size = infos->regs_size; + mtl017->init_mode = infos->init_mode; + if (!mtl017->regs) + return -ENOMEM; + + return 0; + } + } + + return -ENOENT; +} + +static int mtl017_conf(struct mtl017_private *mtl017) +{ + int i, ret, retry; + + for (i = 0, retry = 0; i < mtl017->regs_size; i += BLOCK_TX_SIZE) { +retry: + msleep(1); + ret = i2c_smbus_write_i2c_block_data(mtl017->i2c_client, i, + BLOCK_TX_SIZE, + &(mtl017->regs[i])); + if (ret < 0) { + dev_warn(&mtl017->i2c_client->dev, + "%s: write failure (%d)\n", __func__, retry); + if (retry++ < MAX_TRIES) + goto retry; + return ret; + } + } + return 0; +} + +static void mtl017_lcd_power_on(struct mtl017_private *mtl017) +{ + switch (mtl017->init_mode) { + case INIT_MODE_AUO: + gpio_set_value(mtl017->lcd_pwr_gpio, 1); + msleep(10); + gpio_set_value(mtl017->lcd_en_gpio, 1); + msleep(5); + gpio_set_value(mtl017->lvds_pwrctl_gpio, 1); + msleep(5); + break; + case INIT_MODE_OTHER: + gpio_direction_input(mtl017->lcd_en_gpio); + gpio_set_value(mtl017->lcd_pwr_gpio, 1); + gpio_set_value(mtl017->lvds_pwrctl_gpio, 1); + break; + } + + mtl017_conf(mtl017); + msleep(200); +} + +static void mtl017_lcd_power_off(struct mtl017_private *mtl017) +{ + if (mtl017->init_mode == INIT_MODE_AUO) + gpio_set_value(mtl017->lcd_en_gpio, 0); + + gpio_set_value(mtl017->lvds_pwrctl_gpio, 0); + gpio_set_value(mtl017->lcd_pwr_gpio, 0); +} + +static int mtl017_lcd_get_power(struct lcd_device *lcd) +{ + struct mtl017_private *mtl017 = lcd_get_data(lcd); + return mtl017->pwr; +} + +static int mtl017_lcd_set_power(struct lcd_device *lcd, int power) +{ + struct mtl017_private *mtl017 = lcd_get_data(lcd); + int lcd_power = 1; + + if ((power == FB_BLANK_POWERDOWN) || (mtl017->suspended)) + lcd_power = 0; + + if (lcd_power) + mtl017_lcd_power_on(mtl017); + else + mtl017_lcd_power_off(mtl017); + + mtl017->pwr = power; + + return 0; +} + +static struct lcd_ops mtl017_lcd_ops = { + .get_power = mtl017_lcd_get_power, + .set_power = mtl017_lcd_set_power, +}; + +static int mtl017_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct mtl017_private *mtl017; + struct mtl017_pdata *pdata; + int ret; + + pdata = i2c_client->dev.platform_data; + if (!pdata) { + dev_err(&i2c_client->dev, "no platform data provided\n"); + return -EINVAL; + } + + mtl017 = kzalloc(sizeof(struct mtl017_private), GFP_KERNEL); + if (!mtl017) { + dev_err(&i2c_client->dev, "could not allocate private struct\n"); + return -ENOMEM; + } + + i2c_set_clientdata(i2c_client, mtl017); + mtl017->i2c_client = i2c_client; + mtl017->disp_i2c_bus_id = pdata->disp_i2c_bus_id; + + mtl017->lvds_reset_gpio = pdata->lvds_reset_gpio; + ret = gpio_request(mtl017->lvds_reset_gpio, "lvds_reset"); + if (ret) { + dev_err(&i2c_client->dev, "failed to request lvds_reset\n"); + goto err_alloc; + } + gpio_direction_output(mtl017->lvds_reset_gpio, 0); + + mtl017->lvds_pwrctl_gpio = pdata->lvds_pwrctl_gpio; + ret = gpio_request(mtl017->lvds_pwrctl_gpio, "lcd_pwrctl"); + if (ret) { + dev_err(&i2c_client->dev, "failed to request lcd_pwrctl\n"); + goto err_lvds_reset; + } + gpio_direction_output(mtl017->lvds_pwrctl_gpio, 0); + + mtl017->lcd_pwr_gpio = pdata->lcd_pwr_gpio; + ret = gpio_request(mtl017->lcd_pwr_gpio, "lcd_pwron"); + if (ret) { + dev_err(&i2c_client->dev, "failed to request lcd_pwron\n"); + goto err_lcd_pwrctl; + } + gpio_direction_output(mtl017->lcd_pwr_gpio, 0); + + mtl017->lcd_en_gpio = pdata->lcd_en_gpio; + ret = gpio_request(mtl017->lcd_en_gpio, "lcd_en"); + if (ret) { + dev_err(&i2c_client->dev, "failed to request lcd_en\n"); + goto err_lcd_pwron; + } + gpio_direction_output(mtl017->lcd_en_gpio, 0); + + ret = mtl017_find_reg_settings(pdata, mtl017); + if (ret) + goto err_lcd_en; + + mtl017->lcd_dev = lcd_device_register("mtl017", &i2c_client->dev, + mtl017, &mtl017_lcd_ops); + mtl017_lcd_set_power(mtl017->lcd_dev, FB_BLANK_NORMAL); + + return 0; + +err_lcd_en: + gpio_free(mtl017->lcd_en_gpio); +err_lcd_pwron: + gpio_free(mtl017->lcd_pwr_gpio); +err_lcd_pwrctl: + gpio_free(mtl017->lvds_pwrctl_gpio); +err_lvds_reset: + gpio_free(mtl017->lvds_reset_gpio); +err_alloc: + return ret; +} + +static int mtl017_i2c_remove(struct i2c_client *client) +{ + struct mtl017_private *mtl017 = i2c_get_clientdata(client); + + lcd_device_unregister(mtl017->lcd_dev); + + gpio_free(mtl017->lcd_en_gpio); + gpio_free(mtl017->lcd_pwr_gpio); + gpio_free(mtl017->lvds_pwrctl_gpio); + gpio_free(mtl017->lvds_reset_gpio); + + kfree(mtl017->regs); + kfree(mtl017); + return 0; +} + +#ifdef CONFIG_PM +static int mtl017_i2c_suspend(struct i2c_client *client, pm_message_t message) +{ + struct mtl017_private *mtl017 = i2c_get_clientdata(client); + + mtl017->suspended = 1; + mtl017_lcd_set_power(mtl017->lcd_dev, mtl017->pwr); + + return 0; +} + +static int mtl017_i2c_resume(struct i2c_client *client) +{ + struct mtl017_private *mtl017 = i2c_get_clientdata(client); + + mtl017->suspended = 0; + mtl017_lcd_set_power(mtl017->lcd_dev, mtl017->pwr); + + return 0; +} + +#else +#define mtl017_i2c_suspend NULL +#define mtl017_i2c_resume NULL +#endif + +static const struct i2c_device_id mtl017_id[] = { + {"mtl017", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, mtl017_id); + +static struct i2c_driver mtl017_i2c_driver = { + .driver = { + .name = "mtl017", + .owner = THIS_MODULE, + }, + .id_table = mtl017_id, + .probe = mtl017_i2c_probe, + .remove = mtl017_i2c_remove, + .suspend = mtl017_i2c_suspend, + .resume = mtl017_i2c_resume, +}; + +static int __init mlt017_init(void) +{ + int ret; + + ret = i2c_add_driver(&mtl017_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "%s: can't add i2c driver\n", __func__); + return ret; + } + return 0; +} +module_init(mlt017_init); + +static void __exit mtl017_exit(void) +{ + i2c_del_driver(&mtl017_i2c_driver); +} +module_exit(mtl017_exit); + +MODULE_AUTHOR("Arnaud Patard "); +MODULE_DESCRIPTION("MTL017 LCD Driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mtl017.h b/include/linux/mtl017.h new file mode 100644 index 0000000..59bb531 --- /dev/null +++ b/include/linux/mtl017.h @@ -0,0 +1,27 @@ +#ifndef _LINUX_MTL017_H +#define _LINUX_MTL017_H + +enum lcd_init_mode { + INIT_MODE_AUO, + INIT_MODE_OTHER +}; + +struct lcd_panel_info { + char manufacture[16]; + char product_name[16]; + unsigned char *regs; + size_t regs_size; + enum lcd_init_mode init_mode; +}; + +struct mtl017_pdata { + int disp_i2c_bus_id; + int lvds_reset_gpio; + int lvds_pwrctl_gpio; + int lcd_pwr_gpio; + int lcd_en_gpio; + struct lcd_panel_info *lcd_infos; + int nr_lcd_infos; +}; + +#endif -- 1.7.4.4 From f1a2f2040ee717142ea3002cd8c6f59a8984c8e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:34:11 +0200 Subject: [PATCH 19/28] efikasb_fb --- arch/arm/mach-mx5/Kconfig | 2 + arch/arm/mach-mx5/board-mx51_efikasb.c | 108 ++++++++++- arch/arm/mach-mx5/efikasb_lcd.h | 355 ++++++++++++++++++++++++++++++++ 3 files changed, 464 insertions(+), 1 deletions(-) create mode 100644 arch/arm/mach-mx5/efikasb_lcd.h diff --git a/arch/arm/mach-mx5/Kconfig b/arch/arm/mach-mx5/Kconfig index 56477fb..3da5396 100644 --- a/arch/arm/mach-mx5/Kconfig +++ b/arch/arm/mach-mx5/Kconfig @@ -167,6 +167,8 @@ config MACH_MX51_EFIKASB bool "Support MX51 Genesi Efika Smartbook" select LEDS_GPIO_REGISTER select MX51_EFIKA_COMMON + select IMX_HAVE_PLATFORM_IMX_IPUV3 + select IMX_HAVE_PLATFORM_MXC_PWM help Include support for Genesi Efika Smartbook. This includes specific configurations for the board and its peripherals. diff --git a/arch/arm/mach-mx5/board-mx51_efikasb.c b/arch/arm/mach-mx5/board-mx51_efikasb.c index 8a9bca2..901b2de 100644 --- a/arch/arm/mach-mx5/board-mx51_efikasb.c +++ b/arch/arm/mach-mx5/board-mx51_efikasb.c @@ -29,11 +29,15 @@ #include #include #include -#include +#include +#include +#include +#include #include #include #include +#include #include #include @@ -44,6 +48,7 @@ #include "devices-imx51.h" #include "devices.h" #include "efika.h" +#include "efikasb_lcd.h" #define EFIKASB_USBH2_STP IMX_GPIO_NR(2, 20) #define EFIKASB_GREEN_LED IMX_GPIO_NR(1, 3) @@ -54,8 +59,15 @@ #define EFIKASB_LID IMX_GPIO_NR(3, 14) #define EFIKASB_POWEROFF IMX_GPIO_NR(4, 13) #define EFIKASB_RFKILL IMX_GPIO_NR(3, 1) +#define EFIKASB_LVDS_RESET IMX_GPIO_NR(3, 5) +#define EFIKASB_LVDS_PWR IMX_GPIO_NR(3, 7) +#define EFIKASB_LCD_PWR IMX_GPIO_NR(3, 13) +#define EFIKASB_LCD_BL_PWR IMX_GPIO_NR(4, 12) +#define EFIKASB_LCD_BL_PWM IMX_GPIO_NR(1, 2) +#define EFIKASB_LCD_LVDS_EN IMX_GPIO_NR(3, 12) #define MX51_PAD_PWRKEY IOMUX_PAD(0x48c, 0x0f8, 1, 0x0, 0, PAD_CTL_PUS_100K_UP | PAD_CTL_PKE) +#define EFIKASB_DISP_CLK IOMUX_PAD(0x754, 0x34C, 0, 0x0, 0, PAD_CTL_PKE|PAD_CTL_SRE_FAST) static iomux_v3_cfg_t mx51efikasb_pads[] = { /* USB HOST2 */ @@ -97,6 +109,19 @@ static iomux_v3_cfg_t mx51efikasb_pads[] = { /* BT */ MX51_PAD_EIM_A17__GPIO2_11, + + /* lvds reset */ + MX51_PAD_DISPB2_SER_DIN__GPIO3_5, + /* lvds power */ + MX51_PAD_DISPB2_SER_CLK__GPIO3_7, + /* lcd power on */ + MX51_PAD_CSI1_D9__GPIO3_13, + /* lcd bl power on */ + MX51_PAD_CSI2_D19__GPIO4_12, + /* lcd lvds en */ + MX51_PAD_CSI1_D8__GPIO3_12, + + EFIKASB_DISP_CLK, }; static int initialize_usbh2_port(struct platform_device *pdev) @@ -182,6 +207,83 @@ static const struct gpio_keys_platform_data mx51_efikasb_keys_data __initconst = .nbuttons = ARRAY_SIZE(mx51_efikasb_keys), }; +static struct mtl017_pdata mx51_efikasb_mtl017 = { + .disp_i2c_bus_id = 1, + .lvds_reset_gpio = EFIKASB_LVDS_RESET, + .lvds_pwrctl_gpio = EFIKASB_LVDS_PWR, + .lcd_pwr_gpio = EFIKASB_LCD_PWR, + .lcd_en_gpio = EFIKASB_LCD_LVDS_EN, + .lcd_infos = mx51_efikasb_lcd_infos, + .nr_lcd_infos = ARRAY_SIZE(mx51_efikasb_lcd_infos), +}; + +static struct i2c_board_info mx51_efikasb_i2c2_board_info[] __initdata = { + { + I2C_BOARD_INFO("mtl017", 0x3a), + .platform_data = &mx51_efikasb_mtl017, + }, +}; + +/* looks like the mtl017 is getting in our way here */ +/* this mode is very different from what I see in the edid */ +static struct fb_videomode mx51_efikasb_modes[] = { + { + .name = "1024x600", + .refresh = 60, + .xres = 1024, + .yres = 600, + .pixclock = 22800, + .left_margin = 80, + .right_margin = 40, + .upper_margin = 24, + .lower_margin = 21, + .hsync_len = 4, + .vsync_len = 4, + .sync = FB_SYNC_OE_LOW_ACT, + .vmode = FB_VMODE_NONINTERLACED, + }, +}; + +static struct ipuv3_fb_platform_data efikasb_fb1_data = { + .interface_pix_fmt = IPU_PIX_FMT_RGB565, + .flags = IMX_IPU_FB_USE_OVERLAY, + .display = 1, + .modes = mx51_efikasb_modes, + .num_modes = ARRAY_SIZE(mx51_efikasb_modes), +}; + +static struct imx_ipuv3_platform_data ipu_data = { + .fb_head1_platform_data = &efikasb_fb1_data, +}; + +static struct platform_pwm_backlight_data efikasb_pwm_backlight_data = { + .pwm_id = 0, + .max_brightness = 255, + .dft_brightness = 128, + .pwm_period_ns = 78770, +}; + +struct platform_device mxc_pwm_backlight_device = { + .name = "pwm-backlight", + .id = -1, +}; + +static void __init mx51_efikasb_display(void) +{ + iomux_v3_cfg_t pwm = MX51_PAD_GPIO1_2__PWM1_PWMO; + + gpio_request(EFIKASB_LCD_BL_PWR, "lcd_bl_pwron"); + gpio_direction_output(EFIKASB_LCD_BL_PWR, 1); + + imx51_add_ipuv3(&ipu_data); + + mxc_iomux_v3_setup_pad(pwm); + imx51_add_mxc_pwm(0); + mxc_register_device(&mxc_pwm_backlight_device, + &efikasb_pwm_backlight_data); + gpio_set_value(EFIKASB_LCD_BL_PWR, 0); +} + static struct regulator *pwgt1, *pwgt2; static void mx51_efikasb_power_off(void) @@ -246,11 +348,15 @@ static void __init efikasb_board_init(void) mxc_iomux_v3_setup_multiple_pads(mx51efikasb_pads, ARRAY_SIZE(mx51efikasb_pads)); + + i2c_register_board_info(1, mx51_efikasb_i2c2_board_info, + ARRAY_SIZE(mx51_efikasb_i2c2_board_info)); efika_board_common_init(); mx51_efikasb_board_id(); mx51_efikasb_usb(); imx51_add_sdhci_esdhc_imx(1, NULL); + mx51_efikasb_display(); gpio_led_register_device(-1, &mx51_efikasb_leds_data); imx_add_gpio_keys(&mx51_efikasb_keys_data); diff --git a/arch/arm/mach-mx5/efikasb_lcd.h b/arch/arm/mach-mx5/efikasb_lcd.h new file mode 100644 index 0000000..3b18a64 --- /dev/null +++ b/arch/arm/mach-mx5/efikasb_lcd.h @@ -0,0 +1,355 @@ +/* + * all this stuff is meant to be lcd _specific_ if I believe the code + * from pegatron _but_ for instance all the '44M to 53.9M' tables + * are _identical_ + */ + +static u8 mtl017_auo_tbl[] = { + /* ron: 66M to 66M */ +#if 0 + 0x00, 0x20, 0xAF, 0x59, 0x2B, 0xDE, 0x51, 0x00, + 0x00, 0x04, 0x17, 0x00, 0x58, 0x02, 0x00, 0x00, + 0x00, 0x21, 0x01, 0x08, 0x00, 0x1E, 0x01, 0x05, + 0x00, 0x01, 0x7C, 0x04, 0x32, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x20, 0xA8, 0x02, 0x12, 0x00, 0x58, + 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x02, 0x10, 0x01, 0x68, 0x03, 0xC2, 0x01, + 0x4A, 0x03, 0x46, 0x00, 0xF1, 0x01, 0x5C, 0x04, + 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x3A, + 0x18, 0x4B, 0x29, 0x5C, 0xDE, 0xF6, 0xE0, 0x1C, + 0x03, 0xFC, 0xE3, 0x1F, 0xF3, 0x75, 0x26, 0x45, + 0x4A, 0x91, 0x8A, 0xFF, 0x3F, 0x83, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x4E, 0x48, + 0x00, 0x00, 0x06, 0x01, 0x02, 0x00, 0x10, 0x04, + 0x02, 0x1F, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x04, 0x12, 0x00, 0x00, 0x03, + 0x02, 0x7C, 0x04, 0x98, 0x02, 0x11, 0x78, 0x18, + 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +#endif + + /* ron: 44MHz to 54MHz */ +#if 0 + 0x00, 0x20, 0xAF, 0x59, 0x2B, 0xDE, 0x51, 0x00, + 0x00, 0x04, 0x17, 0x00, 0x58, 0x02, 0x00, 0x00, + 0x00, 0x3B, 0x01, 0x08, 0x00, 0x1E, 0x01, 0x05, + 0x00, 0x01, 0x82, 0x05, 0x32, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x20, 0xA8, 0x02, 0x12, 0x00, 0x58, + 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x02, 0x10, 0x01, 0x68, 0x03, 0xC2, 0x01, + 0x4A, 0x03, 0x46, 0x00, 0xF1, 0x01, 0x5C, 0x04, + 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x3A, + 0x18, 0x4B, 0x29, 0x5C, 0xDE, 0xF6, 0xE0, 0x1C, + 0x03, 0xFC, 0xE3, 0x1F, 0xF3, 0x75, 0x26, 0x45, + 0x4A, 0x91, 0x8A, 0xFF, 0x3F, 0x83, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x4E, 0x48, + 0x00, 0x04, 0x2A, 0x01, 0x02, 0x00, 0x10, 0x04, + 0x02, 0x1F, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x04, 0x12, 0x00, 0x58, 0x02, + 0x02, 0x7C, 0x04, 0x7E, 0x02, 0x11, 0x78, 0x18, + 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +#endif + + /* ron: 44MHz to 48MHz */ +#if 0 + 0x00, 0x20, 0xAF, 0x59, 0x2B, 0xDE, 0x51, 0x00, + 0x00, 0x04, 0x16, 0x00, 0x59, 0x02, 0x00, 0x00, + 0x00, 0x21, 0x00, 0x09, 0x00, 0x1E, 0x01, 0x05, + 0x00, 0x01, 0xEA, 0x04, 0x32, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x20, 0xA8, 0x02, 0x12, 0x00, 0x58, + 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x02, 0x10, 0x01, 0x68, 0x03, 0xC2, 0x01, + 0x4A, 0x03, 0x46, 0x00, 0xF1, 0x01, 0x5C, 0x04, + 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x3A, + 0x18, 0x4B, 0x29, 0x5C, 0xDE, 0xF6, 0xE0, 0x1C, + 0x03, 0xFC, 0xE3, 0x1F, 0xF3, 0x75, 0x26, 0x45, + 0x4A, 0x91, 0x8A, 0xFF, 0x3F, 0x83, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x4E, 0x48, + 0x00, 0x02, 0x16, 0x01, 0x02, 0x00, 0x10, 0x04, + 0x02, 0x1F, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x04, 0x12, 0x00, 0x00, 0x03, + 0x02, 0x7C, 0x04, 0x7E, 0x02, 0x11, 0x78, 0x18, + 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +#endif + + /* ron: 44M to 53.8M */ +#if 0 + 0x00, 0x20, 0xAF, 0x59, 0x2B, 0xDE, 0x51, 0x00, + 0x00, 0x04, 0x17, 0x00, 0x58, 0x02, 0x00, 0x00, + 0x00, 0x3B, 0x01, 0x08, 0x00, 0x1E, 0x01, 0x05, + 0x00, 0x01, 0x7E, 0x05, 0x32, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x20, 0xA8, 0x02, 0x12, 0x00, 0x58, + 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x02, 0x10, 0x01, 0x68, 0x03, 0xC2, 0x01, + 0x4A, 0x03, 0x46, 0x00, 0xF1, 0x01, 0x5C, 0x04, + 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x3A, + 0x18, 0x4B, 0x29, 0x5C, 0xDE, 0xF6, 0xE0, 0x1C, + 0x03, 0xFC, 0xE3, 0x1F, 0xF3, 0x75, 0x26, 0x45, + 0x4A, 0x91, 0x8A, 0xFF, 0x3F, 0x83, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x4E, 0x48, + 0x00, 0x06, 0x3B, 0x01, 0x02, 0x00, 0x10, 0x04, + 0x02, 0x1F, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x04, 0x12, 0x00, 0x58, 0x02, + 0x02, 0x7C, 0x04, 0x98, 0x02, 0x11, 0x78, 0x18, + 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +#endif + + /* ron: 44M to 53.9M */ + 0x00, 0x20, 0xAF, 0x59, 0x2B, 0xDE, 0x51, 0x00, + 0x00, 0x04, 0x17, 0x00, 0x58, 0x02, 0x00, 0x00, + 0x00, 0x3B, 0x01, 0x08, 0x00, 0x1E, 0x01, 0x05, + 0x00, 0x01, 0x72, 0x05, 0x32, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x20, 0xA8, 0x02, 0x12, 0x00, 0x58, + 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x02, 0x10, 0x01, 0x68, 0x03, 0xC2, 0x01, + 0x4A, 0x03, 0x46, 0x00, 0xF1, 0x01, 0x5C, 0x04, + 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x3A, + 0x18, 0x4B, 0x29, 0x5C, 0xDE, 0xF6, 0xE0, 0x1C, + 0x03, 0xFC, 0xE3, 0x1F, 0xF3, 0x75, 0x26, 0x45, + 0x4A, 0x91, 0x8A, 0xFF, 0x3F, 0x83, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x4E, 0x48, + 0x00, 0x01, 0x10, 0x01, 0x00, 0x00, 0x10, 0x04, + 0x02, 0x1F, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x04, 0x12, 0x00, 0x58, 0x02, + 0x02, 0x7C, 0x04, 0x98, 0x02, 0x11, 0x78, 0x18, + 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +}; + +static u8 mtl017_cpt_tbl[] = { +#if 0 + 0x00, 0x20, 0xAF, 0x59, 0x2B, 0xDE, 0x51, 0x00, + 0x00, 0x04, 0x17, 0x00, 0x58, 0x02, 0x00, 0x00, + 0x00, 0x21, 0x01, 0x08, 0x00, 0x1E, 0x01, 0x05, + 0x00, 0x01, 0x7C, 0x04, 0x32, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x20, 0xA8, 0x02, 0x12, 0x00, 0x58, + 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x02, 0x10, 0x01, 0x68, 0x03, 0xC2, 0x01, + 0x4A, 0x03, 0x46, 0x00, 0xF1, 0x01, 0x5C, 0x04, + 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x3A, + 0x18, 0x4B, 0x29, 0x5C, 0xDE, 0xF6, 0xE0, 0x1C, + 0x03, 0xFC, 0xE3, 0x1F, 0xF3, 0x75, 0x26, 0x45, + 0x4A, 0x91, 0x8A, 0xFF, 0x3F, 0x83, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x4E, 0x48, + 0x00, 0x00, 0x06, 0x01, 0x02, 0x00, 0x10, 0x04, + 0x02, 0x1F, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x04, 0x12, 0x00, 0x00, 0x03, + 0x02, 0x7C, 0x04, 0x98, 0x02, 0x11, 0x78, 0x18, + 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +#endif + /* ron: 44M to 53.9M */ + 0x00, 0x20, 0xAF, 0x59, 0x2B, 0xDE, 0x51, 0x00, + 0x00, 0x04, 0x17, 0x00, 0x58, 0x02, 0x00, 0x00, + 0x00, 0x3B, 0x01, 0x08, 0x00, 0x1E, 0x01, 0x05, + 0x00, 0x01, 0x72, 0x05, 0x32, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x20, 0xA8, 0x02, 0x12, 0x00, 0x58, + 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x02, 0x10, 0x01, 0x68, 0x03, 0xC2, 0x01, + 0x4A, 0x03, 0x46, 0x00, 0xF1, 0x01, 0x5C, 0x04, + 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x3A, + 0x18, 0x4B, 0x29, 0x5C, 0xDE, 0xF6, 0xE0, 0x1C, + 0x03, 0xFC, 0xE3, 0x1F, 0xF3, 0x75, 0x26, 0x45, + 0x4A, 0x91, 0x8A, 0xFF, 0x3F, 0x83, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x4E, 0x48, + 0x00, 0x01, 0x10, 0x01, 0x00, 0x00, 0x10, 0x04, + 0x02, 0x1F, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x04, 0x12, 0x00, 0x58, 0x02, + 0x02, 0x7C, 0x04, 0x98, 0x02, 0x11, 0x78, 0x18, + 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +}; + +static u8 mtl017_auo_hires_tbl[] = { + 0x00, 0x20, 0xAF, 0x59, 0x2B, 0xDE, 0x3D, 0x00, + 0x00, 0x05, 0x0C, 0x00, 0xD0, 0x02, 0x00, 0x00, + 0x00, 0x05, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0A, + 0x00, 0x01, 0x70, 0x05, 0x3D, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x20, 0xF0, 0x02, 0x0C, 0x00, 0xD0, + 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x02, 0x10, 0x01, 0x68, 0x03, 0xC2, 0x01, + 0x4A, 0x03, 0x46, 0x00, 0xF1, 0x01, 0x5C, 0x04, + 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x3A, + 0x18, 0x4B, 0x29, 0x5C, 0xDE, 0xF6, 0xE0, 0x1C, + 0x03, 0xFC, 0xE3, 0x1F, 0xF3, 0x75, 0x26, 0x45, + 0x4A, 0x91, 0x8A, 0xFF, 0x3F, 0x83, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x4E, 0x48, + 0x00, 0x00, 0x06, 0x01, 0x02, 0x00, 0x12, 0x04, + 0x02, 0x1F, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, + 0x3D, 0x00, 0x00, 0x05, 0x0C, 0x00, 0xD0, 0x02, + 0x02, 0x70, 0x05, 0xE0, 0x02, 0x11, 0x78, 0x18, + 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +}; + +static u8 mtl017_cmo_tbl[] = { + /* ron: 44M to 53.9M */ + 0x00, 0x20, 0xAF, 0x59, 0x2B, 0xDE, 0x51, 0x00, + 0x00, 0x04, 0x17, 0x00, 0x58, 0x02, 0x00, 0x00, + 0x00, 0x3B, 0x01, 0x08, 0x00, 0x1E, 0x01, 0x05, + 0x00, 0x01, 0x72, 0x05, 0x32, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x20, 0xA8, 0x02, 0x12, 0x00, 0x58, + 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x02, 0x10, 0x01, 0x68, 0x03, 0xC2, 0x01, + 0x4A, 0x03, 0x46, 0x00, 0xF1, 0x01, 0x5C, 0x04, + 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x3A, + 0x18, 0x4B, 0x29, 0x5C, 0xDE, 0xF6, 0xE0, 0x1C, + 0x03, 0xFC, 0xE3, 0x1F, 0xF3, 0x75, 0x26, 0x45, + 0x4A, 0x91, 0x8A, 0xFF, 0x3F, 0x83, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x4E, 0x48, + 0x00, 0x01, 0x10, 0x01, 0x00, 0x00, 0x10, 0x04, + 0x02, 0x1F, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x04, 0x12, 0x00, 0x58, 0x02, + 0x02, 0x7C, 0x04, 0x98, 0x02, 0x11, 0x78, 0x18, + 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +}; + +static struct lcd_panel_info mx51_efikasb_lcd_infos[] = { + { + .manufacture = "AUO", + .product_name = "B101AW02 V0", + .init_mode = INIT_MODE_AUO, + .regs = mtl017_auo_tbl, + .regs_size = ARRAY_SIZE(mtl017_auo_tbl), + }, { + .manufacture = "CPT", + .product_name = "CLAA101NB03A", + .init_mode = INIT_MODE_OTHER, + .regs = mtl017_cpt_tbl, + .regs_size = ARRAY_SIZE(mtl017_cpt_tbl), + }, { + .manufacture = "AUO", + .product_name = "B101EW01", + .init_mode = INIT_MODE_OTHER, + .regs = mtl017_auo_hires_tbl, + .regs_size = ARRAY_SIZE(mtl017_auo_hires_tbl), + }, { + .manufacture = "CMO", + .product_name = "N101L6-L0D", + .init_mode = INIT_MODE_OTHER, + .regs = mtl017_cmo_tbl, + .regs_size = ARRAY_SIZE(mtl017_cmo_tbl), + } +}; + -- 1.7.4.4 From 45443d7efa9f6ca68dc113b726db5b156d10b945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:35:01 +0200 Subject: [PATCH 20/28] efikamx_mtdoops --- arch/arm/mach-mx5/mx51_efika.c | 5 +++++ 1 files changed, 5 insertions(+), 0 deletions(-) diff --git a/arch/arm/mach-mx5/mx51_efika.c b/arch/arm/mach-mx5/mx51_efika.c index 3e0c180..e3afeeb 100644 --- a/arch/arm/mach-mx5/mx51_efika.c +++ b/arch/arm/mach-mx5/mx51_efika.c @@ -259,6 +259,11 @@ static struct mtd_partition mx51_efika_spi_nor_partitions[] = { .offset = MTDPART_OFS_APPEND, .size = SZ_64K, }, + { + .name = "test", + .offset = MTDPART_OFS_APPEND, + .size = SZ_64K, + }, }; static struct flash_platform_data mx51_efika_spi_flash_data = { -- 1.7.4.4 From 8e14d31401d677d472990361d4b91818d892e3e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:35:55 +0200 Subject: [PATCH 21/28] keyboards_leds_trigger leds: add keyboard num/caps lock trigger Implement a led trigger which enable/disable a led according to keyboard leds events. Signed-off-by: Arnaud Patard --- drivers/leds/Kconfig | 7 ++ drivers/leds/Makefile | 1 + drivers/leds/ledtrig-keyleds.c | 172 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 0 deletions(-) create mode 100644 drivers/leds/ledtrig-keyleds.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index b591e72..53a23b7 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -450,6 +450,13 @@ config LEDS_TRIGGER_DEFAULT_ON This allows LEDs to be initialised in the ON state. If unsure, say Y. +config LEDS_TRIGGER_KEYBOARD + tristate "Keyboard LED Trigger" + depends on INPUT + help + This allows LEDs to be controller by keyboard caps/num lock + state. + comment "iptables trigger is under Netfilter config (LED target)" depends on LEDS_TRIGGERS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index bbfd2e3..99eacd5 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -54,3 +54,4 @@ obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT) += ledtrig-heartbeat.o obj-$(CONFIG_LEDS_TRIGGER_BACKLIGHT) += ledtrig-backlight.o obj-$(CONFIG_LEDS_TRIGGER_GPIO) += ledtrig-gpio.o obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON) += ledtrig-default-on.o +obj-$(CONFIG_LEDS_TRIGGER_KEYBOARD) += ledtrig-keyleds.o diff --git a/drivers/leds/ledtrig-keyleds.c b/drivers/leds/ledtrig-keyleds.c new file mode 100644 index 0000000..52504be --- /dev/null +++ b/drivers/leds/ledtrig-keyleds.c @@ -0,0 +1,172 @@ +/* + * ledtrig-keyleds.c - LED Trigger based on num/caps lock events + * + * Copyright 2010 Arnaud Patard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "leds.h" + +struct keyboard_led_data { + struct led_classdev *caps_led; + struct led_classdev *num_led; +}; + +static struct keyboard_led_data led_datas; + +static void keyboard_led_event(struct input_handle *handle, unsigned int type, + unsigned int code, int value) +{ + switch (code) { + case LED_CAPSL: + if (led_datas.caps_led) + led_set_brightness(led_datas.caps_led, + value ? LED_FULL : LED_OFF); + break; + case LED_NUML: + if (led_datas.num_led) + led_set_brightness(led_datas.num_led, + value ? LED_FULL : LED_OFF); + break; + } +} + +static int keyboard_led_connect(struct input_handler *handler, + struct input_dev *dev, const struct input_device_id *id) +{ + struct input_handle *handle; + int error; + + handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = "led-trigger"; + + error = input_register_handle(handle); + if (error) + goto err_free_handle; + + error = input_open_device(handle); + if (error) + goto err_unregister_handle; + + return 0; + + err_unregister_handle: + input_unregister_handle(handle); + err_free_handle: + kfree(handle); + return error; +} + +static void keyboard_led_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id keyboard_led_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT(EV_LED) }, + }, + { } +}; + +static struct input_handler keyboard_led_handler = { + .name = "led-trigger", + .event = keyboard_led_event, + .connect = keyboard_led_connect, + .disconnect = keyboard_led_disconnect, + .id_table = keyboard_led_ids, +}; + + +static void caps_trig_activate(struct led_classdev *led) +{ + led_datas.caps_led = led; +} + +static void caps_trig_deactivate(struct led_classdev *led) +{ + led_datas.caps_led = NULL; +} + +static struct led_trigger caps_led_trigger = { + .name = "caps", + .activate = caps_trig_activate, + .deactivate = caps_trig_deactivate, +}; + +static void num_trig_activate(struct led_classdev *led) +{ + led_datas.num_led = led; +} + +static void num_trig_deactivate(struct led_classdev *led) +{ + led_datas.num_led = NULL; +} + +static struct led_trigger num_led_trigger = { + .name = "num", + .activate = num_trig_activate, + .deactivate = num_trig_deactivate, +}; + +static int __init keyboard_led_trig_init(void) +{ + int ret; + + ret = led_trigger_register(&caps_led_trigger); + if (ret) + goto err; + + ret = led_trigger_register(&num_led_trigger); + if (ret) + goto err_caps; + + ret = input_register_handler(&keyboard_led_handler); + if (ret) + goto err_num; + + return 0; + +err_num: + led_trigger_unregister(&num_led_trigger); +err_caps: + led_trigger_unregister(&caps_led_trigger); +err: + printk(KERN_ERR "Failed to register led triggers %d\n", ret); + return ret; +} +module_init(keyboard_led_trig_init); + +static void __exit keyboard_led_trig_exit(void) +{ + input_unregister_handler(&keyboard_led_handler); + led_trigger_unregister(&num_led_trigger); + led_trigger_unregister(&caps_led_trigger); +} +module_exit(keyboard_led_trig_exit); + +MODULE_AUTHOR("Arnaud Patard "); +MODULE_DESCRIPTION("Keyboard LED trigger"); +MODULE_LICENSE("GPL"); -- 1.7.4.4 From e50ce5407685aeca679b6e39c2fe16991a286453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:36:51 +0200 Subject: [PATCH 22/28] battery_nekos_tree --- drivers/power/Kconfig | 6 + drivers/power/Makefile | 1 + drivers/power/efikasb_battery.c | 1024 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 1031 insertions(+), 0 deletions(-) create mode 100644 drivers/power/efikasb_battery.c diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 57de051..80d597a 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -249,4 +249,10 @@ config CHARGER_MAX8998 Say Y to enable support for the battery charger control sysfs and platform data of MAX8998/LP3974 PMICs. +config BATTERY_EFIKASB + tristate "Efika MX Smartbook battery driver" + depends on I2C + help + Say Y here to enable support for Efika MX Smartbook battery. + endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile index b4af13d..83bffc8 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -38,3 +38,4 @@ obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o +obj-$(CONFIG_BATTERY_EFIKASB) += efikasb_battery.o diff --git a/drivers/power/efikasb_battery.c b/drivers/power/efikasb_battery.c new file mode 100644 index 0000000..8864485 --- /dev/null +++ b/drivers/power/efikasb_battery.c @@ -0,0 +1,1024 @@ +/* + * Efika MX Smart Battery driver + * + * Copyright (C) 2009 Ron Lee + * + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SBS_MFG_ACCESS 0x00 +#define SBS_REMAIN_CAPACITY_ALARM 0x01 +#define SBS_REMNAIN_TIME_ALARM 0x02 +#define SBS_BATTERY_MODE 0x03 +#define SBS_AT_RATE 0x04 +#define SBS_AT_RATE_TIME_TO_FULL 0x05 +#define SBS_AT_RATE_TIME_TO_EMPTY 0x06 +#define SBS_AT_RATE_OK 0x07 +#define SBS_TEMP 0x08 +#define SBS_VOLTAGE 0x09 +#define SBS_CURRENT 0x0A +#define SBS_AVG_CURRENT 0x0B +#define SBS_MAX_ERROR 0x0C +#define SBS_REL_STATE_OF_CHARGE 0x0D +#define SBS_ABS_STATE_OF_CHARGE 0x0E +#define SBS_REMAIN_CAPABILITY 0x0F +#define SBS_FULL_CHARGE_CAPACITY 0x10 +#define SBS_RUN_TIME_TO_EMPTY 0x11 +#define SBS_AVG_TIME_TO_EMPTY 0x12 +#define SBS_AVG_TIME_TO_FULL 0x13 +#define SBS_CHARGE_CURRENT 0x14 +#define SBS_CHARGE_VOLTAGE 0x15 +#define SBS_BATTERY_STATUS 0x16 +#define SBS_CYCLE_COUNT 0x17 +#define SBS_DESIGN_CAPACITY 0x18 +#define SBS_DESIGN_VOLTAGE 0x19 +#define SBS_SPEC_INFO 0x1A +#define SBS_MFG_DATE 0x1B +#define SBS_SERIAL_NO 0x1C +#define SBS_MFG_NAME 0x20 +#define SBS_DEV_NAME 0x21 +#define SBS_DEV_CHEMISTRY 0x22 +#define SBS_MFG_DATA 0x23 +#define SBS_BATTERY_USAGE 0x30 +#define SBS_PERMANENT_FAILURE 0x31 +#define SBS_BATTERY_LOG1 0x32 +#define SBS_BATTERY_LOG2 0x33 +#define SBS_FET_TEMP 0x3B +#define SBS_OPTION_MFG_FUNC5 0x2F +#define SBS_OPTION_MFG_FUNC4 0x3C +#define SBS_OPTION_MFG_FUNC3 0x3D +#define SBS_OPTION_MFG_FUNC2 0x3E +#define SBS_OPTION_MFG_FUNC1 0x3F + +/* SBS Battery Mode Flags */ +#define SBS_CHARGE_CONTROLLER_ENABLED (1 << 9) +#define SBS_PRIMARY_BATTERY (1 << 10) +#define SBS_ALARM_MODE (1 << 13) +#define SBS_CHARGER_MODE (1 << 14) +#define SBS_CAPACITY_MODE (1 << 15) + +/* SBS_BATTERY_STATUS Register Bit Mapping */ +#define SBS_STATUS_OVER_CHARGE_ALARM 0x8000 +#define SBS_STATUS_TERM_CHARGE_ALARM 0x4000 +#define SBS_STATUS_OVER_TEMP_ALARM 0x1000 +#define SBS_STATUS_TERM_DISCHARGE_ALARM 0x0800 +#define SBS_STATUS_REMAIN_CAPACITY_ALARM 0x0200 +#define SBS_STATUS_REMAIN_TIME_ALARM 0x0100 + +#define SBS_STATUS_INITIALIZED 0x0080 +#define SBS_STATUS_DISCHARGING 0x0040 +#define SBS_STATUS_FULLY_CHARGED 0x0020 +#define SBS_STATUS_FULLY_DISCHARGED 0x0010 + +static void update_status_worker(struct work_struct *work); +static DECLARE_DELAYED_WORK(update_status_work, update_status_worker); + +static void batt_capacity_worker(struct work_struct *work); +static DECLARE_WORK(batt_capacity_work, batt_capacity_worker); + +static void power_off_worker(struct work_struct *work); +static DECLARE_DELAYED_WORK(power_off_work, power_off_worker); + +extern void kernel_power_off(void); + +struct sbs_reg_tbl { + char *name; + unsigned char reg_offset; +}; + +#ifdef EFIKASB_BATTERY_DEBUG +static struct sbs_reg_tbl reg_tbl[] = { + {"Manufacture Access", SBS_MFG_ACCESS}, + {"Remain Capacity Alarm", SBS_REMAIN_CAPACITY_ALARM }, + {"Remain Time Alarm", SBS_REMNAIN_TIME_ALARM}, + {"Battery Mode", SBS_BATTERY_MODE}, + {"AT Rate", SBS_AT_RATE}, + {"AT Rate Time To Full", SBS_AT_RATE_TIME_TO_FULL}, + {"AT Rate Time To Empty", SBS_AT_RATE_TIME_TO_EMPTY}, + {"AT Rate OK", SBS_AT_RATE_OK}, + {"Temperature", SBS_TEMP}, + {"Voltage", SBS_VOLTAGE}, + {"Current", SBS_CURRENT}, + {"Average Current", SBS_AVG_CURRENT}, + {"Maximum Error", SBS_MAX_ERROR}, + {"Relative State of Charge", SBS_REL_STATE_OF_CHARGE}, + {"Absolute State of Charge", SBS_ABS_STATE_OF_CHARGE}, + {"Remain Capacity", SBS_REMAIN_CAPABILITY}, + {"Full Charge Capacity", SBS_FULL_CHARGE_CAPACITY}, + {"Run Time To Empty", SBS_RUN_TIME_TO_EMPTY}, + {"Average Time To Empty", SBS_AVG_TIME_TO_EMPTY}, + {"Average Time To Full", SBS_AVG_TIME_TO_FULL}, + {"Charge Current", SBS_CHARGE_CURRENT}, + {"Charge Voltage", SBS_CHARGE_VOLTAGE}, + {"Battery Status", SBS_BATTERY_STATUS}, + {"Cycle Count", SBS_CYCLE_COUNT}, + {"Design Capacity", SBS_DESIGN_CAPACITY}, + {"Design Voltage", SBS_DESIGN_VOLTAGE}, + {"Spec Info", SBS_SPEC_INFO}, + {"Manufacture Date", SBS_MFG_DATE}, + {"Serial Number", SBS_SERIAL_NO}, + {"Manufacture Name", SBS_MFG_NAME}, + {"Device Name", SBS_DEV_NAME}, + {"Device Chemistry", SBS_DEV_CHEMISTRY}, + {"Manufacture Date", SBS_MFG_DATA}, + {"Battery Usage", SBS_BATTERY_USAGE}, + {"Permanent Failure", SBS_PERMANENT_FAILURE}, + {"Battery Log1", SBS_BATTERY_LOG1}, + {"Battery Log2", SBS_BATTERY_LOG2}, + {"FET Temperature", SBS_FET_TEMP}, + {"Optional Mfg Func5", SBS_OPTION_MFG_FUNC5}, + {"Optional Mfg Func4", SBS_OPTION_MFG_FUNC4}, + {"Optional Mfg Func3", SBS_OPTION_MFG_FUNC3}, + {"Optional Mfg Func2", SBS_OPTION_MFG_FUNC2}, + {"Optional Mfg Func1", SBS_OPTION_MFG_FUNC1}, + +}; + +void dump_sbs_reg(struct i2c_client *client) +{ + int i; + unsigned int value; + + printk("Dump Smart Battery Register\n"); + for (i = 0; i < ARRAY_SIZE(reg_tbl); i ++) { + value = i2c_smbus_read_word_data(client, reg_tbl[i].reg_offset); + printk("%s: 0x%04x\n", reg_tbl[i].name, value); + } +} +#else +void dump_sbs_reg(struct i2c_client *client) {} +#endif + +struct efikasb_batt_dev_info; + +struct efikasb_batt_dev_info { + struct device *dev; + struct i2c_client *client; + struct power_supply *bat; + struct power_supply *ac_charger; + char mfg_name[32]; + char model_name[32]; + char serial[32]; + char chemistry[32]; + + int batt_in_irq; + int batt_low_irq; + int ac_in_irq; + + int batt_in; + int ac_in; + int batt_low; + u32 capacity; + + int (*get_batt_in_status) (void); + int (*get_batt_low_status) (void); + int (*get_ac_in_status) (void); + void (*set_batt_low_led) (int); + + struct timer_list batt_low_timer; +}; + +static struct efikasb_batt_dev_info *batt = NULL; + +static enum power_supply_property efikasb_batt_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + /* POWER_SUPPLY_PROP_HEALTH, */ + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, +}; + +static enum power_supply_property efikasb_ac_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static int efikasb_batt_read(struct i2c_client *client, u8 reg, u32 *value) +{ + int ret; + int retry = 5; + + /* ron: retry i2c again to avoid conflict with PIC */ + do { + ret = i2c_smbus_read_word_data(client, reg); + } while (ret < 0 && --retry > 0); + + if (ret < 0) + return ret; + + *value = ret; + return 0; +} + +static int efikasb_batt_write(struct i2c_client *client, u8 reg, u32 value) +{ + int ret; + int retry = 5; + + do { + ret = i2c_smbus_write_word_data(client, reg, value); + } while (ret < 0 && --retry > 0); + + return ret; +} + +/* Smart Battery Helper Function */ +static int efikasb_batt_get_status(struct efikasb_batt_dev_info *di, u32 *value) +{ + return efikasb_batt_read(di->client, SBS_BATTERY_STATUS, value); +} + +static int efikasb_batt_get_current(struct efikasb_batt_dev_info *di, u32 *value) +{ + return efikasb_batt_read(di->client, SBS_CURRENT, value); +} + +static int efikasb_batt_get_voltage(struct efikasb_batt_dev_info *di, u32 *value) +{ + return efikasb_batt_read(di->client, SBS_VOLTAGE, value); +} + +static int efikasb_batt_get_average_current(struct efikasb_batt_dev_info *di, u32 *value) +{ + return efikasb_batt_read(di->client, SBS_AVG_CURRENT, value); +} + +static int efikasb_batt_get_capacity(struct efikasb_batt_dev_info *di, u32 *value) +{ + return efikasb_batt_read(di->client, SBS_REL_STATE_OF_CHARGE, value); +} + +static int efikasb_batt_get_temperature(struct efikasb_batt_dev_info *di, u32 *value) +{ + return efikasb_batt_read(di->client, SBS_TEMP, value); +} + +static int efikasb_batt_run_time_to_empty(struct efikasb_batt_dev_info *di, u32 *value) +{ + return efikasb_batt_read(di->client, SBS_RUN_TIME_TO_EMPTY, value); +} + +static int efikasb_batt_avg_time_to_empty(struct efikasb_batt_dev_info *di, u32 *value) +{ + return efikasb_batt_read(di->client, SBS_AVG_TIME_TO_EMPTY, value); +} + +static int efikasb_batt_avg_time_to_full(struct efikasb_batt_dev_info *di, u32 *value) +{ + return efikasb_batt_read(di->client, SBS_AVG_TIME_TO_FULL, value); +} + +static int efikasb_batt_get_serial_no(struct efikasb_batt_dev_info *di) +{ + int ret; + u32 sn; + + ret = efikasb_batt_read(di->client, SBS_SERIAL_NO, &sn); + if(ret != 0) + return ret; + + sprintf(di->serial, "%d", sn); + + return ret; +} + +static int efikasb_batt_get_mfg_name(struct efikasb_batt_dev_info *di) +{ + int ret; + char mfg[33]; + + ret = i2c_smbus_read_i2c_block_data(di->client, SBS_MFG_NAME, 32, mfg); + if(ret < 0) + return ret; + + if(mfg[0] > sizeof(di->mfg_name)) + return -EINVAL; + + strncpy(di->mfg_name, &mfg[1], mfg[0]); + return 0; +} + +static int efikasb_batt_get_model_name(struct efikasb_batt_dev_info *di) +{ + int ret; + char model[33]; + + ret = i2c_smbus_read_i2c_block_data(di->client, SBS_DEV_NAME, 32, model); + if(ret < 0) + return ret; + + if(model[0] > sizeof(di->model_name)) + return -EINVAL; + + strncpy(di->model_name, &model[1], model[0]); + return 0; +} + +static int efikasb_batt_get_technology(struct efikasb_batt_dev_info *di) +{ + int ret; + char chem[33]; + + ret = i2c_smbus_read_i2c_block_data(di->client, SBS_DEV_CHEMISTRY, 32, chem); + if(ret < 0) + return ret; + + if(chem[0] > sizeof(di->chemistry)) + return -EINVAL; + + strncpy(di->chemistry, &chem[1], chem[0]); + /* printk("Technology: %s\n", di->chemistry); */ + + if (!strcasecmp("NiCd", di->chemistry)) + return POWER_SUPPLY_TECHNOLOGY_NiCd; + if (!strcasecmp("NiMH", di->chemistry)) + return POWER_SUPPLY_TECHNOLOGY_NiMH; + if (!strcasecmp("LION", di->chemistry)) + return POWER_SUPPLY_TECHNOLOGY_LION; + if (!strncasecmp("LI-ION", di->chemistry, 6)) + return POWER_SUPPLY_TECHNOLOGY_LION; + if (!strcasecmp("LiP", di->chemistry)) + return POWER_SUPPLY_TECHNOLOGY_LIPO; + return POWER_SUPPLY_TECHNOLOGY_UNKNOWN; +} + +static int efikasb_batt_get_energy_now(struct efikasb_batt_dev_info *di, + u32 *value) +{ + int ret; + u32 battery_mode; + + ret = efikasb_batt_read(di->client, SBS_BATTERY_MODE, &battery_mode); + if (ret < 0) + return ret; + + efikasb_batt_write(di->client, SBS_BATTERY_MODE, + battery_mode | SBS_CAPACITY_MODE); + + ret = efikasb_batt_read(di->client, SBS_REMAIN_CAPABILITY, value); + if (ret == 0) { + *value *= 10000; /* 10 mWh -> uWh */ + } + + return ret; +} + +static int efikasb_batt_get_energy_full(struct efikasb_batt_dev_info *di, + u32 *value) +{ + int ret; + u32 battery_mode; + + ret = efikasb_batt_read(di->client, SBS_BATTERY_MODE, &battery_mode); + if (ret < 0) + return ret; + + efikasb_batt_write(di->client, SBS_BATTERY_MODE, + battery_mode | SBS_CAPACITY_MODE); + + ret = efikasb_batt_read(di->client, SBS_FULL_CHARGE_CAPACITY, value); + if (ret == 0) { + *value *= 10000; /* 10 mWh -> uWh */ + } + + return ret; +} + +static int efikasb_batt_get_energy_full_design(struct efikasb_batt_dev_info *di, + u32 *value) +{ + int ret; + u32 battery_mode; + + ret = efikasb_batt_read(di->client, SBS_BATTERY_MODE, &battery_mode); + if (ret < 0) + return ret; + + efikasb_batt_write(di->client, SBS_BATTERY_MODE, + battery_mode | SBS_CAPACITY_MODE); + + ret = efikasb_batt_read(di->client, SBS_DESIGN_CAPACITY, value); + if (ret == 0) { + *value *= 10000; /* 10 mWh -> uWh */ + } + + return ret; +} + +static int efikasb_batt_get_charge_now(struct efikasb_batt_dev_info *di, + u32 *value) +{ + int ret; + u32 battery_mode; + + ret = efikasb_batt_read(di->client, SBS_BATTERY_MODE, &battery_mode); + if (ret < 0) + return ret; + + efikasb_batt_write(di->client, SBS_BATTERY_MODE, + battery_mode & SBS_CAPACITY_MODE); + + ret = efikasb_batt_read(di->client, SBS_REMAIN_CAPABILITY, value); + if (ret == 0) { + *value *= 1000; /* mAh -> uAh */ + } + + return ret; +} + +static int efikasb_batt_get_charge_full(struct efikasb_batt_dev_info *di, + u32 *value) +{ + int ret; + u32 battery_mode; + + ret = efikasb_batt_read(di->client, SBS_BATTERY_MODE, &battery_mode); + if (ret < 0) + return ret; + + efikasb_batt_write(di->client, SBS_BATTERY_MODE, + battery_mode & SBS_CAPACITY_MODE); + + ret = efikasb_batt_read(di->client, SBS_FULL_CHARGE_CAPACITY, value); + if (ret == 0) { + *value *= 1000; /* mAh -> uAh */ + } + + return ret; +} + +static int efikasb_batt_get_charge_full_design(struct efikasb_batt_dev_info *di, + u32 *value) +{ + int ret; + u32 battery_mode; + + ret = efikasb_batt_read(di->client, SBS_BATTERY_MODE, &battery_mode); + if (ret < 0) + return ret; + + efikasb_batt_write(di->client, SBS_BATTERY_MODE, + battery_mode & SBS_CAPACITY_MODE); + + ret = efikasb_batt_read(di->client, SBS_DESIGN_CAPACITY, value); + if (ret == 0) { + *value *= 1000; /* mAh -> uAh */ + } + + return ret; +} + +#define to_efikasb_batt_device_info(x) container_of((x), \ + struct efikasb_batt_dev_info, bat); + +static int efikasb_batt_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct efikasb_batt_dev_info *di = batt; + u32 value; + int ret; + int batt_in, ac_in; + + batt_in = di->get_batt_in_status(); + + if (!batt_in) { + val->intval = 0; + return -ENODEV; + } + + ac_in = di->get_ac_in_status(); + + switch(psp) { + case POWER_SUPPLY_PROP_PRESENT: + val->intval = di->get_batt_in_status(); + break; + case POWER_SUPPLY_PROP_STATUS: + if (ac_in) { + ret = efikasb_batt_get_status(di, &value); + if (ret != 0) { + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + return 0; + } + if (value & SBS_STATUS_FULLY_CHARGED) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_CHARGING; + } else { + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = efikasb_batt_get_technology(di); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: /* mV */ + ret = efikasb_batt_get_voltage(di, &value); + if(ret != 0) { + val->intval = 0; + break; + } + val->intval = value; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: /* mA */ + ret = efikasb_batt_get_current(di, &value); + if(ret != 0) { + val->intval = 0; + return ret; + } + val->intval = value; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: /* mA in 1 minute rolling avg */ + ret = efikasb_batt_get_average_current(di, &value); + if(ret != 0) + return ret; + val->intval = value; + break; + case POWER_SUPPLY_PROP_CAPACITY: /* % percent */ + ret = efikasb_batt_get_capacity(di, &value); + if(ret != 0) + return ret; + val->intval = value; + di->capacity = value; /* ron: cache the battery capacity */ + break; + case POWER_SUPPLY_PROP_TEMP: /* K degree */ + ret = efikasb_batt_get_temperature(di, &value); + if(ret != 0) + return ret; + val->intval = value; + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + ret = efikasb_batt_run_time_to_empty(di, &value);/* minutes */ + if(ret != 0) + return ret; + val->intval = value * 60; + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + ret = efikasb_batt_avg_time_to_empty(di, &value); /* minutes */ + if(ret != 0) + return ret; + val->intval = value * 60; + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: + ret = efikasb_batt_avg_time_to_full(di, &value); /* minutes */ + if(ret != 0) + return ret; + val->intval = value * 60; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + ret = efikasb_batt_get_model_name(di); + if(ret < 0) + return ret; + val->strval = di->model_name; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + ret = efikasb_batt_get_mfg_name(di); + if(ret < 0) + return ret; + val->strval = di->mfg_name; + break; + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + ret = efikasb_batt_get_serial_no(di); + if(ret < 0) + return ret; + val->strval = di->serial; + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + ret = efikasb_batt_get_energy_now(di, &value); + if (ret != 0) + return ret; + val->intval = value; + break; + case POWER_SUPPLY_PROP_ENERGY_FULL: + ret = efikasb_batt_get_energy_full(di, &value); + if (ret != 0) + return ret; + val->intval = value; + break; + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + ret = efikasb_batt_get_energy_full_design(di, &value); + if (ret != 0) + return ret; + val->intval = value; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + ret = efikasb_batt_get_charge_now(di, &value); + if (ret != 0) + return ret; + val->intval = value; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + ret = efikasb_batt_get_charge_full(di, &value); + if (ret != 0) + return ret; + val->intval = value; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + ret = efikasb_batt_get_charge_full_design(di, &value); + if (ret != 0) + return ret; + val->intval = value; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int efikasb_ac_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct efikasb_batt_dev_info *di = batt; + + switch(psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->get_ac_in_status(); + break; + default: + return -EINVAL; + break; + } + + return 0; +} + +static struct power_supply efikasb_batt = { + .name = "efikasb_battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = efikasb_batt_props, + .num_properties = ARRAY_SIZE(efikasb_batt_props), + .get_property = efikasb_batt_get_property, + .external_power_changed = NULL, + +}; + +static struct power_supply efikasb_ac_charger = { + .name = "efikasb_ac_charger", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = efikasb_ac_charger_props, + .num_properties = ARRAY_SIZE(efikasb_ac_charger_props), + .get_property = efikasb_ac_charger_get_property, + .external_power_changed = NULL, +}; + +static void update_status_worker(struct work_struct *work) +{ + power_supply_changed(batt->ac_charger); + power_supply_changed(batt->bat); +} + + +static irqreturn_t efikasb_ac_detect_handler(int irq, void *data) +{ + struct efikasb_batt_dev_info *di = data; + + di->ac_in = di->get_ac_in_status(); + printk("efikasb_ac_charger: AC %s\n", + di->ac_in ? "Inserted" : "Removed"); + + schedule_delayed_work(&update_status_work, msecs_to_jiffies(1500)); + + if (di->ac_in) { + if(di->set_batt_low_led) + di->set_batt_low_led(0); + set_irq_type(irq, IRQ_TYPE_EDGE_RISING); + mod_timer(&di->batt_low_timer, jiffies + 10); + } else { + mod_timer(&di->batt_low_timer, jiffies + 10); + set_irq_type(irq, IRQ_TYPE_EDGE_FALLING); + } + + return IRQ_HANDLED; +} + +static irqreturn_t efikasb_batt_detect_handler(int irq, void *data) +{ + struct efikasb_batt_dev_info *di = data; + + di->batt_in = di->get_batt_in_status(); + printk("efikasb_battery: Battery %s\n", + di->batt_in ? "Inserted" : "Removed"); + + schedule_delayed_work(&update_status_work, msecs_to_jiffies(1500)); + + if (di->batt_in) { + mod_timer(&di->batt_low_timer, jiffies + 10); + set_irq_type(irq, IRQ_TYPE_EDGE_RISING); + } else { + if(di->set_batt_low_led) + di->set_batt_low_led(0); + set_irq_type(irq, IRQ_TYPE_EDGE_FALLING); + } + + return IRQ_HANDLED; +} + +static void power_off_worker(struct work_struct *work) +{ + if(!batt->ac_in && batt->batt_low && batt->capacity == 0) { + sys_sync(); + kernel_power_off(); + } +} + +static irqreturn_t efikasb_batt_low_handler(int irq, void *data) +{ + struct efikasb_batt_dev_info *di = data; + + di->batt_low = batt->get_batt_low_status(); + printk("efikasb_batter: Battery %s\n", + di->batt_low ? "Low" : "Normal"); + + if (di->batt_low) { + set_irq_type(irq, IRQ_TYPE_LEVEL_HIGH); + } else { + set_irq_type(irq, IRQ_TYPE_LEVEL_LOW); + } + + return IRQ_HANDLED; +} + +static void batt_capacity_worker(struct work_struct *work) +{ + int ret; + u32 capacity; + + if(batt->batt_in) { + ret = efikasb_batt_get_capacity(batt, &capacity); + if(ret == 0) + batt->capacity = capacity; + + if(!batt->ac_in && batt->capacity == 0) { + sys_sync(); + kernel_power_off(); + } + } + +} + +static void batt_low_fn(unsigned long data) +{ + struct efikasb_batt_dev_info *di = (struct efikasb_batt_dev_info *)data; + + if(!di->batt_in) + return; + + if(!di->ac_in && di->batt_in) { /* battery is discharging */ + schedule_work(&batt_capacity_work); + if(di->set_batt_low_led) { + if(di->capacity <= 10) + di->set_batt_low_led(1); + else + di->set_batt_low_led(0); + + if(di->capacity <= 11 && di->capacity > 9) { + mod_timer(&di->batt_low_timer, jiffies + 4 * HZ); + } + + if(di->capacity <= 9 || di->capacity > 11) { + mod_timer(&di->batt_low_timer, jiffies + 60 * HZ); + } + } else { + mod_timer(&di->batt_low_timer, jiffies + 60 * HZ); + } + } else if(di->batt_in) { /* battery is charging */ + mod_timer(&di->batt_low_timer, jiffies + 60 * HZ); + } + + /* battery is not charging and capacity is critical low, power off immediately */ + if(!di->ac_in && di->batt_in && di->capacity == 0) + schedule_delayed_work(&power_off_work, msecs_to_jiffies(1000)); + +} + +static int efikasb_batt_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct efikasb_batt_dev_info *di; + struct mxc_battery_platform_data *platform_data; + int retval = 0; + + platform_data = (struct mxc_battery_platform_data *)client->dev.platform_data; + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if(!di) { + dev_err(&client->dev, "failed to allocate device info\n"); + retval = -ENOMEM; + return retval; + } + + batt = di; + + i2c_set_clientdata(client, di); + di->dev = &client->dev; + di->client = client; + di->bat = &efikasb_batt; + di->ac_charger = &efikasb_ac_charger; + + di->get_batt_in_status = platform_data->get_batt_in_status; + di->get_batt_low_status = platform_data->get_batt_low_status; + di->get_ac_in_status = platform_data->get_ac_in_status; + di->set_batt_low_led = platform_data->set_batt_low_led; + + if(di->get_batt_in_status) + di->batt_in = di->get_batt_in_status(); + if(di->get_ac_in_status) + di->ac_in = di->get_ac_in_status(); + if(di->get_batt_low_status) + di->batt_low = di->get_batt_low_status(); + + di->batt_in_irq = platform_data->batt_in_irq; + di->batt_low_irq = platform_data->batt_low_irq; + di->ac_in_irq = platform_data->ac_in_irq; + +#ifndef CONFIG_EFIKASB_EXPERIMENTAL_OS + if(di->batt_in) { + retval = request_irq(di->batt_in_irq, + efikasb_batt_detect_handler, \ + IRQ_TYPE_EDGE_RISING, + "efikasb_battery", di); + } else { + retval = request_irq(di->batt_in_irq, + efikasb_batt_detect_handler, + IRQ_TYPE_EDGE_FALLING, + "efikasb_battery", di); + } + if(retval) + goto batt_irq_failed; + + if(di->ac_in) { + retval = request_irq(di->ac_in_irq, + efikasb_ac_detect_handler, + IRQ_TYPE_EDGE_RISING, + "efikasb_ac_charger", di); + } else { + retval = request_irq(di->ac_in_irq, + efikasb_ac_detect_handler, + IRQ_TYPE_EDGE_FALLING, + "efikasb_ac_charger", di); + } + if(retval) + goto ac_irq_failed; + + if(di->batt_low) { + retval = request_irq(di->batt_low_irq, + efikasb_batt_low_handler, + IRQ_TYPE_LEVEL_HIGH, "efikasb_batt_low", di); + } else { + retval = request_irq(di->batt_low_irq, + efikasb_batt_low_handler, + IRQ_TYPE_LEVEL_LOW, "efikasb_batt_low", di); + } + if(retval) + goto batt_low_irq_failed; + + /* ron: the battery low pin is triggered at 10% battery capacity, + battery critical low won't generate wake up event, + the battery power may be exhauseted */ + /* enable_irq_wake(di->batt_low_irq); */ +#endif + + retval = power_supply_register(&client->dev, &efikasb_batt); + if(retval) { + dev_err(&client->dev, "failed to register battery\n"); + goto batt_failed; + } + + retval =power_supply_register(&client->dev, &efikasb_ac_charger); + if(retval) { + dev_err(&client->dev, "failed to register ac charger\n"); + goto ac_charger_failed; + } + if(di->batt_in) { + efikasb_batt_get_mfg_name(di); + efikasb_batt_get_model_name(di); + efikasb_batt_get_capacity(di, &di->capacity); + } + + + printk("Probe Efika MX Smartbook Battery: %s %s %s: %d%%, AC %s\n", + di->mfg_name, di->model_name, + di->batt_in ? "Inserted" : "Removed", di->capacity, + di->ac_in ? "Inserted" : "Removed"); + + dump_sbs_reg(client); + + init_timer(&di->batt_low_timer); + di->batt_low_timer.data = (unsigned long)di; + di->batt_low_timer.function = batt_low_fn; + di->batt_low_timer.expires = jiffies + 10; + if(!di->ac_in && di->batt_in) + add_timer(&di->batt_low_timer); + + /* ron: if battery critical low, shutdown immediately */ + if(!di->ac_in && di->batt_low && di->capacity == 0) { + printk("Battery critical low, shutdown now....\n"); + schedule_delayed_work(&power_off_work, msecs_to_jiffies(1000)); + } + + return 0; + +ac_charger_failed: + power_supply_unregister(&efikasb_batt); +batt_failed: + free_irq(di->batt_low_irq, NULL); +batt_low_irq_failed: + free_irq(di->ac_in_irq, NULL); +ac_irq_failed: + free_irq(di->batt_in_irq, NULL); +batt_irq_failed: + i2c_set_clientdata(client, NULL); + kfree(di); + return retval; +} + +static int efikasb_batt_i2c_remove(struct i2c_client *client) +{ + struct efikasb_batt_dev_info *di = i2c_get_clientdata(client); + + power_supply_unregister(&efikasb_batt); + power_supply_unregister(&efikasb_ac_charger); + free_irq(di->batt_in_irq, NULL); + free_irq(di->ac_in_irq, NULL); + kfree(di); + batt = NULL; + + return 0; +} + +static const struct i2c_device_id efikasb_batt_id[] = { + { "efikasb-battery", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, efikasb_batt_id); + +static struct i2c_driver efikasb_batt_i2c_driver = { + .driver = { + .name = "efikasb-battery", + }, + .probe = efikasb_batt_i2c_probe, + .remove = efikasb_batt_i2c_remove, + .id_table = efikasb_batt_id, +}; + +static int __init efikasb_batt_init(void) +{ + int ret; + + ret = i2c_add_driver(&efikasb_batt_i2c_driver); + if(ret) + printk(KERN_ERR "Unable to register Efika MX Smartbook Battery Driver\n"); + + return ret; +} +module_init(efikasb_batt_init); + +static void __exit efikasb_batt_exit(void) +{ + i2c_del_driver(&efikasb_batt_i2c_driver); + +} +module_exit(efikasb_batt_exit); + +MODULE_AUTHOR("Ron Lee "); +MODULE_DESCRIPTION("Efika MX Smart Battery Driver"); +MODULE_LICENSE("GPL"); + -- 1.7.4.4 From 5855a43ffe30b0c982dc838b8101c6ad952e8a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:37:51 +0200 Subject: [PATCH 23/28] battery_fixes --- arch/arm/mach-mx5/board-mx51_efikasb.c | 32 ++ drivers/power/efikasb_battery.c | 673 ++++++++++++++++---------------- include/linux/efikasb.h | 13 + 3 files changed, 373 insertions(+), 345 deletions(-) create mode 100644 include/linux/efikasb.h diff --git a/arch/arm/mach-mx5/board-mx51_efikasb.c b/arch/arm/mach-mx5/board-mx51_efikasb.c index 901b2de..8bc8a41 100644 --- a/arch/arm/mach-mx5/board-mx51_efikasb.c +++ b/arch/arm/mach-mx5/board-mx51_efikasb.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -65,9 +66,21 @@ #define EFIKASB_LCD_BL_PWR IMX_GPIO_NR(4, 12) #define EFIKASB_LCD_BL_PWM IMX_GPIO_NR(1, 2) #define EFIKASB_LCD_LVDS_EN IMX_GPIO_NR(3, 12) +#define EFIKASB_BATT_INS IMX_GPIO_NR(3, 6) +#define EFIKASB_AC_ADAP_INS IMX_GPIO_NR(3, 3) +#define EFIKASB_BATT_LOW IMX_GPIO_NR(3, 0) #define MX51_PAD_PWRKEY IOMUX_PAD(0x48c, 0x0f8, 1, 0x0, 0, PAD_CTL_PUS_100K_UP | PAD_CTL_PKE) #define EFIKASB_DISP_CLK IOMUX_PAD(0x754, 0x34C, 0, 0x0, 0, PAD_CTL_PKE|PAD_CTL_SRE_FAST) +/* + * getting ac in gpio working sucks : + * iomux set to sion+alt mode 4 doesn't work + * iomux set to alt mode 4 but sion not set doesn't work + * iomux set to sion+alt mode 0 does work + * this sion bit just getting on my nerves. + */ +#define EFIKASB_AC_IN IOMUX_PAD(0x6B4, 0x2B4, 0x10, 0x980, 1, NO_PAD_CTRL) +#define EFIKASB_BATT_LOW_PAD IOMUX_PAD(0x6A8, 0x2A8, 0x10, 0x0, 0, NO_PAD_CTRL) static iomux_v3_cfg_t mx51efikasb_pads[] = { /* USB HOST2 */ @@ -122,6 +135,13 @@ static iomux_v3_cfg_t mx51efikasb_pads[] = { MX51_PAD_CSI1_D8__GPIO3_12, EFIKASB_DISP_CLK, + + /* bat low */ + EFIKASB_BATT_LOW_PAD, + /* bat ins */ + MX51_PAD_DISPB2_SER_DIO__GPIO3_6, + /* ac in */ + EFIKASB_AC_IN, }; static int initialize_usbh2_port(struct platform_device *pdev) @@ -217,10 +237,22 @@ static struct mtl017_pdata mx51_efikasb_mtl017 = { .nr_lcd_infos = ARRAY_SIZE(mx51_efikasb_lcd_infos), }; +static struct efikasb_battery_platform_data efikasb_batt_data = { + .batt_gpio = EFIKASB_BATT_INS, + .batt_inverted = 1, + .ac_gpio = EFIKASB_AC_ADAP_INS, + .ac_inverted = 1, + .batt_low_gpio = EFIKASB_BATT_LOW, + .batt_low_inverted = 1, +}; + static struct i2c_board_info mx51_efikasb_i2c2_board_info[] __initdata = { { I2C_BOARD_INFO("mtl017", 0x3a), .platform_data = &mx51_efikasb_mtl017, + }, { + I2C_BOARD_INFO("efikasb-battery", 0x0b), + .platform_data = &efikasb_batt_data, }, }; diff --git a/drivers/power/efikasb_battery.c b/drivers/power/efikasb_battery.c index 8864485..79da47b 100644 --- a/drivers/power/efikasb_battery.c +++ b/drivers/power/efikasb_battery.c @@ -17,16 +17,16 @@ #include #include #include -#include -#include #include #include -#include #include -#include +#include +#include #include -#include -#include +#include +#include + +#warning ac stuff should be converted into pda_power #define SBS_MFG_ACCESS 0x00 #define SBS_REMAIN_CAPACITY_ALARM 0x01 @@ -92,23 +92,20 @@ #define SBS_STATUS_FULLY_CHARGED 0x0020 #define SBS_STATUS_FULLY_DISCHARGED 0x0010 +#define MAX_I2C_TRIES 5 + static void update_status_worker(struct work_struct *work); static DECLARE_DELAYED_WORK(update_status_work, update_status_worker); static void batt_capacity_worker(struct work_struct *work); static DECLARE_WORK(batt_capacity_work, batt_capacity_worker); -static void power_off_worker(struct work_struct *work); -static DECLARE_DELAYED_WORK(power_off_work, power_off_worker); - -extern void kernel_power_off(void); - +#ifdef DEBUG struct sbs_reg_tbl { char *name; unsigned char reg_offset; }; -#ifdef EFIKASB_BATTERY_DEBUG static struct sbs_reg_tbl reg_tbl[] = { {"Manufacture Access", SBS_MFG_ACCESS}, {"Remain Capacity Alarm", SBS_REMAIN_CAPACITY_ALARM }, @@ -171,10 +168,7 @@ void dump_sbs_reg(struct i2c_client *client) void dump_sbs_reg(struct i2c_client *client) {} #endif -struct efikasb_batt_dev_info; - struct efikasb_batt_dev_info { - struct device *dev; struct i2c_client *client; struct power_supply *bat; struct power_supply *ac_charger; @@ -183,20 +177,9 @@ struct efikasb_batt_dev_info { char serial[32]; char chemistry[32]; - int batt_in_irq; - int batt_low_irq; - int ac_in_irq; - - int batt_in; - int ac_in; - int batt_low; + struct efikasb_battery_platform_data pdata; u32 capacity; - int (*get_batt_in_status) (void); - int (*get_batt_low_status) (void); - int (*get_ac_in_status) (void); - void (*set_batt_low_led) (int); - struct timer_list batt_low_timer; }; @@ -233,7 +216,7 @@ static enum power_supply_property efikasb_ac_charger_props[] = { static int efikasb_batt_read(struct i2c_client *client, u8 reg, u32 *value) { int ret; - int retry = 5; + int retry = MAX_I2C_TRIES; /* ron: retry i2c again to avoid conflict with PIC */ do { @@ -250,7 +233,7 @@ static int efikasb_batt_read(struct i2c_client *client, u8 reg, u32 *value) static int efikasb_batt_write(struct i2c_client *client, u8 reg, u32 value) { int ret; - int retry = 5; + int retry = MAX_I2C_TRIES; do { ret = i2c_smbus_write_word_data(client, reg, value); @@ -259,6 +242,88 @@ static int efikasb_batt_write(struct i2c_client *client, u8 reg, u32 value) return ret; } +static int efikasb_batt_set_mah_mode(struct efikasb_batt_dev_info *di) +{ + int ret; + u32 battery_mode; + + ret = efikasb_batt_read(di->client, SBS_BATTERY_MODE, &battery_mode); + if (ret < 0) + return ret; + + battery_mode &= ~SBS_CAPACITY_MODE; + return efikasb_batt_write(di->client, SBS_BATTERY_MODE, battery_mode); +} + +static int efikasb_batt_set_10mwh_mode(struct efikasb_batt_dev_info *di) +{ + int ret; + u32 battery_mode; + + ret = efikasb_batt_read(di->client, SBS_BATTERY_MODE, &battery_mode); + if (ret < 0) + return ret; + + battery_mode |= SBS_CAPACITY_MODE; + return efikasb_batt_write(di->client, SBS_BATTERY_MODE, battery_mode); +} + +/* + * functions used to convert read values into units expected by power_supply + * class + */ +static int efikasb_batt_read_time_reg(struct efikasb_batt_dev_info *di, + unsigned char reg, u32 *value) +{ + int ret; + + ret = efikasb_batt_read(di->client, reg, value); + if (!ret) { + /* + * sbs allows to return no error but to report 65535 when + * invalid. For instance, reading time to empty when charging + */ + if (*value == 65535) + ret = -ENODATA; + else + *value *= 60; /* min -> secs */ + } + + return ret; +} + +static int efikasb_read_uah_reg(struct efikasb_batt_dev_info *di, + unsigned char reg, u32 *value) +{ + int ret; + + ret = efikasb_batt_set_mah_mode(di); + if (ret < 0) + return ret; + + ret = efikasb_batt_read(di->client, reg, value); + if (ret == 0) + *value *= 1000; /* 10 mAh -> uAh */ + + return ret; +} + +static int efikasb_read_uwh_reg(struct efikasb_batt_dev_info *di, + unsigned char reg, u32 *value) +{ + int ret; + + ret = efikasb_batt_set_10mwh_mode(di); + if (ret < 0) + return ret; + + ret = efikasb_batt_read(di->client, reg, value); + if (ret == 0) + *value *= 10000; /* 10 mWh -> uWh */ + + return ret; +} + /* Smart Battery Helper Function */ static int efikasb_batt_get_status(struct efikasb_batt_dev_info *di, u32 *value) { @@ -267,17 +332,41 @@ static int efikasb_batt_get_status(struct efikasb_batt_dev_info *di, u32 *value) static int efikasb_batt_get_current(struct efikasb_batt_dev_info *di, u32 *value) { - return efikasb_batt_read(di->client, SBS_CURRENT, value); + int ret; + + ret = efikasb_batt_read(di->client, SBS_CURRENT, value); + if (ret) + *value = 0; + else + *value *= 1000; /* mA -> uA */ + + return ret; } static int efikasb_batt_get_voltage(struct efikasb_batt_dev_info *di, u32 *value) { - return efikasb_batt_read(di->client, SBS_VOLTAGE, value); + int ret; + + ret = efikasb_batt_read(di->client, SBS_VOLTAGE, value); + if (ret) + *value = 0; + else + *value *= 1000; /* mV -> uV */ + + return ret; } static int efikasb_batt_get_average_current(struct efikasb_batt_dev_info *di, u32 *value) { - return efikasb_batt_read(di->client, SBS_AVG_CURRENT, value); + int ret; + + ret = efikasb_batt_read(di->client, SBS_AVG_CURRENT, value); + if (ret) + *value = 0; + else + *value *= 1000; /* mA -> uA */ + + return ret; } static int efikasb_batt_get_capacity(struct efikasb_batt_dev_info *di, u32 *value) @@ -285,24 +374,38 @@ static int efikasb_batt_get_capacity(struct efikasb_batt_dev_info *di, u32 *valu return efikasb_batt_read(di->client, SBS_REL_STATE_OF_CHARGE, value); } +/* + * 0.1K to 0.1C + * power_supply wants tenths of degrees C not K + */ +#define KELVIN_TO_CELSIUS 2731 static int efikasb_batt_get_temperature(struct efikasb_batt_dev_info *di, u32 *value) { - return efikasb_batt_read(di->client, SBS_TEMP, value); + int ret; + + ret = efikasb_batt_read(di->client, SBS_TEMP, value); + if (!ret) + /* to 0.1 C */ + *value -= 2731; + return ret; } -static int efikasb_batt_run_time_to_empty(struct efikasb_batt_dev_info *di, u32 *value) +static int efikasb_batt_run_time_to_empty(struct efikasb_batt_dev_info *di, + u32 *value) { - return efikasb_batt_read(di->client, SBS_RUN_TIME_TO_EMPTY, value); + return efikasb_batt_read_time_reg(di, SBS_RUN_TIME_TO_EMPTY, value); } -static int efikasb_batt_avg_time_to_empty(struct efikasb_batt_dev_info *di, u32 *value) +static int efikasb_batt_avg_time_to_empty(struct efikasb_batt_dev_info *di, + u32 *value) { - return efikasb_batt_read(di->client, SBS_AVG_TIME_TO_EMPTY, value); + return efikasb_batt_read_time_reg(di, SBS_AVG_TIME_TO_EMPTY, value); } -static int efikasb_batt_avg_time_to_full(struct efikasb_batt_dev_info *di, u32 *value) +static int efikasb_batt_avg_time_to_full(struct efikasb_batt_dev_info *di, + u32 *value) { - return efikasb_batt_read(di->client, SBS_AVG_TIME_TO_FULL, value); + return efikasb_batt_read_time_reg(di, SBS_AVG_TIME_TO_FULL, value); } static int efikasb_batt_get_serial_no(struct efikasb_batt_dev_info *di) @@ -311,10 +414,10 @@ static int efikasb_batt_get_serial_no(struct efikasb_batt_dev_info *di) u32 sn; ret = efikasb_batt_read(di->client, SBS_SERIAL_NO, &sn); - if(ret != 0) + if (ret != 0) return ret; - sprintf(di->serial, "%d", sn); + sprintf(di->serial, "%x", sn); return ret; } @@ -325,10 +428,10 @@ static int efikasb_batt_get_mfg_name(struct efikasb_batt_dev_info *di) char mfg[33]; ret = i2c_smbus_read_i2c_block_data(di->client, SBS_MFG_NAME, 32, mfg); - if(ret < 0) + if (ret < 0) return ret; - if(mfg[0] > sizeof(di->mfg_name)) + if (mfg[0] > sizeof(di->mfg_name)) return -EINVAL; strncpy(di->mfg_name, &mfg[1], mfg[0]); @@ -341,10 +444,10 @@ static int efikasb_batt_get_model_name(struct efikasb_batt_dev_info *di) char model[33]; ret = i2c_smbus_read_i2c_block_data(di->client, SBS_DEV_NAME, 32, model); - if(ret < 0) + if (ret < 0) return ret; - if(model[0] > sizeof(di->model_name)) + if (model[0] > sizeof(di->model_name)) return -EINVAL; strncpy(di->model_name, &model[1], model[0]); @@ -356,15 +459,15 @@ static int efikasb_batt_get_technology(struct efikasb_batt_dev_info *di) int ret; char chem[33]; - ret = i2c_smbus_read_i2c_block_data(di->client, SBS_DEV_CHEMISTRY, 32, chem); - if(ret < 0) + ret = i2c_smbus_read_i2c_block_data(di->client, + SBS_DEV_CHEMISTRY, 32, chem); + if (ret < 0) return ret; - if(chem[0] > sizeof(di->chemistry)) + if (chem[0] > sizeof(di->chemistry)) return -EINVAL; strncpy(di->chemistry, &chem[1], chem[0]); - /* printk("Technology: %s\n", di->chemistry); */ if (!strcasecmp("NiCd", di->chemistry)) return POWER_SUPPLY_TECHNOLOGY_NiCd; @@ -376,137 +479,76 @@ static int efikasb_batt_get_technology(struct efikasb_batt_dev_info *di) return POWER_SUPPLY_TECHNOLOGY_LION; if (!strcasecmp("LiP", di->chemistry)) return POWER_SUPPLY_TECHNOLOGY_LIPO; + /* label says li-ion but chemitry is returning unknown type */ + if (!strcasecmp("LGC0", di->chemistry)) + return POWER_SUPPLY_TECHNOLOGY_LION; + + dev_warn(&di->client->dev, "unknown battery chemistry %s\n", + di->chemistry); + return POWER_SUPPLY_TECHNOLOGY_UNKNOWN; } static int efikasb_batt_get_energy_now(struct efikasb_batt_dev_info *di, u32 *value) { - int ret; - u32 battery_mode; - - ret = efikasb_batt_read(di->client, SBS_BATTERY_MODE, &battery_mode); - if (ret < 0) - return ret; - - efikasb_batt_write(di->client, SBS_BATTERY_MODE, - battery_mode | SBS_CAPACITY_MODE); - - ret = efikasb_batt_read(di->client, SBS_REMAIN_CAPABILITY, value); - if (ret == 0) { - *value *= 10000; /* 10 mWh -> uWh */ - } - - return ret; + return efikasb_read_uwh_reg(di, SBS_REMAIN_CAPABILITY, value); } static int efikasb_batt_get_energy_full(struct efikasb_batt_dev_info *di, u32 *value) { - int ret; - u32 battery_mode; - - ret = efikasb_batt_read(di->client, SBS_BATTERY_MODE, &battery_mode); - if (ret < 0) - return ret; - - efikasb_batt_write(di->client, SBS_BATTERY_MODE, - battery_mode | SBS_CAPACITY_MODE); - - ret = efikasb_batt_read(di->client, SBS_FULL_CHARGE_CAPACITY, value); - if (ret == 0) { - *value *= 10000; /* 10 mWh -> uWh */ - } - - return ret; + return efikasb_read_uwh_reg(di, SBS_FULL_CHARGE_CAPACITY, value); } static int efikasb_batt_get_energy_full_design(struct efikasb_batt_dev_info *di, u32 *value) { - int ret; - u32 battery_mode; - - ret = efikasb_batt_read(di->client, SBS_BATTERY_MODE, &battery_mode); - if (ret < 0) - return ret; - - efikasb_batt_write(di->client, SBS_BATTERY_MODE, - battery_mode | SBS_CAPACITY_MODE); - - ret = efikasb_batt_read(di->client, SBS_DESIGN_CAPACITY, value); - if (ret == 0) { - *value *= 10000; /* 10 mWh -> uWh */ - } - - return ret; + return efikasb_read_uwh_reg(di, SBS_DESIGN_CAPACITY, value); } static int efikasb_batt_get_charge_now(struct efikasb_batt_dev_info *di, u32 *value) { - int ret; - u32 battery_mode; - - ret = efikasb_batt_read(di->client, SBS_BATTERY_MODE, &battery_mode); - if (ret < 0) - return ret; - - efikasb_batt_write(di->client, SBS_BATTERY_MODE, - battery_mode & SBS_CAPACITY_MODE); - - ret = efikasb_batt_read(di->client, SBS_REMAIN_CAPABILITY, value); - if (ret == 0) { - *value *= 1000; /* mAh -> uAh */ - } - - return ret; + return efikasb_read_uah_reg(di, SBS_REMAIN_CAPABILITY, value); } static int efikasb_batt_get_charge_full(struct efikasb_batt_dev_info *di, u32 *value) { - int ret; - u32 battery_mode; - - ret = efikasb_batt_read(di->client, SBS_BATTERY_MODE, &battery_mode); - if (ret < 0) - return ret; - - efikasb_batt_write(di->client, SBS_BATTERY_MODE, - battery_mode & SBS_CAPACITY_MODE); - - ret = efikasb_batt_read(di->client, SBS_FULL_CHARGE_CAPACITY, value); - if (ret == 0) { - *value *= 1000; /* mAh -> uAh */ - } - - return ret; + return efikasb_read_uah_reg(di, SBS_FULL_CHARGE_CAPACITY, value); } static int efikasb_batt_get_charge_full_design(struct efikasb_batt_dev_info *di, u32 *value) { - int ret; - u32 battery_mode; + return efikasb_read_uah_reg(di, SBS_DESIGN_CAPACITY, value); +} - ret = efikasb_batt_read(di->client, SBS_BATTERY_MODE, &battery_mode); - if (ret < 0) - return ret; +static int efikasb_get_ac_status(struct efikasb_batt_dev_info *di) +{ + if (di->pdata.ac_inverted) + return !gpio_get_value(di->pdata.ac_gpio); - efikasb_batt_write(di->client, SBS_BATTERY_MODE, - battery_mode & SBS_CAPACITY_MODE); + return gpio_get_value(di->pdata.ac_gpio); +} - ret = efikasb_batt_read(di->client, SBS_DESIGN_CAPACITY, value); - if (ret == 0) { - *value *= 1000; /* mAh -> uAh */ - } +static int efikasb_battery_inserted(struct efikasb_batt_dev_info *di) +{ + if (di->pdata.batt_inverted) + return !gpio_get_value(di->pdata.batt_gpio); - return ret; + return gpio_get_value(di->pdata.batt_gpio); } -#define to_efikasb_batt_device_info(x) container_of((x), \ - struct efikasb_batt_dev_info, bat); +/* change value a 10% of battery left */ +static int efikasb_battery_low(struct efikasb_batt_dev_info *di) +{ + if (di->pdata.batt_low_inverted) + return !gpio_get_value(di->pdata.batt_low_gpio); + + return gpio_get_value(di->pdata.batt_low_gpio); +} static int efikasb_batt_get_property(struct power_supply *psy, enum power_supply_property psp, @@ -514,24 +556,20 @@ static int efikasb_batt_get_property(struct power_supply *psy, { struct efikasb_batt_dev_info *di = batt; u32 value; - int ret; - int batt_in, ac_in; - - batt_in = di->get_batt_in_status(); + int ret, batt_in; + batt_in = efikasb_battery_inserted(di); if (!batt_in) { val->intval = 0; return -ENODEV; } - ac_in = di->get_ac_in_status(); - switch(psp) { case POWER_SUPPLY_PROP_PRESENT: - val->intval = di->get_batt_in_status(); + val->intval = efikasb_battery_inserted(di); break; case POWER_SUPPLY_PROP_STATUS: - if (ac_in) { + if (efikasb_get_ac_status(di)) { ret = efikasb_batt_get_status(di, &value); if (ret != 0) { val->intval = POWER_SUPPLY_STATUS_UNKNOWN; @@ -541,81 +579,84 @@ static int efikasb_batt_get_property(struct power_supply *psy, val->intval = POWER_SUPPLY_STATUS_FULL; else val->intval = POWER_SUPPLY_STATUS_CHARGING; - } else { + } else val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - } break; case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = efikasb_batt_get_technology(di); + ret = efikasb_batt_get_technology(di); + if (ret < 0) { + val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + return 0; + } + val->intval = ret; break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: /* mV */ + case POWER_SUPPLY_PROP_VOLTAGE_NOW: ret = efikasb_batt_get_voltage(di, &value); - if(ret != 0) { + if (ret != 0) { val->intval = 0; - break; + return ret; } val->intval = value; break; - case POWER_SUPPLY_PROP_CURRENT_NOW: /* mA */ + case POWER_SUPPLY_PROP_CURRENT_NOW: ret = efikasb_batt_get_current(di, &value); - if(ret != 0) { + if (ret != 0) { val->intval = 0; return ret; } val->intval = value; break; - case POWER_SUPPLY_PROP_CURRENT_AVG: /* mA in 1 minute rolling avg */ + case POWER_SUPPLY_PROP_CURRENT_AVG: ret = efikasb_batt_get_average_current(di, &value); - if(ret != 0) + if (ret != 0) return ret; val->intval = value; break; case POWER_SUPPLY_PROP_CAPACITY: /* % percent */ ret = efikasb_batt_get_capacity(di, &value); - if(ret != 0) + if (ret != 0) return ret; val->intval = value; - di->capacity = value; /* ron: cache the battery capacity */ break; - case POWER_SUPPLY_PROP_TEMP: /* K degree */ + case POWER_SUPPLY_PROP_TEMP: ret = efikasb_batt_get_temperature(di, &value); - if(ret != 0) + if (ret != 0) return ret; val->intval = value; break; case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: - ret = efikasb_batt_run_time_to_empty(di, &value);/* minutes */ - if(ret != 0) + ret = efikasb_batt_run_time_to_empty(di, &value); + if (ret != 0) return ret; - val->intval = value * 60; + val->intval = value; break; case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: - ret = efikasb_batt_avg_time_to_empty(di, &value); /* minutes */ - if(ret != 0) + ret = efikasb_batt_avg_time_to_empty(di, &value); + if (ret != 0) return ret; - val->intval = value * 60; + val->intval = value; break; case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: - ret = efikasb_batt_avg_time_to_full(di, &value); /* minutes */ - if(ret != 0) + ret = efikasb_batt_avg_time_to_full(di, &value); + if (ret != 0) return ret; - val->intval = value * 60; + val->intval = value; break; case POWER_SUPPLY_PROP_MODEL_NAME: ret = efikasb_batt_get_model_name(di); - if(ret < 0) + if (ret != 0) return ret; val->strval = di->model_name; break; case POWER_SUPPLY_PROP_MANUFACTURER: ret = efikasb_batt_get_mfg_name(di); - if(ret < 0) + if (ret != 0) return ret; val->strval = di->mfg_name; break; case POWER_SUPPLY_PROP_SERIAL_NUMBER: ret = efikasb_batt_get_serial_no(di); - if(ret < 0) + if (ret != 0) return ret; val->strval = di->serial; break; @@ -670,7 +711,7 @@ static int efikasb_ac_charger_get_property(struct power_supply *psy, switch(psp) { case POWER_SUPPLY_PROP_ONLINE: - val->intval = di->get_ac_in_status(); + val->intval = efikasb_get_ac_status(di); break; default: return -EINVAL; @@ -681,7 +722,7 @@ static int efikasb_ac_charger_get_property(struct power_supply *psy, } static struct power_supply efikasb_batt = { - .name = "efikasb_battery", + .name = "main-battery", .type = POWER_SUPPLY_TYPE_BATTERY, .properties = efikasb_batt_props, .num_properties = ARRAY_SIZE(efikasb_batt_props), @@ -705,26 +746,18 @@ static void update_status_worker(struct work_struct *work) power_supply_changed(batt->bat); } - static irqreturn_t efikasb_ac_detect_handler(int irq, void *data) { struct efikasb_batt_dev_info *di = data; + int ac_in; - di->ac_in = di->get_ac_in_status(); - printk("efikasb_ac_charger: AC %s\n", - di->ac_in ? "Inserted" : "Removed"); + ac_in = efikasb_get_ac_status(di); + dev_info(&di->client->dev, "AC %s\n", + ac_in ? "inserted" : "removed"); schedule_delayed_work(&update_status_work, msecs_to_jiffies(1500)); - if (di->ac_in) { - if(di->set_batt_low_led) - di->set_batt_low_led(0); - set_irq_type(irq, IRQ_TYPE_EDGE_RISING); - mod_timer(&di->batt_low_timer, jiffies + 10); - } else { - mod_timer(&di->batt_low_timer, jiffies + 10); - set_irq_type(irq, IRQ_TYPE_EDGE_FALLING); - } + mod_timer(&di->batt_low_timer, jiffies + 10); return IRQ_HANDLED; } @@ -732,46 +765,32 @@ static irqreturn_t efikasb_ac_detect_handler(int irq, void *data) static irqreturn_t efikasb_batt_detect_handler(int irq, void *data) { struct efikasb_batt_dev_info *di = data; + int batt_in; - di->batt_in = di->get_batt_in_status(); - printk("efikasb_battery: Battery %s\n", - di->batt_in ? "Inserted" : "Removed"); + batt_in = efikasb_battery_inserted(di); + dev_info(&di->client->dev, "Battery %s\n", + batt_in ? "inserted" : "removed"); schedule_delayed_work(&update_status_work, msecs_to_jiffies(1500)); - if (di->batt_in) { - mod_timer(&di->batt_low_timer, jiffies + 10); - set_irq_type(irq, IRQ_TYPE_EDGE_RISING); - } else { - if(di->set_batt_low_led) - di->set_batt_low_led(0); - set_irq_type(irq, IRQ_TYPE_EDGE_FALLING); - } + mod_timer(&di->batt_low_timer, jiffies + 10); return IRQ_HANDLED; } -static void power_off_worker(struct work_struct *work) -{ - if(!batt->ac_in && batt->batt_low && batt->capacity == 0) { - sys_sync(); - kernel_power_off(); - } -} - static irqreturn_t efikasb_batt_low_handler(int irq, void *data) { struct efikasb_batt_dev_info *di = data; + int batt_low; - di->batt_low = batt->get_batt_low_status(); - printk("efikasb_batter: Battery %s\n", - di->batt_low ? "Low" : "Normal"); + batt_low = efikasb_battery_low(di); + dev_info(&di->client->dev, "Battery %s\n", + batt_low ? "low" : "normal"); - if (di->batt_low) { - set_irq_type(irq, IRQ_TYPE_LEVEL_HIGH); - } else { - set_irq_type(irq, IRQ_TYPE_LEVEL_LOW); - } + if (batt_low) + irq_set_irq_type(irq, IRQ_TYPE_LEVEL_HIGH); + else + irq_set_irq_type(irq, IRQ_TYPE_LEVEL_LOW); return IRQ_HANDLED; } @@ -781,163 +800,123 @@ static void batt_capacity_worker(struct work_struct *work) int ret; u32 capacity; - if(batt->batt_in) { + if (efikasb_battery_inserted(batt)) { ret = efikasb_batt_get_capacity(batt, &capacity); - if(ret == 0) + if (ret == 0) batt->capacity = capacity; - - if(!batt->ac_in && batt->capacity == 0) { - sys_sync(); - kernel_power_off(); - } } - } static void batt_low_fn(unsigned long data) { struct efikasb_batt_dev_info *di = (struct efikasb_batt_dev_info *)data; + int batt_in, ac_in; - if(!di->batt_in) + batt_in = efikasb_battery_inserted(di); + if (!batt_in) return; - if(!di->ac_in && di->batt_in) { /* battery is discharging */ + ac_in = efikasb_get_ac_status(di); + if (!ac_in) schedule_work(&batt_capacity_work); - if(di->set_batt_low_led) { - if(di->capacity <= 10) - di->set_batt_low_led(1); - else - di->set_batt_low_led(0); - - if(di->capacity <= 11 && di->capacity > 9) { - mod_timer(&di->batt_low_timer, jiffies + 4 * HZ); - } - - if(di->capacity <= 9 || di->capacity > 11) { - mod_timer(&di->batt_low_timer, jiffies + 60 * HZ); - } - } else { - mod_timer(&di->batt_low_timer, jiffies + 60 * HZ); - } - } else if(di->batt_in) { /* battery is charging */ - mod_timer(&di->batt_low_timer, jiffies + 60 * HZ); - } - - /* battery is not charging and capacity is critical low, power off immediately */ - if(!di->ac_in && di->batt_in && di->capacity == 0) - schedule_delayed_work(&power_off_work, msecs_to_jiffies(1000)); + mod_timer(&di->batt_low_timer, jiffies + 60 * HZ); } static int efikasb_batt_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct efikasb_batt_dev_info *di; - struct mxc_battery_platform_data *platform_data; - int retval = 0; + struct efikasb_battery_platform_data *pdata; + int ac_in, batt_in, batt_low, retval = 0; + unsigned long edge; - platform_data = (struct mxc_battery_platform_data *)client->dev.platform_data; + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->dev, "no platform data provided\n"); + return -EINVAL; + } di = kzalloc(sizeof(*di), GFP_KERNEL); - if(!di) { + if (!di) { dev_err(&client->dev, "failed to allocate device info\n"); - retval = -ENOMEM; - return retval; + return -ENOMEM; } batt = di; i2c_set_clientdata(client, di); - di->dev = &client->dev; di->client = client; di->bat = &efikasb_batt; di->ac_charger = &efikasb_ac_charger; + memcpy(&di->pdata, pdata, sizeof(struct efikasb_battery_platform_data)); - di->get_batt_in_status = platform_data->get_batt_in_status; - di->get_batt_low_status = platform_data->get_batt_low_status; - di->get_ac_in_status = platform_data->get_ac_in_status; - di->set_batt_low_led = platform_data->set_batt_low_led; - - if(di->get_batt_in_status) - di->batt_in = di->get_batt_in_status(); - if(di->get_ac_in_status) - di->ac_in = di->get_ac_in_status(); - if(di->get_batt_low_status) - di->batt_low = di->get_batt_low_status(); - - di->batt_in_irq = platform_data->batt_in_irq; - di->batt_low_irq = platform_data->batt_low_irq; - di->ac_in_irq = platform_data->ac_in_irq; - -#ifndef CONFIG_EFIKASB_EXPERIMENTAL_OS - if(di->batt_in) { - retval = request_irq(di->batt_in_irq, - efikasb_batt_detect_handler, \ - IRQ_TYPE_EDGE_RISING, - "efikasb_battery", di); - } else { - retval = request_irq(di->batt_in_irq, - efikasb_batt_detect_handler, - IRQ_TYPE_EDGE_FALLING, - "efikasb_battery", di); - } - if(retval) - goto batt_irq_failed; - - if(di->ac_in) { - retval = request_irq(di->ac_in_irq, - efikasb_ac_detect_handler, - IRQ_TYPE_EDGE_RISING, - "efikasb_ac_charger", di); - } else { - retval = request_irq(di->ac_in_irq, - efikasb_ac_detect_handler, - IRQ_TYPE_EDGE_FALLING, - "efikasb_ac_charger", di); + retval = gpio_request(pdata->batt_gpio, "batt insert"); + if (retval) + goto err_alloc; + + retval = gpio_request(pdata->ac_gpio, "ac insert"); + if (retval) + goto err_batt_gpio; + + retval = gpio_request(pdata->batt_low_gpio, "batt low"); + if (retval) + goto err_ac_gpio; + + gpio_direction_input(pdata->batt_gpio); + gpio_direction_input(pdata->ac_gpio); + gpio_direction_input(pdata->batt_low_gpio); + + ac_in = efikasb_get_ac_status(di); + + batt_in = efikasb_battery_inserted(di); + if (batt_in) { + efikasb_batt_get_mfg_name(di); + efikasb_batt_get_model_name(di); + efikasb_batt_get_capacity(di, &di->capacity); } - if(retval) + + retval = request_irq(gpio_to_irq(pdata->batt_gpio), + efikasb_batt_detect_handler, + IRQ_TYPE_EDGE_BOTH, "efikasb_battery", di); + if (retval) + goto err_batt_low_gpio; + + retval = request_irq(gpio_to_irq(pdata->ac_gpio), + efikasb_ac_detect_handler, + IRQ_TYPE_EDGE_BOTH, "efikasb_ac_charger", di); + if (retval) goto ac_irq_failed; - if(di->batt_low) { - retval = request_irq(di->batt_low_irq, - efikasb_batt_low_handler, - IRQ_TYPE_LEVEL_HIGH, "efikasb_batt_low", di); - } else { - retval = request_irq(di->batt_low_irq, - efikasb_batt_low_handler, - IRQ_TYPE_LEVEL_LOW, "efikasb_batt_low", di); - } - if(retval) - goto batt_low_irq_failed; + batt_low = efikasb_battery_low(di); + if (batt_low) + edge = IRQ_TYPE_LEVEL_HIGH; + else + edge = IRQ_TYPE_LEVEL_LOW; - /* ron: the battery low pin is triggered at 10% battery capacity, - battery critical low won't generate wake up event, - the battery power may be exhauseted */ - /* enable_irq_wake(di->batt_low_irq); */ -#endif + retval = request_irq(gpio_to_irq(pdata->batt_low_gpio), + efikasb_batt_low_handler, + edge, "efikasb_batt_low", di); + if (retval) + goto batt_low_irq_failed; retval = power_supply_register(&client->dev, &efikasb_batt); - if(retval) { + if (retval) { dev_err(&client->dev, "failed to register battery\n"); goto batt_failed; } - retval =power_supply_register(&client->dev, &efikasb_ac_charger); - if(retval) { + retval = power_supply_register(&client->dev, &efikasb_ac_charger); + if (retval) { dev_err(&client->dev, "failed to register ac charger\n"); goto ac_charger_failed; } - if(di->batt_in) { - efikasb_batt_get_mfg_name(di); - efikasb_batt_get_model_name(di); - efikasb_batt_get_capacity(di, &di->capacity); - } - - printk("Probe Efika MX Smartbook Battery: %s %s %s: %d%%, AC %s\n", - di->mfg_name, di->model_name, - di->batt_in ? "Inserted" : "Removed", di->capacity, - di->ac_in ? "Inserted" : "Removed"); + dev_info(&di->client->dev, "Battery: %s %s %s: %d%%, AC %s\n", + di->mfg_name ? di->mfg_name : "", + di->model_name ? di->model_name : "", + batt_in ? "Inserted" : "Removed", di->capacity, + ac_in ? "Inserted" : "Removed"); dump_sbs_reg(client); @@ -945,26 +924,25 @@ static int efikasb_batt_i2c_probe(struct i2c_client *client, di->batt_low_timer.data = (unsigned long)di; di->batt_low_timer.function = batt_low_fn; di->batt_low_timer.expires = jiffies + 10; - if(!di->ac_in && di->batt_in) + if (!ac_in && batt_in) add_timer(&di->batt_low_timer); - - /* ron: if battery critical low, shutdown immediately */ - if(!di->ac_in && di->batt_low && di->capacity == 0) { - printk("Battery critical low, shutdown now....\n"); - schedule_delayed_work(&power_off_work, msecs_to_jiffies(1000)); - } - return 0; ac_charger_failed: power_supply_unregister(&efikasb_batt); batt_failed: - free_irq(di->batt_low_irq, NULL); + free_irq(gpio_to_irq(pdata->batt_low_gpio), di); batt_low_irq_failed: - free_irq(di->ac_in_irq, NULL); + free_irq(gpio_to_irq(pdata->ac_gpio), di); ac_irq_failed: - free_irq(di->batt_in_irq, NULL); -batt_irq_failed: + free_irq(gpio_to_irq(pdata->batt_gpio), di); +err_batt_low_gpio: + gpio_free(pdata->batt_low_gpio); +err_ac_gpio: + gpio_free(pdata->ac_gpio); +err_batt_gpio: + gpio_free(pdata->batt_gpio); +err_alloc: i2c_set_clientdata(client, NULL); kfree(di); return retval; @@ -976,9 +954,14 @@ static int efikasb_batt_i2c_remove(struct i2c_client *client) power_supply_unregister(&efikasb_batt); power_supply_unregister(&efikasb_ac_charger); - free_irq(di->batt_in_irq, NULL); - free_irq(di->ac_in_irq, NULL); + gpio_free(di->pdata.batt_low_gpio); + gpio_free(di->pdata.ac_gpio); + gpio_free(di->pdata.batt_gpio); + free_irq(gpio_to_irq(di->pdata.batt_low_gpio), di); + free_irq(gpio_to_irq(di->pdata.ac_gpio), di); + free_irq(gpio_to_irq(di->pdata.batt_gpio), di); kfree(di); + i2c_set_clientdata(client, NULL); batt = NULL; return 0; @@ -1004,7 +987,7 @@ static int __init efikasb_batt_init(void) int ret; ret = i2c_add_driver(&efikasb_batt_i2c_driver); - if(ret) + if (ret) printk(KERN_ERR "Unable to register Efika MX Smartbook Battery Driver\n"); return ret; diff --git a/include/linux/efikasb.h b/include/linux/efikasb.h new file mode 100644 index 0000000..311bfad --- /dev/null +++ b/include/linux/efikasb.h @@ -0,0 +1,13 @@ +#ifndef _LINUX_EFIKASB_H +#define _LINUX_EFIKASB_H + +struct efikasb_battery_platform_data { + int batt_gpio; + bool batt_inverted; + int ac_gpio; + bool ac_inverted; + int batt_low_gpio; + bool batt_low_inverted; +}; + +#endif -- 1.7.4.4 From 6e9f77ffe3594def55045a436141745ee24f5f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:41:23 +0200 Subject: [PATCH 24/28] rfkill efikasb: implement rfkill support Add support for switching on/off bt and wlan. Signed-off-by: Arnaud Patard --- arch/arm/mach-mx5/Kconfig | 7 + arch/arm/mach-mx5/Makefile | 1 + arch/arm/mach-mx5/board-mx51_efikasb.c | 6 + arch/arm/mach-mx5/efikasb_rfkill.c | 193 ++++++++++++++++++++++++++++++++ 4 files changed, 207 insertions(+), 0 deletions(-) create mode 100644 arch/arm/mach-mx5/efikasb_rfkill.c diff --git a/arch/arm/mach-mx5/Kconfig b/arch/arm/mach-mx5/Kconfig index 3da5396..e5a4bf5 100644 --- a/arch/arm/mach-mx5/Kconfig +++ b/arch/arm/mach-mx5/Kconfig @@ -173,6 +173,13 @@ config MACH_MX51_EFIKASB Include support for Genesi Efika Smartbook. This includes specific configurations for the board and its peripherals. +config EFIKASB_RFKILL + tristate "Support for enabling wifi/bt on Efika Smartbook" + depends on MACH_MX51_EFIKASB + select RFKILL + help + Allow to enable/disable wifi and bluetooth on Efika Smartbook + endif # ARCH_MX51 if ARCH_MX53_SUPPORTED diff --git a/arch/arm/mach-mx5/Makefile b/arch/arm/mach-mx5/Makefile index 383e7cd..50a621d 100644 --- a/arch/arm/mach-mx5/Makefile +++ b/arch/arm/mach-mx5/Makefile @@ -21,4 +21,5 @@ obj-$(CONFIG_MACH_EUKREA_MBIMXSD51_BASEBOARD) += eukrea_mbimxsd-baseboard.o obj-$(CONFIG_MX51_EFIKA_COMMON) += mx51_efika.o obj-$(CONFIG_MACH_MX51_EFIKAMX) += board-mx51_efikamx.o obj-$(CONFIG_MACH_MX51_EFIKASB) += board-mx51_efikasb.o +obj-$(CONFIG_EFIKASB_RFKILL) += efikasb_rfkill.o obj-$(CONFIG_MACH_MX50_RDP) += board-mx50_rdp.o diff --git a/arch/arm/mach-mx5/board-mx51_efikasb.c b/arch/arm/mach-mx5/board-mx51_efikasb.c index 8bc8a41..48ec3c8 100644 --- a/arch/arm/mach-mx5/board-mx51_efikasb.c +++ b/arch/arm/mach-mx5/board-mx51_efikasb.c @@ -316,6 +316,11 @@ static void __init mx51_efikasb_display(void) gpio_set_value(EFIKASB_LCD_BL_PWR, 0); } +static struct platform_device mx51_efikasb_rfkill_device = { + .name = "efikasb-rfkill", + .id = -1, +}; + static struct regulator *pwgt1, *pwgt2; static void mx51_efikasb_power_off(void) @@ -389,6 +394,7 @@ static void __init efikasb_board_init(void) mx51_efikasb_usb(); imx51_add_sdhci_esdhc_imx(1, NULL); mx51_efikasb_display(); + platform_device_register(&mx51_efikasb_rfkill_device); gpio_led_register_device(-1, &mx51_efikasb_leds_data); imx_add_gpio_keys(&mx51_efikasb_keys_data); diff --git a/arch/arm/mach-mx5/efikasb_rfkill.c b/arch/arm/mach-mx5/efikasb_rfkill.c new file mode 100644 index 0000000..dba2912 --- /dev/null +++ b/arch/arm/mach-mx5/efikasb_rfkill.c @@ -0,0 +1,193 @@ +/* + * Copyright (c) Arnaud Patard + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + * + * Genesi Efika Smartbook rfkill support + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "efika.h" + +#define EFIKASB_BT IMX_GPIO_NR(2, 11) + +struct efikasb_rfkill_priv { + struct rfkill *wlan; + struct rfkill *bt; +}; + +static int efikasb_rfkill_wlan_set_block(void *data, bool blocked) +{ + if (blocked == false) { + gpio_set_value(EFIKA_WLAN_EN, 1); + msleep(10); + gpio_set_value(EFIKA_WLAN_RESET, 0); + msleep(10); + gpio_set_value(EFIKA_WLAN_RESET, 1); + } else { + gpio_set_value(EFIKA_WLAN_EN, 0); + } + + return 0; +} + +static const struct rfkill_ops efikasb_rfkill_wlan_rfkill_ops = { + .set_block = efikasb_rfkill_wlan_set_block, +}; + +static int efikasb_rfkill_bt_set_block(void *data, bool blocked) +{ + gpio_set_value(EFIKASB_BT, !blocked); + return 0; +} + +static const struct rfkill_ops efikasb_rfkill_bt_rfkill_ops = { + .set_block = efikasb_rfkill_bt_set_block, +}; + +static int __devinit efikasb_rfkill_probe(struct platform_device *pdev) +{ + struct efikasb_rfkill_priv *priv; + struct rfkill *rfk; + int ret = 0; + + priv = kzalloc(sizeof(struct efikasb_rfkill_priv), GFP_KERNEL); + if (!priv) { + dev_err(&pdev->dev, "can't allow private structure\n"); + return -ENOMEM; + } + + ret = gpio_request(EFIKA_WLAN_EN, "wlan_en"); + if (ret) { + dev_err(&pdev->dev, "could not get wlan en\n"); + goto err_alloc; + } + + ret = gpio_request(EFIKA_WLAN_RESET, "wlan_rst"); + if (ret) { + dev_err(&pdev->dev, "could not get wlan reset\n"); + goto err_wlan_en; + } + + gpio_direction_output(EFIKA_WLAN_EN, 0); + gpio_direction_output(EFIKA_WLAN_RESET, 0); + + rfk = rfkill_alloc("efikasb-wlan", &pdev->dev, RFKILL_TYPE_WLAN, + &efikasb_rfkill_wlan_rfkill_ops, NULL); + if (!rfk) { + ret = -ENOMEM; + goto err_wlan_rst; + } + + priv->wlan = rfk; + + ret = rfkill_register(priv->wlan); + if (ret) + goto err_wlan_alloc; + + ret = gpio_request(EFIKASB_BT, "bt_en"); + if (ret) { + dev_err(&pdev->dev, "could not get bt en\n"); + goto err_wlan_register; + } + + gpio_direction_output(EFIKASB_BT, 0); + + rfk = rfkill_alloc("efikasb-bt", &pdev->dev, RFKILL_TYPE_BLUETOOTH, + &efikasb_rfkill_bt_rfkill_ops, NULL); + if (!rfk) { + ret = -ENOMEM; + goto err_bt_en; + } + + priv->bt = rfk; + + ret = rfkill_register(priv->bt); + if (ret) + goto err_bt_alloc; + + platform_set_drvdata(pdev, priv); + + return 0; + +err_bt_alloc: + rfkill_destroy(priv->bt); +err_bt_en: + gpio_free(EFIKASB_BT); +err_wlan_register: + rfkill_unregister(priv->wlan); +err_wlan_alloc: + rfkill_destroy(priv->wlan); +err_wlan_rst: + gpio_free(EFIKA_WLAN_RESET); +err_wlan_en: + gpio_free(EFIKA_WLAN_EN); +err_alloc: + kfree(priv); + return ret; +} + +static int efikasb_rfkill_remove(struct platform_device *pdev) +{ + struct efikasb_rfkill_priv *priv = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + + if (priv) { + if (priv->bt) { + rfkill_unregister(priv->bt); + rfkill_destroy(priv->bt); + gpio_set_value(EFIKASB_BT, 0); + gpio_free(EFIKASB_BT); + } + if (priv->wlan) { + rfkill_unregister(priv->wlan); + rfkill_destroy(priv->wlan); + gpio_set_value(EFIKA_WLAN_EN, 0); + gpio_free(EFIKA_WLAN_RESET); + gpio_free(EFIKA_WLAN_EN); + } + kfree(priv); + } + + return 0; +} + + +static struct platform_driver efikasb_rfkill_driver = { + .driver = { + .name = "efikasb-rfkill", + }, + .probe = efikasb_rfkill_probe, + .remove = efikasb_rfkill_remove, +}; + + +static int __init efikasb_rfkill_init(void) +{ + return platform_driver_register(&efikasb_rfkill_driver); +} + +static void __exit efikasb_rfkill_exit(void) +{ + platform_driver_unregister(&efikasb_rfkill_driver); +} + +module_init(efikasb_rfkill_init); +module_exit(efikasb_rfkill_exit); + +MODULE_AUTHOR("Arnaud Patard "); +MODULE_DESCRIPTION("Driver for the efika Smartbook wlan/bt rfkill"); +MODULE_LICENSE("GPL"); -- 1.7.4.4 From c4394c77308605f11c4e50a3a13032364569bb2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:42:02 +0200 Subject: [PATCH 25/28] wcam_regul efikasb: allow to enable/disable webcam The Webcam can be enabled/disabled with a gpio so use userspace regulator to allow userspace to enable/disable it to save power. Signed-off-by: Arnaud Patard --- arch/arm/mach-mx5/board-mx51_efikasb.c | 60 +++++++++++++++++++++++++++++++- 1 files changed, 59 insertions(+), 1 deletions(-) diff --git a/arch/arm/mach-mx5/board-mx51_efikasb.c b/arch/arm/mach-mx5/board-mx51_efikasb.c index 48ec3c8..e6f2cef 100644 --- a/arch/arm/mach-mx5/board-mx51_efikasb.c +++ b/arch/arm/mach-mx5/board-mx51_efikasb.c @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include #include @@ -69,7 +71,7 @@ #define EFIKASB_BATT_INS IMX_GPIO_NR(3, 6) #define EFIKASB_AC_ADAP_INS IMX_GPIO_NR(3, 3) #define EFIKASB_BATT_LOW IMX_GPIO_NR(3, 0) - +#define EFIKASB_WCAM_ENB IMX_GPIO_NR(3, 16) #define MX51_PAD_PWRKEY IOMUX_PAD(0x48c, 0x0f8, 1, 0x0, 0, PAD_CTL_PUS_100K_UP | PAD_CTL_PKE) #define EFIKASB_DISP_CLK IOMUX_PAD(0x754, 0x34C, 0, 0x0, 0, PAD_CTL_PKE|PAD_CTL_SRE_FAST) /* @@ -123,6 +125,9 @@ static iomux_v3_cfg_t mx51efikasb_pads[] = { /* BT */ MX51_PAD_EIM_A17__GPIO2_11, + /* webcam */ + MX51_PAD_NANDF_CS0__GPIO3_16, + /* lvds reset */ MX51_PAD_DISPB2_SER_DIN__GPIO3_5, /* lvds power */ @@ -321,6 +326,56 @@ static struct platform_device mx51_efikasb_rfkill_device = { .id = -1, }; +static struct regulator_consumer_supply mx51_efikasb_wcam_consumers[] = { + { + .supply = "webcam", + }, +}; + +static struct regulator_init_data mx51_efikasb_wcam_data = { + .constraints = { + .boot_on = 0, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(mx51_efikasb_wcam_consumers), + .consumer_supplies = mx51_efikasb_wcam_consumers, +}; + +static struct fixed_voltage_config mx51_efikasb_wcam_pdata = { + .supply_name = "webcam", + .microvolts = 5000000, + .init_data = &mx51_efikasb_wcam_data, + .gpio = EFIKASB_WCAM_ENB, + .enable_high = 0, + .enabled_at_boot = 0, +}; + +static struct platform_device mx51_efikasb_wcam = { + .name = "reg-fixed-voltage", + .id = -1, + .dev = { + .platform_data = &mx51_efikasb_wcam_pdata, + }, +}; + +static struct regulator_bulk_data mx51_efikasb_wcam_consumer_supply = { + .supply = "webcam", +}; + +static struct regulator_userspace_consumer_data mx51_efikasb_wcam_consumer_data = { + .name = "wcam", + .num_supplies = 1, + .supplies = &mx51_efikasb_wcam_consumer_supply, +}; + +static struct platform_device mx51_efikasb_wcam_userspace_consumer = { + .name = "reg-userspace-consumer", + .id = -1, + .dev = { + .platform_data = &mx51_efikasb_wcam_consumer_data, + }, +}; + static struct regulator *pwgt1, *pwgt2; static void mx51_efikasb_power_off(void) @@ -347,6 +402,9 @@ static int __init mx51_efikasb_power_init(void) pm_power_off = mx51_efikasb_power_off; regulator_has_full_constraints(); + + platform_device_register(&mx51_efikasb_wcam); + platform_device_register(&mx51_efikasb_wcam_userspace_consumer); } return 0; -- 1.7.4.4 From d656fe7d40afea290d6b3b0f6f3e4829e7a0942c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:42:49 +0200 Subject: [PATCH 26/28] rt2800usb_disable_ps Johannes Stezenbach [4]js at sig21.net Wed Jan 19 04:43:34 EST 2011 Powersaving is broken on rt2800usb (the hardware cannot do timed wakeup to receive beacons) and also on rt2800pci currently (auto-wakeup irq does no seem to work, for reasons so far unknown). Signed-off-by: Johannes Stezenbach <[11]js at sig21.net> --- drivers/net/wireless/rt2x00/rt2800lib.c | 5 ++--- 1 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/rt2x00/rt2800lib.c b/drivers/net/wireless/rt2x00/rt2800lib.c index ef67f67..38eee53 100644 --- a/drivers/net/wireless/rt2x00/rt2800lib.c +++ b/drivers/net/wireless/rt2x00/rt2800lib.c @@ -4119,10 +4119,9 @@ int rt2800_probe_hw_mode(struct rt2x00_dev *rt2x00dev) u16 eeprom; /* - * Disable powersaving as default on PCI devices. + * Disable powersaving as default. */ - if (rt2x00_is_pci(rt2x00dev) || rt2x00_is_soc(rt2x00dev)) - rt2x00dev->hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; + rt2x00dev->hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; /* * Initialize all hw fields. -- 1.7.4.4 From 097418c128411e89acb9d7921037cd40a5e8525e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:44:13 +0200 Subject: [PATCH 27/28] libata-ide-ledtrigger-support --- arch/arm/mach-mx5/board-mx51_efikasb.c | 2 +- drivers/ata/libata-sff.c | 21 +++++++++++++++++++++ drivers/ata/pata_imx.c | 3 +++ drivers/leds/Kconfig | 2 +- include/linux/libata.h | 1 + 5 files changed, 27 insertions(+), 2 deletions(-) diff --git a/arch/arm/mach-mx5/board-mx51_efikasb.c b/arch/arm/mach-mx5/board-mx51_efikasb.c index e6f2cef..7873baf 100644 --- a/arch/arm/mach-mx5/board-mx51_efikasb.c +++ b/arch/arm/mach-mx5/board-mx51_efikasb.c @@ -185,7 +185,7 @@ static void __init mx51_efikasb_usb(void) static const struct gpio_led mx51_efikasb_leds[] __initconst = { { .name = "efikasb:green", - .default_trigger = "default-on", + .default_trigger = "ide-disk", .gpio = EFIKASB_GREEN_LED, .active_low = 1, }, diff --git a/drivers/ata/libata-sff.c b/drivers/ata/libata-sff.c index c24127d..2f4b274 100644 --- a/drivers/ata/libata-sff.c +++ b/drivers/ata/libata-sff.c @@ -37,6 +37,7 @@ #include #include #include +#include #include "libata.h" @@ -1486,6 +1487,26 @@ unsigned int ata_sff_qc_issue(struct ata_queued_cmd *qc) EXPORT_SYMBOL_GPL(ata_sff_qc_issue); /** + * ata_sff_qc_issue_ledtrigger - trigger LED core + * @qc: command to issue to device + * + * This triggers the LED core and then calls the + * regular ata_sff_qc_issue function. + * + * LOCKING: + * spin_lock_irqsave(host lock) + * + * RETURNS: + * Zero on success, AC_ERR_* mask on failure + */ +unsigned int ata_sff_qc_issue_ledtrigger(struct ata_queued_cmd *qc) +{ + ledtrig_ide_activity(); + return ata_sff_qc_issue(qc); +} +EXPORT_SYMBOL_GPL(ata_sff_qc_issue_ledtrigger); + +/** * ata_sff_qc_fill_rtf - fill result TF using ->sff_tf_read * @qc: qc to fill result TF for * diff --git a/drivers/ata/pata_imx.c b/drivers/ata/pata_imx.c index ca9d9ca..49f533f 100644 --- a/drivers/ata/pata_imx.c +++ b/drivers/ata/pata_imx.c @@ -71,6 +71,9 @@ static struct scsi_host_template pata_imx_sht = { static struct ata_port_operations pata_imx_port_ops = { .inherits = &ata_sff_port_ops, +#ifdef CONFIG_LEDS_TRIGGER_IDE_DISK + .qc_issue = ata_sff_qc_issue_ledtrigger, +#endif .sff_data_xfer = ata_sff_data_xfer_noirq, .cable_detect = ata_cable_unknown, .set_mode = pata_imx_set_mode, diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 53a23b7..906461e 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -406,7 +406,7 @@ config LEDS_TRIGGER_TIMER config LEDS_TRIGGER_IDE_DISK bool "LED IDE Disk Trigger" - depends on IDE_GD_ATA + depends on (IDE_GD_ATA || ATA_SFF) depends on LEDS_TRIGGERS help This allows LEDs to be controlled by IDE disk activity. diff --git a/include/linux/libata.h b/include/linux/libata.h index efd6f98..005763c 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -1655,6 +1655,7 @@ extern void ata_sff_queue_delayed_work(struct delayed_work *dwork, unsigned long delay); extern void ata_sff_queue_pio_task(struct ata_link *link, unsigned long delay); extern unsigned int ata_sff_qc_issue(struct ata_queued_cmd *qc); +extern unsigned int ata_sff_qc_issue_ledtrigger(struct ata_queued_cmd *qc); extern bool ata_sff_qc_fill_rtf(struct ata_queued_cmd *qc); extern unsigned int ata_sff_port_intr(struct ata_port *ap, struct ata_queued_cmd *qc); -- 1.7.4.4 From 55d27ed25ec418369fb41e086dd1238895c53134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 12 Aug 2011 18:49:03 +0200 Subject: [PATCH 28/28] mmc-for-next --- drivers/mmc/host/sdhci-esdhc.h | 3 +-- drivers/mmc/host/sdhci.c | 9 ++++++--- drivers/mmc/host/sdhci.h | 2 ++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci-esdhc.h b/drivers/mmc/host/sdhci-esdhc.h index c3b08f1..a1a9742 100644 --- a/drivers/mmc/host/sdhci-esdhc.h +++ b/drivers/mmc/host/sdhci-esdhc.h @@ -18,8 +18,7 @@ * Ops and quirks for the Freescale eSDHC controller. */ -#define ESDHC_DEFAULT_QUIRKS (SDHCI_QUIRK_FORCE_BLK_SZ_2048 | \ - SDHCI_QUIRK_NO_BUSY_IRQ | \ +#define ESDHC_DEFAULT_QUIRKS (SDHCI_QUIRK_NO_BUSY_IRQ | \ SDHCI_QUIRK_NONSTANDARD_CLOCK | \ SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | \ SDHCI_QUIRK_PIO_NEEDS_DELAY | \ diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 0e02cc1..ccb393b 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2697,8 +2697,8 @@ int sdhci_add_host(struct sdhci_host *host) * Maximum block size. This varies from controller to controller and * is specified in the capabilities register. */ - if (host->quirks & SDHCI_QUIRK_FORCE_BLK_SZ_2048) { - mmc->max_blk_size = 2; + if (host->ops->get_max_blk_size) { + mmc->max_blk_size = host->ops->get_max_blk_size(host); } else { mmc->max_blk_size = (caps[0] & SDHCI_MAX_BLOCK_MASK) >> SDHCI_MAX_BLOCK_SHIFT; @@ -2714,7 +2714,10 @@ int sdhci_add_host(struct sdhci_host *host) /* * Maximum block count. */ - mmc->max_blk_count = (host->quirks & SDHCI_QUIRK_NO_MULTIBLOCK) ? 1 : 65535; + if (host->ops->get_max_blk_count) + mmc->max_blk_count = host->ops->get_max_blk_count(host); + else + mmc->max_blk_count = 65535; /* * Init tasklets. diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 745c42f..7930e34 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -265,6 +265,8 @@ struct sdhci_ops { unsigned int (*get_max_clock)(struct sdhci_host *host); unsigned int (*get_min_clock)(struct sdhci_host *host); unsigned int (*get_timeout_clock)(struct sdhci_host *host); + unsigned int (*get_max_blk_size)(struct sdhci_host *host); + unsigned int (*get_max_blk_count)(struct sdhci_host *host); int (*platform_8bit_width)(struct sdhci_host *host, int width); void (*platform_send_init_74_clocks)(struct sdhci_host *host, -- 1.7.4.4