OLD | NEW |
(Empty) | |
| 1 /* linux/drivers/media/video/samsung/tv20/hpd.c |
| 2 * |
| 3 * Copyright (c) 2010 Samsung Electronics Co., Ltd. |
| 4 * http://www.samsung.com/ |
| 5 * |
| 6 * S5PV210 - hpd interface ftn file for Samsung TVOut driver |
| 7 * |
| 8 * This program is free software; you can redistribute it and/or modify |
| 9 * it under the terms of the GNU General Public License version 2 as |
| 10 * published by the Free Software Foundation. |
| 11 */ |
| 12 |
| 13 #include <linux/module.h> |
| 14 #include <linux/kernel.h> |
| 15 #include <linux/interrupt.h> |
| 16 #include <linux/fs.h> |
| 17 #include <linux/miscdevice.h> |
| 18 #include <linux/errno.h> |
| 19 #include <linux/wait.h> |
| 20 #include <linux/poll.h> |
| 21 #include <linux/irq.h> |
| 22 #include <linux/kobject.h> |
| 23 #include <linux/workqueue.h> |
| 24 |
| 25 #include <linux/io.h> |
| 26 |
| 27 #include <mach/gpio.h> |
| 28 #include <plat/gpio-cfg.h> |
| 29 |
| 30 #include <mach/regs-hdmi.h> |
| 31 |
| 32 #include "s5p_tv.h" |
| 33 #include "hpd.h" |
| 34 |
| 35 /* #define HPDDEBUG */ |
| 36 |
| 37 #define USEEXTINT |
| 38 |
| 39 #ifdef HPDDEBUG |
| 40 #define HPDIFPRINTK(fmt, args...) \ |
| 41 printk(KERN_INFO "[HPD_IF] %s: " fmt, __func__ , ## args) |
| 42 #else |
| 43 #define HPDIFPRINTK(fmt, args...) |
| 44 #endif |
| 45 |
| 46 static struct hpd_struct hpd_struct; |
| 47 |
| 48 #ifndef USEEXTINT |
| 49 static int last_hpd_state; |
| 50 #endif |
| 51 |
| 52 #ifdef CONFIG_PM |
| 53 #ifndef USEEXTINT |
| 54 static bool hdmi_on; |
| 55 #endif |
| 56 #endif |
| 57 |
| 58 static DECLARE_WORK(hpd_work, s5p_tv_kobject_uevent); |
| 59 |
| 60 int s5p_hpd_get_state(void) |
| 61 { |
| 62 return atomic_read(&hpd_struct.state); |
| 63 } |
| 64 |
| 65 |
| 66 int s5p_hpd_open(struct inode *inode, struct file *file) |
| 67 { |
| 68 #ifdef USEEXTINT |
| 69 #else |
| 70 /* adjust the duration of HPD detection */ |
| 71 s5p_tv_clk_gate(true); |
| 72 hdmi_on = true; |
| 73 |
| 74 s5p_hdmi_hpd_gen(); |
| 75 |
| 76 s3c_gpio_cfgpin(S5PV210_GPH1(5), S5P_GPH1_5_HDMI_HPD); |
| 77 s3c_gpio_setpull(S5PV210_GPH1(5), S3C_GPIO_PULL_UP); |
| 78 |
| 79 s5p_hdmi_enable_interrupts(HDMI_IRQ_HPD_PLUG); |
| 80 s5p_hdmi_enable_interrupts(HDMI_IRQ_HPD_UNPLUG); |
| 81 #endif |
| 82 |
| 83 |
| 84 return 0; |
| 85 } |
| 86 |
| 87 int s5p_hpd_release(struct inode *inode, struct file *file) |
| 88 { |
| 89 /* disable HPD interrupts */ |
| 90 #ifdef USEEXTINT |
| 91 #else |
| 92 s5p_tv_clk_gate(false); |
| 93 hdmi_on = false; |
| 94 |
| 95 s5p_hdmi_disable_interrupts(HDMI_IRQ_HPD_PLUG); |
| 96 s5p_hdmi_disable_interrupts(HDMI_IRQ_HPD_UNPLUG); |
| 97 #endif |
| 98 |
| 99 return 0; |
| 100 } |
| 101 |
| 102 ssize_t s5p_hpd_read(struct file *file, char __user *buffer, |
| 103 size_t count, loff_t *ppos) |
| 104 { |
| 105 ssize_t retval; |
| 106 |
| 107 if (wait_event_interruptible(hpd_struct.waitq, |
| 108 atomic_read(&hpd_struct.state) != -1)) |
| 109 return -ERESTARTSYS; |
| 110 |
| 111 spin_lock_irq(&hpd_struct.lock); |
| 112 |
| 113 retval = put_user(atomic_read(&hpd_struct.state), |
| 114 (unsigned int __user *) buffer); |
| 115 |
| 116 atomic_set(&hpd_struct.state, -1); |
| 117 |
| 118 spin_unlock_irq(&hpd_struct.lock); |
| 119 |
| 120 return retval; |
| 121 } |
| 122 |
| 123 unsigned int s5p_hpd_poll(struct file *file, poll_table *wait) |
| 124 { |
| 125 poll_wait(file, &hpd_struct.waitq, wait); |
| 126 |
| 127 if (atomic_read(&hpd_struct.state) != -1) |
| 128 return POLLIN | POLLRDNORM; |
| 129 |
| 130 return 0; |
| 131 } |
| 132 |
| 133 static const struct file_operations hpd_fops = { |
| 134 .owner = THIS_MODULE, |
| 135 .open = s5p_hpd_open, |
| 136 .release = s5p_hpd_release, |
| 137 .read = s5p_hpd_read, |
| 138 .poll = s5p_hpd_poll, |
| 139 }; |
| 140 |
| 141 static struct miscdevice hpd_misc_device = { |
| 142 HPD_MINOR, |
| 143 "HPD", |
| 144 &hpd_fops, |
| 145 }; |
| 146 |
| 147 /* |
| 148 * HPD interrupt handler |
| 149 * |
| 150 * Handles interrupt requests from HPD hardware. |
| 151 * Handler changes value of internal variable and notifies waiting thread. |
| 152 */ |
| 153 irqreturn_t s5p_hpd_irq_handler(int irq, void *dev_id) |
| 154 { |
| 155 #ifdef USEEXTINT |
| 156 spin_lock_irq(&hpd_struct.lock); |
| 157 |
| 158 if (gpio_get_value(S5PV210_GPH1(5))) { |
| 159 if (atomic_read(&hpd_struct.state) == HPD_HI) |
| 160 goto out; |
| 161 atomic_set(&hpd_struct.state, HPD_HI); |
| 162 } else { |
| 163 if (atomic_read(&hpd_struct.state) == HPD_LO) |
| 164 goto out; |
| 165 atomic_set(&hpd_struct.state, HPD_LO); |
| 166 } |
| 167 |
| 168 if (atomic_read(&hpd_struct.state)) |
| 169 set_irq_type(IRQ_EINT13, IRQ_TYPE_LEVEL_LOW); |
| 170 else |
| 171 set_irq_type(IRQ_EINT13, IRQ_TYPE_LEVEL_HIGH); |
| 172 |
| 173 /* Send UEvent for HPD - added by jyg1004 */ |
| 174 schedule_work(&hpd_work); |
| 175 |
| 176 spin_unlock_irq(&hpd_struct.lock); |
| 177 |
| 178 HPDIFPRINTK("hpd_status = %d\n", atomic_read(&hpd_struct.state)); |
| 179 |
| 180 return IRQ_HANDLED; |
| 181 out: |
| 182 spin_unlock_irq(&hpd_struct.lock); |
| 183 |
| 184 return IRQ_HANDLED; |
| 185 #else |
| 186 u8 flag; |
| 187 int ret = IRQ_HANDLED; |
| 188 |
| 189 /* read flag register */ |
| 190 flag = s5p_hdmi_get_interrupts(); |
| 191 |
| 192 /* is this our interrupt? */ |
| 193 |
| 194 if (!(flag & (1 << HDMI_IRQ_HPD_PLUG | 1 << HDMI_IRQ_HPD_UNPLUG))) { |
| 195 ret = IRQ_NONE; |
| 196 goto out; |
| 197 } |
| 198 |
| 199 /* workaround: ignore HPD IRQ caused by reseting HDCP engine */ |
| 200 if (s5p_hdmi_get_swhpd_status()) { |
| 201 s5p_hdmi_swhpd_disable(); |
| 202 /* clear pending bit */ |
| 203 s5p_hdmi_clear_pending(HDMI_IRQ_HPD_UNPLUG); |
| 204 s5p_hdmi_clear_pending(HDMI_IRQ_HPD_PLUG); |
| 205 ret = IRQ_HANDLED; |
| 206 goto out; |
| 207 } |
| 208 |
| 209 if (flag == (1 << HDMI_IRQ_HPD_PLUG | 1 << HDMI_IRQ_HPD_UNPLUG)) { |
| 210 HPDIFPRINTK("HPD_HI && HPD_LO\n"); |
| 211 |
| 212 if (last_hpd_state == HPD_HI && s5p_hdmi_get_hpd_status()) |
| 213 /*if ( last_hpd_state == HPD_HI ) */ |
| 214 flag = 1 << HDMI_IRQ_HPD_UNPLUG; |
| 215 else |
| 216 flag = 1 << HDMI_IRQ_HPD_PLUG; |
| 217 } |
| 218 |
| 219 if (flag & (1 << HDMI_IRQ_HPD_PLUG)) { |
| 220 HPDIFPRINTK("HPD_HI\n"); |
| 221 /* clear pending bit */ |
| 222 s5p_hdmi_clear_pending(HDMI_IRQ_HPD_PLUG); |
| 223 s5p_hdmi_clear_pending(HDMI_IRQ_HPD_UNPLUG); |
| 224 atomic_set(&hpd_struct.state, HPD_HI); |
| 225 /* workaround: enable HDMI_IRQ_HPD_UNPLUG interrupt */ |
| 226 s5p_hdmi_disable_interrupts(HDMI_IRQ_HPD_PLUG); |
| 227 s5p_hdmi_enable_interrupts(HDMI_IRQ_HPD_UNPLUG); |
| 228 last_hpd_state = HPD_HI; |
| 229 wake_up_interruptible(&hpd_struct.waitq); |
| 230 } else if (flag & (1 << HDMI_IRQ_HPD_UNPLUG)) { |
| 231 HPDIFPRINTK("HPD_LO\n"); |
| 232 /* clear pending bit */ |
| 233 s5p_hdmi_clear_pending(HDMI_IRQ_HPD_PLUG); |
| 234 s5p_hdmi_clear_pending(HDMI_IRQ_HPD_UNPLUG); |
| 235 atomic_set(&hpd_struct.state, HPD_LO); |
| 236 /* workaround: disable HDMI_IRQ_HPD_UNPLUG interrupt */ |
| 237 last_hpd_state = HPD_LO; |
| 238 s5p_hdmi_disable_interrupts(HDMI_IRQ_HPD_UNPLUG); |
| 239 s5p_hdmi_enable_interrupts(HDMI_IRQ_HPD_PLUG); |
| 240 wake_up_interruptible(&hpd_struct.waitq); |
| 241 } |
| 242 |
| 243 out: |
| 244 return IRQ_HANDLED; |
| 245 |
| 246 #endif |
| 247 } |
| 248 |
| 249 static int __init s5p_hpd_probe(struct platform_device *pdev) |
| 250 { |
| 251 if (misc_register(&hpd_misc_device)) { |
| 252 printk(KERN_WARNING |
| 253 "Couldn't register device 10, %d.\n", HPD_MINOR); |
| 254 return -EBUSY; |
| 255 } |
| 256 |
| 257 init_waitqueue_head(&hpd_struct.waitq); |
| 258 |
| 259 spin_lock_init(&hpd_struct.lock); |
| 260 |
| 261 atomic_set(&hpd_struct.state, -1); |
| 262 |
| 263 #ifdef USEEXTINT |
| 264 s3c_gpio_cfgpin(S5PV210_GPH1(5), S5P_GPH1_5_EXT_INT31_5); |
| 265 s3c_gpio_setpull(S5PV210_GPH1(5), S3C_GPIO_PULL_UP); |
| 266 |
| 267 if (gpio_get_value(S5PV210_GPH1(5))) |
| 268 atomic_set(&hpd_struct.state, HPD_HI); |
| 269 else |
| 270 atomic_set(&hpd_struct.state, HPD_LO); |
| 271 |
| 272 set_irq_type(IRQ_EINT13, IRQ_TYPE_EDGE_BOTH); |
| 273 |
| 274 if (request_irq(IRQ_EINT13, s5p_hpd_irq_handler, IRQF_DISABLED, |
| 275 "hpd", s5p_hpd_irq_handler)) { |
| 276 printk(KERN_ERR "failed to install %s irq\n", "hpd"); |
| 277 misc_deregister(&hpd_misc_device); |
| 278 return -EIO; |
| 279 } |
| 280 #else |
| 281 /* must be checked */ |
| 282 s5p_hdmi_register_isr(s5p_hpd_irq_handler, (u8)HDMI_IRQ_HPD_PLUG); |
| 283 s5p_hdmi_register_isr(s5p_hpd_irq_handler, (u8)HDMI_IRQ_HPD_UNPLUG); |
| 284 #endif |
| 285 |
| 286 return 0; |
| 287 } |
| 288 |
| 289 static int s5p_hpd_remove(struct platform_device *pdev) |
| 290 { |
| 291 return 0; |
| 292 } |
| 293 |
| 294 |
| 295 #ifdef CONFIG_PM |
| 296 |
| 297 int s5p_hpd_suspend(struct platform_device *dev, pm_message_t state) |
| 298 { |
| 299 #ifndef USEEXTINT |
| 300 if (hdmi_on) |
| 301 s5p_tv_clk_gate(false); |
| 302 #endif |
| 303 return 0; |
| 304 } |
| 305 |
| 306 int s5p_hpd_resume(struct platform_device *dev) |
| 307 { |
| 308 #ifndef USEEXTINT |
| 309 if (hdmi_on) |
| 310 s5p_tv_clk_gate(true); |
| 311 #endif |
| 312 return 0; |
| 313 } |
| 314 #else |
| 315 #define s5p_hpd_suspend NULL |
| 316 #define s5p_hpd_resume NULL |
| 317 #endif |
| 318 |
| 319 static struct platform_driver s5p_hpd_driver = { |
| 320 .probe = s5p_hpd_probe, |
| 321 .remove = s5p_hpd_remove, |
| 322 .suspend = s5p_hpd_suspend, |
| 323 .resume = s5p_hpd_resume, |
| 324 .driver = { |
| 325 .name = "s5p-hpd", |
| 326 .owner = THIS_MODULE, |
| 327 }, |
| 328 }; |
| 329 |
| 330 static char banner[] __initdata = |
| 331 "S5PV210 HPD Driver, (c) 2010 Samsung Electronics\n"; |
| 332 |
| 333 int __init s5p_hpd_init(void) |
| 334 { |
| 335 int ret; |
| 336 |
| 337 printk(banner); |
| 338 |
| 339 ret = platform_driver_register(&s5p_hpd_driver); |
| 340 |
| 341 if (ret) { |
| 342 printk(KERN_ERR "Platform Device Register Failed %d\n", ret); |
| 343 return -1; |
| 344 } |
| 345 |
| 346 return 0; |
| 347 } |
| 348 |
| 349 static void __exit s5p_hpd_exit(void) |
| 350 { |
| 351 misc_deregister(&hpd_misc_device); |
| 352 } |
| 353 |
| 354 module_init(s5p_hpd_init); |
| 355 module_exit(s5p_hpd_exit); |
| 356 |
| 357 |
OLD | NEW |