diff --git a/README b/README index a24ec89ba4420a..ceaca518ade76e 100644 --- a/README +++ b/README @@ -1,3 +1,26 @@ +Summary: + Get CEC (tv-remote control over HDMI) working on linux. + There are android-kernel sources, which have not made it + to the main linux kernel-tree, so also the hardkernel + linux-tree doesn't have CEC for exynos platforms. + +Status: + Copied s5p-cec code from all over the internet, mostly from + http://dn.odroid.com/Android_Alpha_4.0.3/BSP/ + Made it compile as module (s5p-cec.ko) + Fixed the insmod()/rmmod() stuff. Error handling and resource + free-ing were quite broken. + + Using a modified cec-client, it's possible to observe the + tv changing it's inputs, see: + https://github.com/vamanea/libcec.git + +TODO: + Cleanup this tree, so it becomes a nice diff upon hardkernel's 3.8 tree + + +================================================================================ + Linux kernel release 3.x These are the release notes for Linux version 3. Read them carefully, diff --git a/arch/arm/mach-exynos/clock-exynos4.c b/arch/arm/mach-exynos/clock-exynos4.c index 56b9c93617d255..7de33c3ee67f5b 100644 --- a/arch/arm/mach-exynos/clock-exynos4.c +++ b/arch/arm/mach-exynos/clock-exynos4.c @@ -716,6 +716,10 @@ static struct clk exynos4_init_clocks_off[] = { .parent = &exynos4_clk_aclk_100.clk, .enable = exynos4_clk_ip_perir_ctrl, .ctrlbit = (1 << 14), + }, { + .name = "hdmicec", + .enable = exynos4_clk_ip_perir_ctrl, + .ctrlbit = (1 << 11), }, { .name = "usbhost", .enable = exynos4_clk_ip_fsys_ctrl , diff --git a/arch/arm/mach-exynos/include/mach/irqs.h b/arch/arm/mach-exynos/include/mach/irqs.h index 1f4dc35cd4b9b8..f5b0f19ae43ccf 100644 --- a/arch/arm/mach-exynos/include/mach/irqs.h +++ b/arch/arm/mach-exynos/include/mach/irqs.h @@ -132,6 +132,7 @@ #define EXYNOS4_IRQ_GPS IRQ_SPI(111) #define EXYNOS4_IRQ_INTFEEDCTRL_SSS IRQ_SPI(112) #define EXYNOS4_IRQ_SLIMBUS IRQ_SPI(113) +#define EXYNOS4_IRQ_CEC IRQ_SPI(114) #define EXYNOS4_IRQ_TSI IRQ_SPI(115) #define EXYNOS4_IRQ_SATA IRQ_SPI(116) @@ -235,6 +236,8 @@ #define IRQ_KEYPAD EXYNOS4_IRQ_KEYPAD #define IRQ_PMU EXYNOS4_IRQ_PMU +#define IRQ_CEC EXYNOS4_IRQ_CEC + #define IRQ_FIMD0_FIFO EXYNOS4_IRQ_FIMD0_FIFO #define IRQ_FIMD0_VSYNC EXYNOS4_IRQ_FIMD0_VSYNC #define IRQ_FIMD0_SYSTEM EXYNOS4_IRQ_FIMD0_SYSTEM diff --git a/arch/arm/mach-exynos/include/mach/map.h b/arch/arm/mach-exynos/include/mach/map.h index e178cf189d1f5b..49cacd2ef52ca8 100644 --- a/arch/arm/mach-exynos/include/mach/map.h +++ b/arch/arm/mach-exynos/include/mach/map.h @@ -79,6 +79,8 @@ #define EXYNOS4_PA_KEYPAD 0x100A0000 +#define EXYNOS4_PA_CEC 0x100B0000 + #define EXYNOS4_PA_DMC0 0x10400000 #define EXYNOS4_PA_DMC1 0x10410000 @@ -276,6 +278,9 @@ #define SAMSUNG_PA_ADC1 EXYNOS4_PA_ADC1 #define SAMSUNG_PA_KEYPAD EXYNOS4_PA_KEYPAD +#define S5P_PA_HDMI_CEC EXYNOS4_PA_CEC +#define S5P_SZ_HDMI_CEC SZ_4K + /* Compatibility UART */ #define EXYNOS4_PA_UART0 0x13800000 diff --git a/arch/arm/mach-exynos/mach-hkdk4412.c b/arch/arm/mach-exynos/mach-hkdk4412.c index 2e0cf0a52e6d93..8b8a9b66f5d360 100644 --- a/arch/arm/mach-exynos/mach-hkdk4412.c +++ b/arch/arm/mach-exynos/mach-hkdk4412.c @@ -393,6 +393,7 @@ static struct platform_device *hkdk4412_devices[] __initdata = { &mali_gpu_device, #if defined(CONFIG_S5P_DEV_TV) &s5p_device_hdmi, + &s5p_device_cec, &s5p_device_i2c_hdmiphy, &s5p_device_mixer, &hdmi_fixed_voltage, @@ -417,6 +418,12 @@ static struct platform_device *hkdk4412_devices[] __initdata = { #endif }; +#if defined(CONFIG_S5P_DEV_TV) +static struct s5p_platform_cec hdmi_cec_data __initdata = { + +}; +#endif + static void __init hkdk4412_map_io(void) { clk_xusbxti.rate = 24000000; @@ -525,6 +532,7 @@ static void __init hkdk4412_machine_init(void) s5p_tv_setup(); s5p_i2c_hdmiphy_set_platdata(NULL); s5p_hdmi_set_platdata(&hdmiphy_info, NULL, 0); + s5p_hdmi_cec_set_platdata(&hdmi_cec_data); #endif s5p_fimd0_set_platdata(&hkdk4412_fb_pdata); diff --git a/arch/arm/mach-exynos/mach-origen.c b/arch/arm/mach-exynos/mach-origen.c index 5e34b9c16196c4..d283b06db8d8ab 100644 --- a/arch/arm/mach-exynos/mach-origen.c +++ b/arch/arm/mach-exynos/mach-origen.c @@ -705,6 +705,7 @@ static struct platform_device *origen_devices[] __initdata = { &s5p_device_fimd0, &s5p_device_g2d, &s5p_device_hdmi, + &s5p_device_cec, &s5p_device_i2c_hdmiphy, &s5p_device_jpeg, &s5p_device_mfc, diff --git a/arch/arm/mach-exynos/mach-smdk4x12.c b/arch/arm/mach-exynos/mach-smdk4x12.c index ae6da40c2aa9e1..2a7f8afff9ee83 100644 --- a/arch/arm/mach-exynos/mach-smdk4x12.c +++ b/arch/arm/mach-exynos/mach-smdk4x12.c @@ -39,12 +39,19 @@ #include #include #include +#include #include #include #include "common.h" +#if defined(CONFIG_ARCH_EXYNOS4) +#define HDMI_GPX(_nr) EXYNOS4_GPX3(_nr) +#elif defined(CONFIG_ARCH_EXYNOS5) +#define HDMI_GPX(_nr) EXYNOS5_GPX3(_nr) +#endif + /* Following are default values for UCON, ULCON and UFCON UART registers */ #define SMDK4X12_UCON_DEFAULT (S3C2410_UCON_TXILEVEL | \ S3C2410_UCON_RXILEVEL | \ @@ -246,6 +253,12 @@ static struct samsung_keypad_platdata smdk4x12_keypad_data __initdata = { .cols = 8, }; +#if defined(CONFIG_S5P_DEV_TV) +static struct s5p_platform_cec hdmi_cec_data __initdata = { + +}; +#endif + #ifdef CONFIG_DRM_EXYNOS_FIMD static struct exynos_drm_fimd_pdata drm_fimd_pdata = { .panel = { @@ -332,6 +345,14 @@ static void __init smdk4x12_reserve(void) s5p_mfc_reserve_mem(0x43000000, 8 << 20, 0x51000000, 8 << 20); } +#if defined(CONFIG_S5P_DEV_TV) +void s5p_cec_cfg_gpio(struct platform_device *pdev) +{ + s3c_gpio_cfgpin(HDMI_GPX(6), S3C_GPIO_SFN(0x3)); + s3c_gpio_setpull(HDMI_GPX(6), S3C_GPIO_PULL_NONE); +} +#endif + static void __init smdk4x12_machine_init(void) { s3c_i2c0_set_platdata(NULL); @@ -359,6 +380,10 @@ static void __init smdk4x12_machine_init(void) s3c_sdhci3_set_platdata(&smdk4x12_hsmmc3_pdata); s3c_hsotg_set_platdata(&smdk4x12_hsotg_pdata); + +#if defined(CONFIG_S5P_DEV_TV) + s5p_hdmi_cec_set_platdata(&hdmi_cec_data); +#endif #ifdef CONFIG_DRM_EXYNOS_FIMD s5p_device_fimd0.dev.platform_data = &drm_fimd_pdata; diff --git a/arch/arm/mach-exynos/mach-smdkv310.c b/arch/arm/mach-exynos/mach-smdkv310.c index 35548e3c097d8e..00174f821fbc09 100644 --- a/arch/arm/mach-exynos/mach-smdkv310.c +++ b/arch/arm/mach-exynos/mach-smdkv310.c @@ -315,6 +315,7 @@ static struct platform_device *smdkv310_devices[] __initdata = { &smdkv310_smsc911x, &exynos4_device_ahci, &s5p_device_hdmi, + &s5p_device_cec, &s5p_device_mixer, }; diff --git a/arch/arm/mach-s5pv210/include/mach/map.h b/arch/arm/mach-s5pv210/include/mach/map.h index b7c8a1917ffce5..824b73423672c6 100644 --- a/arch/arm/mach-s5pv210/include/mach/map.h +++ b/arch/arm/mach-s5pv210/include/mach/map.h @@ -48,6 +48,8 @@ #define S5PV210_PA_PCM1 0xE1200000 #define S5PV210_PA_PCM2 0xE2B00000 +#define S5PV210_PA_CEC 0xE1B00000 + #define S5PV210_PA_TIMER 0xE2500000 #define S5PV210_PA_SYSTIMER 0xE2600000 #define S5PV210_PA_WATCHDOG 0xE2700000 @@ -126,6 +128,8 @@ #define S5P_PA_VP S5PV210_PA_VP #define S5P_PA_MIXER S5PV210_PA_MIXER #define S5P_PA_HDMI S5PV210_PA_HDMI +#define S5P_PA_CEC S5PV210_PA_CEC +#define S5P_SZ_CEC SZ_4K #define S5P_PA_ONENAND S5PC110_PA_ONENAND #define S5P_PA_ONENAND_DMA S5PC110_PA_ONENAND_DMA diff --git a/arch/arm/plat-samsung/devs.c b/arch/arm/plat-samsung/devs.c index 82e30674093a8d..87d5224b8de46c 100644 --- a/arch/arm/plat-samsung/devs.c +++ b/arch/arm/plat-samsung/devs.c @@ -68,6 +68,8 @@ #include #include #include +#include + static u64 samsung_device_dma_mask = DMA_BIT_MASK(32); @@ -806,6 +808,7 @@ void __init s5p_hdmi_set_platdata(struct i2c_board_info *hdmiphy_info, #endif /* CONFIG_S5P_DEV_I2C_HDMIPHY */ + /* I2S */ #ifdef CONFIG_PLAT_S3C24XX @@ -1357,6 +1360,18 @@ struct platform_device s5p_device_hdmi = { .resource = s5p_hdmi_resources, }; +static struct resource s5p_cec_resources[] = { + [0] = DEFINE_RES_MEM(S5P_PA_HDMI_CEC, S5P_SZ_HDMI_CEC), + [1] = DEFINE_RES_IRQ(IRQ_CEC), +}; + +struct platform_device s5p_device_cec = { + .name = "s5p-cec", + .id = -1, + .num_resources = ARRAY_SIZE(s5p_cec_resources), + .resource = s5p_cec_resources, +}; + static struct resource s5p_sdo_resources[] = { [0] = DEFINE_RES_MEM(S5P_PA_SDO, SZ_64K), [1] = DEFINE_RES_IRQ(IRQ_SDO), @@ -1385,6 +1400,22 @@ struct platform_device s5p_device_mixer = { .coherent_dma_mask = DMA_BIT_MASK(32), } }; + +void __init s5p_hdmi_cec_set_platdata(struct s5p_platform_cec *pd) +{ + struct s5p_platform_cec *npd; + printk(KERN_INFO "s5p_hdmi_cec_set_platdata()\n"); + + npd = kmemdup(pd, sizeof(struct s5p_platform_cec), GFP_KERNEL); + if (!npd) + printk(KERN_ERR "%s: no memory for platform data\n", __func__); + else { + if (!npd->cfg_gpio) + npd->cfg_gpio = s5p_cec_cfg_gpio; + + s5p_device_cec.dev.platform_data = npd; + } +} #endif /* CONFIG_S5P_DEV_TV */ /* USB */ diff --git a/arch/arm/plat-samsung/include/plat/devs.h b/arch/arm/plat-samsung/include/plat/devs.h index 92641bb13ef8d1..98b1a96b520bf2 100644 --- a/arch/arm/plat-samsung/include/plat/devs.h +++ b/arch/arm/plat-samsung/include/plat/devs.h @@ -87,6 +87,7 @@ extern struct platform_device mali_gpu_device; extern struct platform_device s5p_device_fimd0; extern struct platform_device s5p_device_hdmi; +extern struct platform_device s5p_device_cec; extern struct platform_device s5p_device_i2c_hdmiphy; extern struct platform_device s5p_device_mfc; extern struct platform_device s5p_device_mfc_l; diff --git a/arch/arm/plat-samsung/include/plat/hdmi.h b/arch/arm/plat-samsung/include/plat/hdmi.h index 331d046ac2c574..3dcca2f493c29c 100644 --- a/arch/arm/plat-samsung/include/plat/hdmi.h +++ b/arch/arm/plat-samsung/include/plat/hdmi.h @@ -10,7 +10,17 @@ #ifndef __PLAT_SAMSUNG_HDMI_H #define __PLAT_SAMSUNG_HDMI_H __FILE__ +struct s5p_platform_cec { + + void (*cfg_gpio)(struct platform_device *pdev); +}; + extern void s5p_hdmi_set_platdata(struct i2c_board_info *hdmiphy_info, struct i2c_board_info *mhl_info, int mhl_bus); +extern void s5p_hdmi_cec_set_platdata(struct s5p_platform_cec *pd); + +/* defined by architecture to configure gpio */ +extern void s5p_cec_cfg_gpio(struct platform_device *pdev); + #endif /* __PLAT_SAMSUNG_HDMI_H */ diff --git a/arch/arm/plat-samsung/include/plat/tv-core.h b/arch/arm/plat-samsung/include/plat/tv-core.h index 3bc34f3ce28fb6..cc0b2265bfad09 100644 --- a/arch/arm/plat-samsung/include/plat/tv-core.h +++ b/arch/arm/plat-samsung/include/plat/tv-core.h @@ -27,6 +27,13 @@ static inline void s5p_hdmi_setname(char *name) #endif } +static inline void s5p_cec_setname(char *name) +{ +#ifdef CONFIG_S5P_DEV_TV + s5p_device_cec.name = name; +#endif +} + static inline void s5p_mixer_setname(char *name) { #ifdef CONFIG_S5P_DEV_TV diff --git a/build.sh b/build.sh new file mode 100755 index 00000000000000..f7cb298d1c65f1 --- /dev/null +++ b/build.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +export CROSS_COMPILE=arm-linux-gnueabihf- +export ARCH=arm + +make M=drivers/media/platform/s5p-tv modules +scp drivers/media/platform/s5p-tv/s5p-cec.ko root@x2_wifi:/root diff --git a/drivers/gpu/drm/drm_vm.c b/drivers/gpu/drm/drm_vm.c index db7bd292410bad..1d4f7c9fe66189 100644 --- a/drivers/gpu/drm/drm_vm.c +++ b/drivers/gpu/drm/drm_vm.c @@ -422,6 +422,7 @@ void drm_vm_open_locked(struct drm_device *dev, list_add(&vma_entry->head, &dev->vmalist); } } +EXPORT_SYMBOL_GPL(drm_vm_open_locked); static void drm_vm_open(struct vm_area_struct *vma) { diff --git a/drivers/media/platform/s5p-tv/Kconfig b/drivers/media/platform/s5p-tv/Kconfig index 9f2fbebc7ed19c..a819eaf0d2ddac 100644 --- a/drivers/media/platform/s5p-tv/Kconfig +++ b/drivers/media/platform/s5p-tv/Kconfig @@ -36,6 +36,15 @@ config VIDEO_SAMSUNG_S5P_HDMI_DEBUG help Enables debugging for HDMI driver. +config VIDEO_SAMSUNG_S5P_HDMI_CEC + tristate "Enable CEC support for HDMI Driver" + depends on VIDEO_SAMSUNG_S5P_HDMI + default n + help + Enables CEC over the HDMI driver. + CEC enables tv-control, and reception of tv-remote + control commands over the HDMI interface + config VIDEO_SAMSUNG_S5P_HDMIPHY tristate "Samsung HDMIPHY Driver" depends on VIDEO_DEV && VIDEO_V4L2 && I2C diff --git a/drivers/media/platform/s5p-tv/Makefile b/drivers/media/platform/s5p-tv/Makefile index da2f98fe24abf9..375806412df0a6 100644 --- a/drivers/media/platform/s5p-tv/Makefile +++ b/drivers/media/platform/s5p-tv/Makefile @@ -8,14 +8,19 @@ obj-$(CONFIG_VIDEO_SAMSUNG_S5P_HDMIPHY) += s5p-hdmiphy.o s5p-hdmiphy-y += hdmiphy_drv.o + obj-$(CONFIG_VIDEO_SAMSUNG_S5P_SII9234) += s5p-sii9234.o s5p-sii9234-y += sii9234_drv.o + obj-$(CONFIG_VIDEO_SAMSUNG_S5P_HDMI) += s5p-hdmi.o s5p-hdmi-$(CONFIG_CPU_EXYNOS4210) += hdmi_drv.o s5p-hdmi-$(CONFIG_SOC_EXYNOS4412) += hdmi_v14_drv.o +obj-$(CONFIG_VIDEO_SAMSUNG_S5P_HDMI_CEC) += s5p-cec.o +s5p-cec-y += cec_drv.o cec_hw.o + obj-$(CONFIG_VIDEO_SAMSUNG_S5P_SDO) += s5p-sdo.o s5p-sdo-y += sdo_drv.o + obj-$(CONFIG_VIDEO_SAMSUNG_S5P_MIXER) += s5p-mixer.o s5p-mixer-y += mixer_drv.o mixer_video.o mixer_reg.o mixer_grp_layer.o mixer_vp_layer.o - diff --git a/drivers/media/platform/s5p-tv/cec_drv.c b/drivers/media/platform/s5p-tv/cec_drv.c new file mode 100644 index 00000000000000..1847c9ebbae505 --- /dev/null +++ b/drivers/media/platform/s5p-tv/cec_drv.c @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * CEC Support for Samsung S5P TVOUT + * + * 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 + +// Modified includes: +#include +#include +#include +#include +#include + +#include + +#include "cec_hw.h" + + +MODULE_AUTHOR("KyungHwan Kim "); +MODULE_DESCRIPTION("Samsung S5P CEC driver"); +MODULE_LICENSE("GPL"); + +#define CEC_IOC_MAGIC 'c' +#define CEC_IOC_SETLADDR _IOW(CEC_IOC_MAGIC, 0, unsigned int) + +/* /dev/cec (Major 10, Minor 242) */ +#define CEC_MINOR 242 + +#define CEC_STATUS_TX_DONE (1 << 2) +#define CEC_STATUS_TX_ERROR (1 << 3) +#define CEC_STATUS_RX_DONE (1 << 18) +#define CEC_STATUS_RX_ERROR (1 << 19) + +#define CEC_TX_BUFF_SIZE 16 + +static atomic_t hdmi_on = ATOMIC_INIT(0); +static DEFINE_MUTEX(cec_lock); +struct clk *hdmi_cec_clk; + +static int s5p_cec_open(struct inode *inode, struct file *file) +{ + int ret = 0; + printk(KERN_INFO "s5p_cec_open\n"); + + mutex_lock(&cec_lock); + clk_enable(hdmi_cec_clk); + + if (atomic_read(&hdmi_on)) { + tvout_dbg("do not allow multiple open for tvout cec\n"); + ret = -EBUSY; + goto err_multi_open; + } else + atomic_inc(&hdmi_on); + + s5p_cec_reset(); + + s5p_cec_set_divider(); + + s5p_cec_threshold(); + + s5p_cec_unmask_tx_interrupts(); + + s5p_cec_set_rx_state(STATE_RX); + s5p_cec_unmask_rx_interrupts(); + s5p_cec_enable_rx(); + +err_multi_open: + mutex_unlock(&cec_lock); + + return ret; +} + +static int s5p_cec_release(struct inode *inode, struct file *file) +{ + printk(KERN_INFO "s5p_cec_release, hdmi_on=%i\n", atomic_read(&hdmi_on)); + + atomic_dec(&hdmi_on); + + s5p_cec_mask_tx_interrupts(); + s5p_cec_mask_rx_interrupts(); + + clk_disable(hdmi_cec_clk); + + return 0; +} + +static ssize_t s5p_cec_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + ssize_t retval; + unsigned long spin_flags; + + printk(KERN_INFO "s5p_cec_read, %li bytes\n", (long)count); + + if (wait_event_interruptible(cec_rx_struct.waitq, + atomic_read(&cec_rx_struct.state) == STATE_DONE)) { + return -ERESTARTSYS; + } + spin_lock_irqsave(&cec_rx_struct.lock, spin_flags); + + if (cec_rx_struct.size > count) { + spin_unlock_irqrestore(&cec_rx_struct.lock, spin_flags); + + return -1; + } + + if (copy_to_user(buffer, cec_rx_struct.buffer, cec_rx_struct.size)) { + spin_unlock_irqrestore(&cec_rx_struct.lock, spin_flags); + printk(KERN_ERR " copy_to_user() failed!\n"); + + return -EFAULT; + } + + retval = cec_rx_struct.size; + + s5p_cec_set_rx_state(STATE_RX); + spin_unlock_irqrestore(&cec_rx_struct.lock, spin_flags); + + return retval; +} + + +static ssize_t s5p_cec_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + char *data; + + printk(KERN_INFO "s5p_cec_write, %li bytes\n", (long)count); + + /* check data size */ + + if (count > CEC_TX_BUFF_SIZE || count == 0) + return -1; + + data = kmalloc(count, GFP_KERNEL); + + if (!data) { + printk(KERN_ERR " kmalloc() failed!\n"); + return -1; + } + + if (copy_from_user(data, buffer, count)) { + printk(KERN_ERR " copy_from_user() failed!\n"); + kfree(data); + return -EFAULT; + } + + s5p_cec_copy_packet(data, count); + + kfree(data); + + /* wait for interrupt */ + if (wait_event_interruptible(cec_tx_struct.waitq, + atomic_read(&cec_tx_struct.state) + != STATE_TX)) + { + printk(KERN_INFO "\ttx no interrupt received\n"); + return -ERESTARTSYS; + } + + if (atomic_read(&cec_tx_struct.state) == STATE_ERROR) + { + printk(KERN_INFO "\ttx error\n"); + return -1; + } + + printk(KERN_INFO "\twritten count: %i\n", count); + return count; +} + +static long s5p_cec_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + u32 laddr; + + printk(KERN_INFO "s5p_cec_ioctl, cmd = 0x%x, arg = 0x%lx\n", cmd, arg); + + switch (cmd) { + case CEC_IOC_SETLADDR: + if (get_user(laddr, (u32 __user *) arg)) + return -EFAULT; + + tvout_dbg("logical address = 0x%02x\n", laddr); + + s5p_cec_set_addr(laddr); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static u32 s5p_cec_poll(struct file *file, poll_table *wait) +{ + poll_wait(file, &cec_rx_struct.waitq, wait); + + if (atomic_read(&cec_rx_struct.state) == STATE_DONE) + { + printk(KERN_INFO "s5p_cec_poll: rx = done\n"); + return POLLIN | POLLRDNORM; + } + return 0; +} + +static const struct file_operations cec_fops = { + .owner = THIS_MODULE, + .open = s5p_cec_open, + .release = s5p_cec_release, + .read = s5p_cec_read, + .write = s5p_cec_write, + .unlocked_ioctl = s5p_cec_ioctl, + .poll = s5p_cec_poll, +}; + +static struct miscdevice cec_misc_device = { + .minor = CEC_MINOR, + .name = "CEC", + .fops = &cec_fops, +}; + +static irqreturn_t s5p_cec_irq_handler(int irq, void *dev_id) +{ + u32 status = s5p_cec_get_status(); + printk(KERN_INFO "s5p_cec_irq_handler on irq %i\n", irq); + + if (status & CEC_STATUS_TX_DONE) { + if (status & CEC_STATUS_TX_ERROR) { + tvout_dbg(" CEC_STATUS_TX_ERROR!\n"); + s5p_cec_set_tx_state(STATE_ERROR); + } else { + tvout_dbg(" CEC_STATUS_TX_DONE!\n"); + s5p_cec_set_tx_state(STATE_DONE); + } + + s5p_clr_pending_tx(); + + wake_up_interruptible(&cec_tx_struct.waitq); + } + + if (status & CEC_STATUS_RX_DONE) { + if (status & CEC_STATUS_RX_ERROR) { + tvout_dbg(" CEC_STATUS_RX_ERROR!\n"); + s5p_cec_rx_reset(); + + } else { + u32 size; + + tvout_dbg(" CEC_STATUS_RX_DONE!\n"); + + /* copy data from internal buffer */ + size = status >> 24; + + spin_lock(&cec_rx_struct.lock); + + s5p_cec_get_rx_buf(size, cec_rx_struct.buffer); + + cec_rx_struct.size = size; + + s5p_cec_set_rx_state(STATE_DONE); + + spin_unlock(&cec_rx_struct.lock); + + s5p_cec_enable_rx(); + } + + /* clear interrupt pending bit */ + s5p_clr_pending_rx(); + + wake_up_interruptible(&cec_rx_struct.waitq); + } + return IRQ_HANDLED; +} + +static int s5p_cec_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct s5p_platform_cec *pdata; + u8 *buffer = NULL; + int irq_num; + int ret = 0; + struct resource *res; + + dev_info(dev, "Probe start\n"); + + pdata = to_platform_device(&pdev->dev)->dev.platform_data; + dev_info(dev, "s5p_cec_probe: pdata=%p\n", pdata); + if (pdata && pdata->cfg_gpio) + pdata->cfg_gpio(pdev); + + /* get ioremap addr */ + ret = s5p_cec_mem_probe(pdev); + if (ret != 0) { + dev_err(dev, "Failed to s5p_cec_mem_probe(). ret = %d\n", ret); + goto err_mem_probe; + } + + if (misc_register(&cec_misc_device)) { + dev_err(dev, "Couldn't register device 10, %d.\n", CEC_MINOR); + ret = -EBUSY; + goto err_misc_register; + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res == NULL) { + dev_err(&pdev->dev, "Failed to get irq resource.\n"); + ret = -ENODEV; + goto err_get_irq; + } + irq_num = res->start; + + dev_info(dev, "Requesting irq %i for %s\n", irq_num, pdev->name); + ret = request_irq(irq_num, s5p_cec_irq_handler, IRQF_DISABLED, pdev->name, &pdev->id); + if (ret != 0) { + dev_err(dev, "Failed to install irq (%d), error %i\n", irq_num, ret); + goto err_request_irq; + } + + init_waitqueue_head(&cec_rx_struct.waitq); + spin_lock_init(&cec_rx_struct.lock); + init_waitqueue_head(&cec_tx_struct.waitq); + + buffer = kmalloc(CEC_TX_BUFF_SIZE, GFP_KERNEL); + if (!buffer) { + dev_err(dev, "kmalloc() failed\n"); + ret = -EIO; + goto err_kmalloc; + } + + cec_rx_struct.buffer = buffer; + cec_rx_struct.size = 0; + + hdmi_cec_clk = clk_get(&pdev->dev, "hdmicec"); + if (IS_ERR(hdmi_cec_clk)) { + dev_err(dev, "failed to find clock 'hdmicec'\n"); + ret = -ENOENT; + goto err_clock; + } + + dev_info(&pdev->dev, "probe successful\n"); + return ret; // All good + + // Unwind the allocations on error +err_clock: + kfree(cec_rx_struct.buffer); + cec_rx_struct.buffer = NULL; +err_kmalloc: + free_irq(irq_num, &pdev->id); +err_request_irq: +err_get_irq: + misc_deregister(&cec_misc_device); +err_misc_register: + s5p_cec_mem_release(pdev); +err_mem_probe: + return ret; +} + +static int s5p_cec_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + dev_info(dev, "s5p_cec_remove, putting clk to sleep\n"); + clk_put(hdmi_cec_clk); + + dev_info(dev, "s5p_cec_remove, cec_rx_struct.buffer=%p\n", cec_rx_struct.buffer); + if(cec_rx_struct.buffer) + { + kfree(cec_rx_struct.buffer); + cec_rx_struct.buffer = NULL; + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if(res) + { + int irq_num = res->start; + printk(KERN_INFO "s5p_cec_remove, irq=%i\n", irq_num); + free_irq(irq_num, &pdev->id); + } + + misc_deregister(&cec_misc_device); + + s5p_cec_mem_release(pdev); + + return 0; +} + +#ifdef CONFIG_PM + static int s5p_cec_suspend(struct platform_device *dev, pm_message_t state) + { + printk(KERN_INFO "s5p_cec_suspend is a NOP\n"); + return 0; + } + + static int s5p_cec_resume(struct platform_device *dev) + { + printk(KERN_INFO "s5p_cec_resume is a NOP\n"); + return 0; + } +#else + #define s5p_cec_suspend NULL + #define s5p_cec_resume NULL +#endif + +static struct platform_driver s5p_cec_driver = { + .probe = s5p_cec_probe, + .remove = s5p_cec_remove, + .suspend = s5p_cec_suspend, + .resume = s5p_cec_resume, + .driver = { + .name = "s5p-cec", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(s5p_cec_driver); diff --git a/drivers/media/platform/s5p-tv/cec_hw.c b/drivers/media/platform/s5p-tv/cec_hw.c new file mode 100644 index 00000000000000..108e5b3da04bc3 --- /dev/null +++ b/drivers/media/platform/s5p-tv/cec_hw.c @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * CEC for Samsung S5P TVOUT driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include +#include +#include + +#include + +#include "cec_hw.h" +#include "regs-cec.h" + +#define S5P_HDMI_FIN 24000000 +#define CEC_DIV_RATIO 320000 + +#define CEC_MESSAGE_BROADCAST_MASK 0x0F +#define CEC_MESSAGE_BROADCAST 0x0F +#define CEC_FILTER_THRESHOLD 0x15 + +static struct resource *cec_mem; +void __iomem *cec_base; + +struct cec_rx_struct cec_rx_struct; +struct cec_tx_struct cec_tx_struct; + +void s5p_cec_set_divider(void) +{ + u32 div_ratio, reg, div_val; + + div_ratio = (S5P_HDMI_FIN / CEC_DIV_RATIO) - 1; + + reg = readl(S5P_HDMI_PHY_CONTROL); + reg = (reg & ~(0x3FF << 16)) | (div_ratio << 16); + + writel(reg, S5P_HDMI_PHY_CONTROL); + + div_val = (CEC_DIV_RATIO * 0.00005) - 1; + + writeb(0x0, cec_base + S5P_CEC_DIVISOR_3); + writeb(0x0, cec_base + S5P_CEC_DIVISOR_2); + writeb(0x0, cec_base + S5P_CEC_DIVISOR_1); + writeb(div_val, cec_base + S5P_CEC_DIVISOR_0); +} + +void s5p_cec_enable_rx(void) +{ + u8 reg; + + reg = readb(cec_base + S5P_CEC_RX_CTRL); + reg |= S5P_CEC_RX_CTRL_ENABLE; + writeb(reg, cec_base + S5P_CEC_RX_CTRL); +} + +void s5p_cec_mask_rx_interrupts(void) +{ + u8 reg; + + reg = readb(cec_base + S5P_CEC_IRQ_MASK); + reg |= S5P_CEC_IRQ_RX_DONE; + reg |= S5P_CEC_IRQ_RX_ERROR; + writeb(reg, cec_base + S5P_CEC_IRQ_MASK); +} + +void s5p_cec_unmask_rx_interrupts(void) +{ + u8 reg; + + reg = readb(cec_base + S5P_CEC_IRQ_MASK); + reg &= ~S5P_CEC_IRQ_RX_DONE; + reg &= ~S5P_CEC_IRQ_RX_ERROR; + writeb(reg, cec_base + S5P_CEC_IRQ_MASK); +} + +void s5p_cec_mask_tx_interrupts(void) +{ + u8 reg; + + reg = readb(cec_base + S5P_CEC_IRQ_MASK); + reg |= S5P_CEC_IRQ_TX_DONE; + reg |= S5P_CEC_IRQ_TX_ERROR; + writeb(reg, cec_base + S5P_CEC_IRQ_MASK); +} + +void s5p_cec_unmask_tx_interrupts(void) +{ + u8 reg; + + reg = readb(cec_base + S5P_CEC_IRQ_MASK); + reg &= ~S5P_CEC_IRQ_TX_DONE; + reg &= ~S5P_CEC_IRQ_TX_ERROR; + writeb(reg, cec_base + S5P_CEC_IRQ_MASK); +} + +void s5p_cec_tx_reset(void) +{ + writeb(S5P_CEC_TX_CTRL_RESET, cec_base + S5P_CEC_TX_CTRL); +} + +void s5p_cec_rx_reset(void) +{ + writeb(S5P_CEC_RX_CTRL_RESET, cec_base + S5P_CEC_RX_CTRL); +} + +void s5p_cec_reset(void) +{ + s5p_cec_rx_reset(); + s5p_cec_tx_reset(); +} + +void s5p_cec_threshold(void) +{ + writeb(CEC_FILTER_THRESHOLD, cec_base + S5P_CEC_RX_FILTER_TH); + writeb(0, cec_base + S5P_CEC_RX_FILTER_CTRL); +} + +void s5p_cec_set_tx_state(enum cec_state state) +{ + atomic_set(&cec_tx_struct.state, state); +} + +void s5p_cec_set_rx_state(enum cec_state state) +{ + atomic_set(&cec_rx_struct.state, state); +} + +void s5p_cec_copy_packet(char *data, size_t count) +{ + int i = 0; + u8 reg; + + while (i < count) { + writeb(data[i], cec_base + (S5P_CEC_TX_BUFF0 + (i * 4))); + i++; + } + printk(KERN_INFO "s5p_cec_copy_packet(): written %i bytes to cec_base\n", count); + + writeb(count, cec_base + S5P_CEC_TX_BYTES); + s5p_cec_set_tx_state(STATE_TX); + reg = readb(cec_base + S5P_CEC_TX_CTRL); + reg |= S5P_CEC_TX_CTRL_START; + + if ((data[0] & CEC_MESSAGE_BROADCAST_MASK) == CEC_MESSAGE_BROADCAST) + reg |= S5P_CEC_TX_CTRL_BCAST; + else + reg &= ~S5P_CEC_TX_CTRL_BCAST; + + reg |= 0x50; + writeb(reg, cec_base + S5P_CEC_TX_CTRL); +} + +void s5p_cec_set_addr(u32 addr) +{ + writeb(addr & 0x0F, cec_base + S5P_CEC_LOGIC_ADDR); +} + +u32 s5p_cec_get_status(void) +{ + u32 status = 0; + + status = readb(cec_base + S5P_CEC_STATUS_0); + status |= readb(cec_base + S5P_CEC_STATUS_1) << 8; + status |= readb(cec_base + S5P_CEC_STATUS_2) << 16; + status |= readb(cec_base + S5P_CEC_STATUS_3) << 24; + + return status; +} + +void s5p_clr_pending_tx(void) +{ + writeb(S5P_CEC_IRQ_TX_DONE | S5P_CEC_IRQ_TX_ERROR, + cec_base + S5P_CEC_IRQ_CLEAR); +} + +void s5p_clr_pending_rx(void) +{ + writeb(S5P_CEC_IRQ_RX_DONE | S5P_CEC_IRQ_RX_ERROR, + cec_base + S5P_CEC_IRQ_CLEAR); +} + +void s5p_cec_get_rx_buf(u32 size, u8 *buffer) +{ + u32 i = 0; + + while (i < size) { + buffer[i] = readb(cec_base + S5P_CEC_RX_BUFF0 + (i * 4)); + i++; + } +} + +int s5p_cec_mem_probe(struct platform_device *pdev) +{ + struct resource *res; + size_t size = 0; + int ret = 0; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, + "Failed to get memory region resource for cec\n"); + return -ENOENT; + } else + size = resource_size(res); + + cec_mem = request_mem_region(res->start, size, pdev->name); + if (cec_mem == NULL) { + dev_err(&pdev->dev, "Failed to get memory at size %i, name %s\n", size, pdev->name); + return -ENOENT; + } + + cec_base = ioremap(res->start, size); + if (cec_base == NULL) { + dev_err(&pdev->dev, + "Failed to ioremap address region for cec\n"); + return -ENOENT; + } + dev_info(&pdev->dev, "s5p_cec_mem_probe(): mapped cec_base to %p, size 0x%x\n", cec_base, size); + return ret; +} + +int s5p_cec_mem_release(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "Releasing memory\n"); + iounmap(cec_base); + if (cec_mem != NULL) { + if (release_resource(cec_mem)) + dev_err(&pdev->dev, "Can't remove s5p-cec driver\n"); + + kfree(cec_mem); + + cec_mem = NULL; + } + return 0; +} diff --git a/drivers/media/platform/s5p-tv/cec_hw.h b/drivers/media/platform/s5p-tv/cec_hw.h new file mode 100644 index 00000000000000..3a0e4c13f6e193 --- /dev/null +++ b/drivers/media/platform/s5p-tv/cec_hw.h @@ -0,0 +1,68 @@ + +#ifndef CEC_HW_H_ +#define CEC_HW_H_ + +#include + +#define to_tvout_plat(d) (to_platform_device(d)->dev.platform_data) + +#define tvout_err(fmt, ...) \ + printk(KERN_ERR "[%s] %s(): " fmt, \ + DRV_NAME, __func__, ##__VA_ARGS__) + +#ifndef tvout_dbg +#ifdef CONFIG_S5P_TVOUT_DEBUG +#define tvout_dbg(fmt, ...) \ + printk(KERN_INFO "[%s] %s(): " fmt, \ + DRV_NAME, __func__, ##__VA_ARGS__) +#else +#define tvout_dbg(fmt, ...) +#endif +#endif + + +enum cec_state { + STATE_RX, + STATE_TX, + STATE_DONE, + STATE_ERROR +}; + +struct cec_rx_struct { + spinlock_t lock; + wait_queue_head_t waitq; + atomic_t state; + u8 *buffer; + unsigned int size; +}; + +struct cec_tx_struct { + wait_queue_head_t waitq; + atomic_t state; +}; + +extern struct cec_rx_struct cec_rx_struct; +extern struct cec_tx_struct cec_tx_struct; + +void s5p_cec_set_divider(void); +void s5p_cec_enable_rx(void); +void s5p_cec_mask_rx_interrupts(void); +void s5p_cec_unmask_rx_interrupts(void); +void s5p_cec_mask_tx_interrupts(void); +void s5p_cec_unmask_tx_interrupts(void); +void s5p_cec_reset(void); +void s5p_cec_tx_reset(void); +void s5p_cec_rx_reset(void); +void s5p_cec_threshold(void); +void s5p_cec_set_tx_state(enum cec_state state); +void s5p_cec_set_rx_state(enum cec_state state); +void s5p_cec_copy_packet(char *data, size_t count); +void s5p_cec_set_addr(u32 addr); +u32 s5p_cec_get_status(void); +void s5p_clr_pending_tx(void); +void s5p_clr_pending_rx(void); +void s5p_cec_get_rx_buf(u32 size, u8 *buffer); +int s5p_cec_mem_probe(struct platform_device *pdev); +int s5p_cec_mem_release(struct platform_device *pdev); + +#endif \ No newline at end of file diff --git a/drivers/media/platform/s5p-tv/regs-cec.h b/drivers/media/platform/s5p-tv/regs-cec.h new file mode 100644 index 00000000000000..6a86d6e153a263 --- /dev/null +++ b/drivers/media/platform/s5p-tv/regs-cec.h @@ -0,0 +1,95 @@ +/* linux/drivers/media/video/samsung/tv20/s5pc100/regs/regs-cec.h + * + * Copyright (c) 2009 Samsung Electronics + * http://www.samsungsemi.com/ + * + * CEC register header file for Samsung TVOut driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +/* + * Source: + * http://code.google.com/p/openwrt-for-embedded/source/browse/trunk/target/linux/s5pv210/files-2.6.35/arch/arm/plat-s5p/include/plat/regs-cec.h?r=56 + */ + +#ifndef __REGS_CEC_H +#define __REGS_CEC_H + +#define HDMIDP_CECREG(x) (x) + +#define S5P_CEC_STATUS_0 HDMIDP_CECREG(0x0000) +#define S5P_CEC_STATUS_1 HDMIDP_CECREG(0x0004) +#define S5P_CEC_STATUS_2 HDMIDP_CECREG(0x0008) +#define S5P_CEC_STATUS_3 HDMIDP_CECREG(0x000C) +#define S5P_CEC_IRQ_MASK HDMIDP_CECREG(0x0010) +#define S5P_CEC_IRQ_CLEAR HDMIDP_CECREG(0x0014) +#define S5P_CEC_LOGIC_ADDR HDMIDP_CECREG(0x0020) +#define S5P_CEC_DIVISOR_0 HDMIDP_CECREG(0x0030) +#define S5P_CEC_DIVISOR_1 HDMIDP_CECREG(0x0034) +#define S5P_CEC_DIVISOR_2 HDMIDP_CECREG(0x0038) +#define S5P_CEC_DIVISOR_3 HDMIDP_CECREG(0x003C) + +#define S5P_CEC_TX_CTRL HDMIDP_CECREG(0x0040) +#define S5P_CEC_TX_BYTES HDMIDP_CECREG(0x0044) +#define S5P_CEC_TX_STAT0 HDMIDP_CECREG(0x0060) +#define S5P_CEC_TX_STAT1 HDMIDP_CECREG(0x0064) +#define S5P_CEC_TX_BUFF0 HDMIDP_CECREG(0x0080) +#define S5P_CEC_TX_BUFF1 HDMIDP_CECREG(0x0084) +#define S5P_CEC_TX_BUFF2 HDMIDP_CECREG(0x0088) +#define S5P_CEC_TX_BUFF3 HDMIDP_CECREG(0x008C) +#define S5P_CEC_TX_BUFF4 HDMIDP_CECREG(0x0090) +#define S5P_CEC_TX_BUFF5 HDMIDP_CECREG(0x0094) +#define S5P_CEC_TX_BUFF6 HDMIDP_CECREG(0x0098) +#define S5P_CEC_TX_BUFF7 HDMIDP_CECREG(0x009C) +#define S5P_CEC_TX_BUFF8 HDMIDP_CECREG(0x00A0) +#define S5P_CEC_TX_BUFF9 HDMIDP_CECREG(0x00A4) +#define S5P_CEC_TX_BUFF10 HDMIDP_CECREG(0x00A8) +#define S5P_CEC_TX_BUFF11 HDMIDP_CECREG(0x00AC) +#define S5P_CEC_TX_BUFF12 HDMIDP_CECREG(0x00B0) +#define S5P_CEC_TX_BUFF13 HDMIDP_CECREG(0x00B4) +#define S5P_CEC_TX_BUFF14 HDMIDP_CECREG(0x00B8) +#define S5P_CEC_TX_BUFF15 HDMIDP_CECREG(0x00BC) + +#define S5P_CEC_RX_CTRL HDMIDP_CECREG(0x00C0) +#define S5P_CEC_RX_STAT0 HDMIDP_CECREG(0x00E0) +#define S5P_CEC_RX_STAT1 HDMIDP_CECREG(0x00E4) +#define S5P_CEC_RX_BUFF0 HDMIDP_CECREG(0x0100) +#define S5P_CEC_RX_BUFF1 HDMIDP_CECREG(0x0104) +#define S5P_CEC_RX_BUFF2 HDMIDP_CECREG(0x0108) +#define S5P_CEC_RX_BUFF3 HDMIDP_CECREG(0x010C) +#define S5P_CEC_RX_BUFF4 HDMIDP_CECREG(0x0110) +#define S5P_CEC_RX_BUFF5 HDMIDP_CECREG(0x0114) +#define S5P_CEC_RX_BUFF6 HDMIDP_CECREG(0x0118) +#define S5P_CEC_RX_BUFF7 HDMIDP_CECREG(0x011C) +#define S5P_CEC_RX_BUFF8 HDMIDP_CECREG(0x0120) +#define S5P_CEC_RX_BUFF9 HDMIDP_CECREG(0x0124) +#define S5P_CEC_RX_BUFF10 HDMIDP_CECREG(0x0128) +#define S5P_CEC_RX_BUFF11 HDMIDP_CECREG(0x012C) +#define S5P_CEC_RX_BUFF12 HDMIDP_CECREG(0x0130) +#define S5P_CEC_RX_BUFF13 HDMIDP_CECREG(0x0134) +#define S5P_CEC_RX_BUFF14 HDMIDP_CECREG(0x0138) +#define S5P_CEC_RX_BUFF15 HDMIDP_CECREG(0x013C) + +#define S5P_CEC_RX_FILTER_CTRL HDMIDP_CECREG(0x0180) +#define S5P_CEC_RX_FILTER_TH HDMIDP_CECREG(0x0184) + +#define S5P_CEC_IRQ_TX_DONE (1<<0) +#define S5P_CEC_IRQ_TX_ERROR (1<<1) +#define S5P_CEC_IRQ_RX_DONE (1<<4) +#define S5P_CEC_IRQ_RX_ERROR (1<<5) + +#define S5P_CEC_TX_CTRL_START (1<<0) +#define S5P_CEC_TX_CTRL_BCAST (1<<1) +#define S5P_CEC_TX_CTRL_RETRY (0x04<<4) +#define S5P_CEC_TX_CTRL_RESET (1<<7) + +#define S5P_CEC_RX_CTRL_ENABLE (1<<0) +#define S5P_CEC_RX_CTRL_RESET (1<<7) + +#define S5P_CEC_LOGIC_ADDR_MASK 0x0F + +#endif /* __REGS_CEC_H */ +