| /* |
| * HDMI PHY |
| * |
| * Copyright (C) 2013 Texas Instruments Incorporated |
| * |
| * 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 <linux/kernel.h> |
| #include <linux/err.h> |
| #include <linux/io.h> |
| #include <linux/platform_device.h> |
| #include <video/omapdss.h> |
| |
| #include "dss.h" |
| #include "hdmi.h" |
| |
| void hdmi_phy_dump(struct hdmi_phy_data *phy, struct seq_file *s) |
| { |
| #define DUMPPHY(r) seq_printf(s, "%-35s %08x\n", #r,\ |
| hdmi_read_reg(phy->base, r)) |
| |
| DUMPPHY(HDMI_TXPHY_TX_CTRL); |
| DUMPPHY(HDMI_TXPHY_DIGITAL_CTRL); |
| DUMPPHY(HDMI_TXPHY_POWER_CTRL); |
| DUMPPHY(HDMI_TXPHY_PAD_CFG_CTRL); |
| } |
| |
| static irqreturn_t hdmi_irq_handler(int irq, void *data) |
| { |
| struct hdmi_wp_data *wp = data; |
| u32 irqstatus; |
| |
| irqstatus = hdmi_wp_get_irqstatus(wp); |
| hdmi_wp_set_irqstatus(wp, irqstatus); |
| |
| if ((irqstatus & HDMI_IRQ_LINK_CONNECT) && |
| irqstatus & HDMI_IRQ_LINK_DISCONNECT) { |
| /* |
| * If we get both connect and disconnect interrupts at the same |
| * time, turn off the PHY, clear interrupts, and restart, which |
| * raises connect interrupt if a cable is connected, or nothing |
| * if cable is not connected. |
| */ |
| hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_OFF); |
| |
| hdmi_wp_set_irqstatus(wp, HDMI_IRQ_LINK_CONNECT | |
| HDMI_IRQ_LINK_DISCONNECT); |
| |
| hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_LDOON); |
| } else if (irqstatus & HDMI_IRQ_LINK_CONNECT) { |
| hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_TXON); |
| } else if (irqstatus & HDMI_IRQ_LINK_DISCONNECT) { |
| hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_LDOON); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| int hdmi_phy_enable(struct hdmi_phy_data *phy, struct hdmi_wp_data *wp, |
| struct hdmi_config *cfg) |
| { |
| u16 r = 0; |
| u32 irqstatus; |
| |
| hdmi_wp_clear_irqenable(wp, 0xffffffff); |
| |
| irqstatus = hdmi_wp_get_irqstatus(wp); |
| hdmi_wp_set_irqstatus(wp, irqstatus); |
| |
| r = hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_LDOON); |
| if (r) |
| return r; |
| |
| /* |
| * Read address 0 in order to get the SCP reset done completed |
| * Dummy access performed to make sure reset is done |
| */ |
| hdmi_read_reg(phy->base, HDMI_TXPHY_TX_CTRL); |
| |
| /* |
| * Write to phy address 0 to configure the clock |
| * use HFBITCLK write HDMI_TXPHY_TX_CONTROL_FREQOUT field |
| */ |
| REG_FLD_MOD(phy->base, HDMI_TXPHY_TX_CTRL, 0x1, 31, 30); |
| |
| /* Write to phy address 1 to start HDMI line (TXVALID and TMDSCLKEN) */ |
| hdmi_write_reg(phy->base, HDMI_TXPHY_DIGITAL_CTRL, 0xF0000000); |
| |
| /* Setup max LDO voltage */ |
| REG_FLD_MOD(phy->base, HDMI_TXPHY_POWER_CTRL, 0xB, 3, 0); |
| |
| /* Write to phy address 3 to change the polarity control */ |
| REG_FLD_MOD(phy->base, HDMI_TXPHY_PAD_CFG_CTRL, 0x1, 27, 27); |
| |
| r = request_threaded_irq(phy->irq, NULL, hdmi_irq_handler, |
| IRQF_ONESHOT, "OMAP HDMI", wp); |
| if (r) { |
| DSSERR("HDMI IRQ request failed\n"); |
| hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_OFF); |
| return r; |
| } |
| |
| hdmi_wp_set_irqenable(wp, |
| HDMI_IRQ_LINK_CONNECT | HDMI_IRQ_LINK_DISCONNECT); |
| |
| return 0; |
| } |
| |
| void hdmi_phy_disable(struct hdmi_phy_data *phy, struct hdmi_wp_data *wp) |
| { |
| free_irq(phy->irq, wp); |
| |
| hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_OFF); |
| } |
| |
| #define PHY_OFFSET 0x300 |
| #define PHY_SIZE 0x100 |
| |
| int hdmi_phy_init(struct platform_device *pdev, struct hdmi_phy_data *phy) |
| { |
| struct resource *res; |
| struct resource temp_res; |
| |
| res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy"); |
| if (!res) { |
| DSSDBG("can't get PHY mem resource by name\n"); |
| /* |
| * if hwmod/DT doesn't have the memory resource information |
| * split into HDMI sub blocks by name, we try again by getting |
| * the platform's first resource. this code will be removed when |
| * the driver can get the mem resources by name |
| */ |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (!res) { |
| DSSERR("can't get PHY mem resource\n"); |
| return -EINVAL; |
| } |
| |
| temp_res.start = res->start + PHY_OFFSET; |
| temp_res.end = temp_res.start + PHY_SIZE - 1; |
| res = &temp_res; |
| } |
| |
| phy->base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); |
| if (!phy->base) { |
| DSSERR("can't ioremap TX PHY\n"); |
| return -ENOMEM; |
| } |
| |
| phy->irq = platform_get_irq(pdev, 0); |
| if (phy->irq < 0) { |
| DSSERR("platform_get_irq failed\n"); |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |