// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2021-2022 Rockchip Electronics Co., Ltd. * Copyright (c) 2024 Collabora Ltd. * * Author: Algea Cao * Author: Cristian Ciocaltea */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dw-hdmi-qp.h" #define DDC_CI_ADDR 0x37 #define DDC_SEGMENT_ADDR 0x30 #define HDMI14_MAX_TMDSCLK 340000000 #define SCRAMB_POLL_DELAY_MS 3000 /* * Unless otherwise noted, entries in this table are 100% optimization. * Values can be obtained from dw_hdmi_qp_compute_n() but that function is * slow so we pre-compute values we expect to see. * * The values for TMDS 25175, 25200, 27000, 54000, 74250 and 148500 kHz are * the recommended N values specified in the Audio chapter of the HDMI * specification. */ static const struct dw_hdmi_audio_tmds_n { unsigned long tmds; unsigned int n_32k; unsigned int n_44k1; unsigned int n_48k; } common_tmds_n_table[] = { { .tmds = 25175000, .n_32k = 4576, .n_44k1 = 7007, .n_48k = 6864, }, { .tmds = 25200000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, }, { .tmds = 27000000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, }, { .tmds = 28320000, .n_32k = 4096, .n_44k1 = 5586, .n_48k = 6144, }, { .tmds = 30240000, .n_32k = 4096, .n_44k1 = 5642, .n_48k = 6144, }, { .tmds = 31500000, .n_32k = 4096, .n_44k1 = 5600, .n_48k = 6144, }, { .tmds = 32000000, .n_32k = 4096, .n_44k1 = 5733, .n_48k = 6144, }, { .tmds = 33750000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, }, { .tmds = 36000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, }, { .tmds = 40000000, .n_32k = 4096, .n_44k1 = 5733, .n_48k = 6144, }, { .tmds = 49500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, }, { .tmds = 50000000, .n_32k = 4096, .n_44k1 = 5292, .n_48k = 6144, }, { .tmds = 54000000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, }, { .tmds = 65000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, }, { .tmds = 68250000, .n_32k = 4096, .n_44k1 = 5376, .n_48k = 6144, }, { .tmds = 71000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, }, { .tmds = 72000000, .n_32k = 4096, .n_44k1 = 5635, .n_48k = 6144, }, { .tmds = 73250000, .n_32k = 11648, .n_44k1 = 14112, .n_48k = 6144, }, { .tmds = 74250000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, }, { .tmds = 75000000, .n_32k = 4096, .n_44k1 = 5880, .n_48k = 6144, }, { .tmds = 78750000, .n_32k = 4096, .n_44k1 = 5600, .n_48k = 6144, }, { .tmds = 78800000, .n_32k = 4096, .n_44k1 = 5292, .n_48k = 6144, }, { .tmds = 79500000, .n_32k = 4096, .n_44k1 = 4704, .n_48k = 6144, }, { .tmds = 83500000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, }, { .tmds = 85500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, }, { .tmds = 88750000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, }, { .tmds = 97750000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, }, { .tmds = 101000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, }, { .tmds = 106500000, .n_32k = 4096, .n_44k1 = 4704, .n_48k = 6144, }, { .tmds = 108000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, }, { .tmds = 115500000, .n_32k = 4096, .n_44k1 = 5712, .n_48k = 6144, }, { .tmds = 119000000, .n_32k = 4096, .n_44k1 = 5544, .n_48k = 6144, }, { .tmds = 135000000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, }, { .tmds = 146250000, .n_32k = 11648, .n_44k1 = 6272, .n_48k = 6144, }, { .tmds = 148500000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, }, { .tmds = 154000000, .n_32k = 4096, .n_44k1 = 5544, .n_48k = 6144, }, { .tmds = 162000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, }, /* For 297 MHz+ HDMI spec have some other rule for setting N */ { .tmds = 297000000, .n_32k = 3073, .n_44k1 = 4704, .n_48k = 5120, }, { .tmds = 594000000, .n_32k = 3073, .n_44k1 = 9408, .n_48k = 10240,}, /* End of table */ { .tmds = 0, .n_32k = 0, .n_44k1 = 0, .n_48k = 0, }, }; /* * These are the CTS values as recommended in the Audio chapter of the HDMI * specification. */ static const struct dw_hdmi_audio_tmds_cts { unsigned long tmds; unsigned int cts_32k; unsigned int cts_44k1; unsigned int cts_48k; } common_tmds_cts_table[] = { { .tmds = 25175000, .cts_32k = 28125, .cts_44k1 = 31250, .cts_48k = 28125, }, { .tmds = 25200000, .cts_32k = 25200, .cts_44k1 = 28000, .cts_48k = 25200, }, { .tmds = 27000000, .cts_32k = 27000, .cts_44k1 = 30000, .cts_48k = 27000, }, { .tmds = 54000000, .cts_32k = 54000, .cts_44k1 = 60000, .cts_48k = 54000, }, { .tmds = 74250000, .cts_32k = 74250, .cts_44k1 = 82500, .cts_48k = 74250, }, { .tmds = 148500000, .cts_32k = 148500, .cts_44k1 = 165000, .cts_48k = 148500, }, /* End of table */ { .tmds = 0, .cts_32k = 0, .cts_44k1 = 0, .cts_48k = 0, }, }; struct dw_hdmi_qp_i2c { struct i2c_adapter adap; struct mutex lock; /* used to serialize data transfers */ struct completion cmp; u8 stat; u8 slave_reg; bool is_regaddr; bool is_segment; }; struct dw_hdmi_qp { struct drm_bridge bridge; struct device *dev; struct dw_hdmi_qp_i2c *i2c; struct { const struct dw_hdmi_qp_phy_ops *ops; void *data; } phy; struct regmap *regm; unsigned long tmds_char_rate; }; static void dw_hdmi_qp_write(struct dw_hdmi_qp *hdmi, unsigned int val, int offset) { regmap_write(hdmi->regm, offset, val); } static unsigned int dw_hdmi_qp_read(struct dw_hdmi_qp *hdmi, int offset) { unsigned int val = 0; regmap_read(hdmi->regm, offset, &val); return val; } static void dw_hdmi_qp_mod(struct dw_hdmi_qp *hdmi, unsigned int data, unsigned int mask, unsigned int reg) { regmap_update_bits(hdmi->regm, reg, mask, data); } static struct dw_hdmi_qp *dw_hdmi_qp_from_bridge(struct drm_bridge *bridge) { return container_of(bridge, struct dw_hdmi_qp, bridge); } static void dw_hdmi_qp_set_cts_n(struct dw_hdmi_qp *hdmi, unsigned int cts, unsigned int n) { /* Set N */ dw_hdmi_qp_mod(hdmi, n, AUDPKT_ACR_N_VALUE, AUDPKT_ACR_CONTROL0); /* Set CTS */ if (cts) dw_hdmi_qp_mod(hdmi, AUDPKT_ACR_CTS_OVR_EN, AUDPKT_ACR_CTS_OVR_EN_MSK, AUDPKT_ACR_CONTROL1); else dw_hdmi_qp_mod(hdmi, 0, AUDPKT_ACR_CTS_OVR_EN_MSK, AUDPKT_ACR_CONTROL1); dw_hdmi_qp_mod(hdmi, AUDPKT_ACR_CTS_OVR_VAL(cts), AUDPKT_ACR_CTS_OVR_VAL_MSK, AUDPKT_ACR_CONTROL1); } static int dw_hdmi_qp_match_tmds_n_table(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk, unsigned long freq) { const struct dw_hdmi_audio_tmds_n *tmds_n = NULL; int i; for (i = 0; common_tmds_n_table[i].tmds != 0; i++) { if (pixel_clk == common_tmds_n_table[i].tmds) { tmds_n = &common_tmds_n_table[i]; break; } } if (!tmds_n) return -ENOENT; switch (freq) { case 32000: return tmds_n->n_32k; case 44100: case 88200: case 176400: return (freq / 44100) * tmds_n->n_44k1; case 48000: case 96000: case 192000: return (freq / 48000) * tmds_n->n_48k; default: return -ENOENT; } } static u32 dw_hdmi_qp_audio_math_diff(unsigned int freq, unsigned int n, unsigned int pixel_clk) { u64 cts = mul_u32_u32(pixel_clk, n); return do_div(cts, 128 * freq); } static unsigned int dw_hdmi_qp_compute_n(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk, unsigned long freq) { unsigned int min_n = DIV_ROUND_UP((128 * freq), 1500); unsigned int max_n = (128 * freq) / 300; unsigned int ideal_n = (128 * freq) / 1000; unsigned int best_n_distance = ideal_n; unsigned int best_n = 0; u64 best_diff = U64_MAX; int n; /* If the ideal N could satisfy the audio math, then just take it */ if (dw_hdmi_qp_audio_math_diff(freq, ideal_n, pixel_clk) == 0) return ideal_n; for (n = min_n; n <= max_n; n++) { u64 diff = dw_hdmi_qp_audio_math_diff(freq, n, pixel_clk); if (diff < best_diff || (diff == best_diff && abs(n - ideal_n) < best_n_distance)) { best_n = n; best_diff = diff; best_n_distance = abs(best_n - ideal_n); } /* * The best N already satisfy the audio math, and also be * the closest value to ideal N, so just cut the loop. */ if (best_diff == 0 && (abs(n - ideal_n) > best_n_distance)) break; } return best_n; } static unsigned int dw_hdmi_qp_find_n(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk, unsigned long sample_rate) { int n = dw_hdmi_qp_match_tmds_n_table(hdmi, pixel_clk, sample_rate); if (n > 0) return n; dev_warn(hdmi->dev, "Rate %lu missing; compute N dynamically\n", pixel_clk); return dw_hdmi_qp_compute_n(hdmi, pixel_clk, sample_rate); } static unsigned int dw_hdmi_qp_find_cts(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk, unsigned long sample_rate) { const struct dw_hdmi_audio_tmds_cts *tmds_cts = NULL; int i; for (i = 0; common_tmds_cts_table[i].tmds != 0; i++) { if (pixel_clk == common_tmds_cts_table[i].tmds) { tmds_cts = &common_tmds_cts_table[i]; break; } } if (!tmds_cts) return 0; switch (sample_rate) { case 32000: return tmds_cts->cts_32k; case 44100: case 88200: case 176400: return tmds_cts->cts_44k1; case 48000: case 96000: case 192000: return tmds_cts->cts_48k; default: return -ENOENT; } } static void dw_hdmi_qp_set_audio_interface(struct dw_hdmi_qp *hdmi, struct hdmi_codec_daifmt *fmt, struct hdmi_codec_params *hparms) { u32 conf0 = 0; /* Reset the audio data path of the AVP */ dw_hdmi_qp_write(hdmi, AVP_DATAPATH_PACKET_AUDIO_SWINIT_P, GLOBAL_SWRESET_REQUEST); /* Disable AUDS, ACR, AUDI */ dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_ACR_TX_EN | PKTSCHED_AUDS_TX_EN | PKTSCHED_AUDI_TX_EN, PKTSCHED_PKT_EN); /* Clear the audio FIFO */ dw_hdmi_qp_write(hdmi, AUDIO_FIFO_CLR_P, AUDIO_INTERFACE_CONTROL0); /* Select I2S interface as the audio source */ dw_hdmi_qp_mod(hdmi, AUD_IF_I2S, AUD_IF_SEL_MSK, AUDIO_INTERFACE_CONFIG0); /* Enable the active i2s lanes */ switch (hparms->channels) { case 7 ... 8: conf0 |= I2S_LINES_EN(3); fallthrough; case 5 ... 6: conf0 |= I2S_LINES_EN(2); fallthrough; case 3 ... 4: conf0 |= I2S_LINES_EN(1); fallthrough; default: conf0 |= I2S_LINES_EN(0); break; } dw_hdmi_qp_mod(hdmi, conf0, I2S_LINES_EN_MSK, AUDIO_INTERFACE_CONFIG0); /* * Enable bpcuv generated internally for L-PCM, or received * from stream for NLPCM/HBR. */ switch (fmt->bit_fmt) { case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE: conf0 = (hparms->channels == 8) ? AUD_HBR : AUD_ASP; conf0 |= I2S_BPCUV_RCV_EN; break; default: conf0 = AUD_ASP | I2S_BPCUV_RCV_DIS; break; } dw_hdmi_qp_mod(hdmi, conf0, I2S_BPCUV_RCV_MSK | AUD_FORMAT_MSK, AUDIO_INTERFACE_CONFIG0); /* Enable audio FIFO auto clear when overflow */ dw_hdmi_qp_mod(hdmi, AUD_FIFO_INIT_ON_OVF_EN, AUD_FIFO_INIT_ON_OVF_MSK, AUDIO_INTERFACE_CONFIG0); } /* * When transmitting IEC60958 linear PCM audio, these registers allow to * configure the channel status information of all the channel status * bits in the IEC60958 frame. For the moment this configuration is only * used when the I2S audio interface, General Purpose Audio (GPA), * or AHB audio DMA (AHBAUDDMA) interface is active * (for S/PDIF interface this information comes from the stream). */ static void dw_hdmi_qp_set_channel_status(struct dw_hdmi_qp *hdmi, u8 *channel_status, bool ref2stream) { /* * AUDPKT_CHSTATUS_OVR0: { RSV, RSV, CS1, CS0 } * AUDPKT_CHSTATUS_OVR1: { CS6, CS5, CS4, CS3 } * * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | * CS0: | Mode | d | c | b | a | * CS1: | Category Code | * CS2: | Channel Number | Source Number | * CS3: | Clock Accuracy | Sample Freq | * CS4: | Ori Sample Freq | Word Length | * CS5: | | CGMS-A | * CS6~CS23: Reserved * * a: use of channel status block * b: linear PCM identification: 0 for lpcm, 1 for nlpcm * c: copyright information * d: additional format information */ if (ref2stream) channel_status[0] |= IEC958_AES0_NONAUDIO; if ((dw_hdmi_qp_read(hdmi, AUDIO_INTERFACE_CONFIG0) & GENMASK(25, 24)) == AUD_HBR) { /* fixup cs for HBR */ channel_status[3] = (channel_status[3] & 0xf0) | IEC958_AES3_CON_FS_768000; channel_status[4] = (channel_status[4] & 0x0f) | IEC958_AES4_CON_ORIGFS_NOTID; } dw_hdmi_qp_write(hdmi, channel_status[0] | (channel_status[1] << 8), AUDPKT_CHSTATUS_OVR0); regmap_bulk_write(hdmi->regm, AUDPKT_CHSTATUS_OVR1, &channel_status[3], 1); if (ref2stream) dw_hdmi_qp_mod(hdmi, 0, AUDPKT_PBIT_FORCE_EN_MASK | AUDPKT_CHSTATUS_OVR_EN_MASK, AUDPKT_CONTROL0); else dw_hdmi_qp_mod(hdmi, AUDPKT_PBIT_FORCE_EN | AUDPKT_CHSTATUS_OVR_EN, AUDPKT_PBIT_FORCE_EN_MASK | AUDPKT_CHSTATUS_OVR_EN_MASK, AUDPKT_CONTROL0); } static void dw_hdmi_qp_set_sample_rate(struct dw_hdmi_qp *hdmi, unsigned long long tmds_char_rate, unsigned int sample_rate) { unsigned int n, cts; n = dw_hdmi_qp_find_n(hdmi, tmds_char_rate, sample_rate); cts = dw_hdmi_qp_find_cts(hdmi, tmds_char_rate, sample_rate); dw_hdmi_qp_set_cts_n(hdmi, cts, n); } static int dw_hdmi_qp_audio_enable(struct drm_connector *connector, struct drm_bridge *bridge) { struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge); if (hdmi->tmds_char_rate) dw_hdmi_qp_mod(hdmi, 0, AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE); return 0; } static int dw_hdmi_qp_audio_prepare(struct drm_connector *connector, struct drm_bridge *bridge, struct hdmi_codec_daifmt *fmt, struct hdmi_codec_params *hparms) { struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge); bool ref2stream = false; if (!hdmi->tmds_char_rate) return -ENODEV; if (fmt->bit_clk_provider | fmt->frame_clk_provider) { dev_err(hdmi->dev, "unsupported clock settings\n"); return -EINVAL; } if (fmt->bit_fmt == SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE) ref2stream = true; dw_hdmi_qp_set_audio_interface(hdmi, fmt, hparms); dw_hdmi_qp_set_sample_rate(hdmi, hdmi->tmds_char_rate, hparms->sample_rate); dw_hdmi_qp_set_channel_status(hdmi, hparms->iec.status, ref2stream); drm_atomic_helper_connector_hdmi_update_audio_infoframe(connector, &hparms->cea); return 0; } static void dw_hdmi_qp_audio_disable_regs(struct dw_hdmi_qp *hdmi) { /* * Keep ACR, AUDI, AUDS packet always on to make SINK device * active for better compatibility and user experience. * * This also fix POP sound on some SINK devices which wakeup * from suspend to active. */ dw_hdmi_qp_mod(hdmi, I2S_BPCUV_RCV_DIS, I2S_BPCUV_RCV_MSK, AUDIO_INTERFACE_CONFIG0); dw_hdmi_qp_mod(hdmi, AUDPKT_PBIT_FORCE_EN | AUDPKT_CHSTATUS_OVR_EN, AUDPKT_PBIT_FORCE_EN_MASK | AUDPKT_CHSTATUS_OVR_EN_MASK, AUDPKT_CONTROL0); dw_hdmi_qp_mod(hdmi, AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE); } static void dw_hdmi_qp_audio_disable(struct drm_connector *connector, struct drm_bridge *bridge) { struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge); drm_atomic_helper_connector_hdmi_clear_audio_infoframe(connector); if (hdmi->tmds_char_rate) dw_hdmi_qp_audio_disable_regs(hdmi); } static int dw_hdmi_qp_i2c_read(struct dw_hdmi_qp *hdmi, unsigned char *buf, unsigned int length) { struct dw_hdmi_qp_i2c *i2c = hdmi->i2c; int stat; if (!i2c->is_regaddr) { dev_dbg(hdmi->dev, "set read register address to 0\n"); i2c->slave_reg = 0x00; i2c->is_regaddr = true; } while (length--) { reinit_completion(&i2c->cmp); dw_hdmi_qp_mod(hdmi, i2c->slave_reg++ << 12, I2CM_ADDR, I2CM_INTERFACE_CONTROL0); if (i2c->is_segment) dw_hdmi_qp_mod(hdmi, I2CM_EXT_READ, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0); else dw_hdmi_qp_mod(hdmi, I2CM_FM_READ, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0); stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10); if (!stat) { dev_err(hdmi->dev, "i2c read timed out\n"); dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0); return -EAGAIN; } /* Check for error condition on the bus */ if (i2c->stat & I2CM_NACK_RCVD_IRQ) { dev_err(hdmi->dev, "i2c read error\n"); dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0); return -EIO; } *buf++ = dw_hdmi_qp_read(hdmi, I2CM_INTERFACE_RDDATA_0_3) & 0xff; dw_hdmi_qp_mod(hdmi, 0, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0); } i2c->is_segment = false; return 0; } static int dw_hdmi_qp_i2c_write(struct dw_hdmi_qp *hdmi, unsigned char *buf, unsigned int length) { struct dw_hdmi_qp_i2c *i2c = hdmi->i2c; int stat; if (!i2c->is_regaddr) { /* Use the first write byte as register address */ i2c->slave_reg = buf[0]; length--; buf++; i2c->is_regaddr = true; } while (length--) { reinit_completion(&i2c->cmp); dw_hdmi_qp_write(hdmi, *buf++, I2CM_INTERFACE_WRDATA_0_3); dw_hdmi_qp_mod(hdmi, i2c->slave_reg++ << 12, I2CM_ADDR, I2CM_INTERFACE_CONTROL0); dw_hdmi_qp_mod(hdmi, I2CM_FM_WRITE, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0); stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10); if (!stat) { dev_err(hdmi->dev, "i2c write time out!\n"); dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0); return -EAGAIN; } /* Check for error condition on the bus */ if (i2c->stat & I2CM_NACK_RCVD_IRQ) { dev_err(hdmi->dev, "i2c write nack!\n"); dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0); return -EIO; } dw_hdmi_qp_mod(hdmi, 0, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0); } return 0; } static int dw_hdmi_qp_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { struct dw_hdmi_qp *hdmi = i2c_get_adapdata(adap); struct dw_hdmi_qp_i2c *i2c = hdmi->i2c; u8 addr = msgs[0].addr; int i, ret = 0; if (addr == DDC_CI_ADDR) /* * The internal I2C controller does not support the multi-byte * read and write operations needed for DDC/CI. * FIXME: Blacklist the DDC/CI address until we filter out * unsupported I2C operations. */ return -EOPNOTSUPP; for (i = 0; i < num; i++) { if (msgs[i].len == 0) { dev_err(hdmi->dev, "unsupported transfer %d/%d, no data\n", i + 1, num); return -EOPNOTSUPP; } } guard(mutex)(&i2c->lock); /* Unmute DONE and ERROR interrupts */ dw_hdmi_qp_mod(hdmi, I2CM_NACK_RCVD_MASK_N | I2CM_OP_DONE_MASK_N, I2CM_NACK_RCVD_MASK_N | I2CM_OP_DONE_MASK_N, MAINUNIT_1_INT_MASK_N); /* Set slave device address taken from the first I2C message */ if (addr == DDC_SEGMENT_ADDR && msgs[0].len == 1) addr = DDC_ADDR; dw_hdmi_qp_mod(hdmi, addr << 5, I2CM_SLVADDR, I2CM_INTERFACE_CONTROL0); /* Set slave device register address on transfer */ i2c->is_regaddr = false; /* Set segment pointer for I2C extended read mode operation */ i2c->is_segment = false; for (i = 0; i < num; i++) { if (msgs[i].addr == DDC_SEGMENT_ADDR && msgs[i].len == 1) { i2c->is_segment = true; dw_hdmi_qp_mod(hdmi, DDC_SEGMENT_ADDR, I2CM_SEG_ADDR, I2CM_INTERFACE_CONTROL1); dw_hdmi_qp_mod(hdmi, *msgs[i].buf << 7, I2CM_SEG_PTR, I2CM_INTERFACE_CONTROL1); } else { if (msgs[i].flags & I2C_M_RD) ret = dw_hdmi_qp_i2c_read(hdmi, msgs[i].buf, msgs[i].len); else ret = dw_hdmi_qp_i2c_write(hdmi, msgs[i].buf, msgs[i].len); } if (ret < 0) break; } if (!ret) ret = num; /* Mute DONE and ERROR interrupts */ dw_hdmi_qp_mod(hdmi, 0, I2CM_OP_DONE_MASK_N | I2CM_NACK_RCVD_MASK_N, MAINUNIT_1_INT_MASK_N); return ret; } static u32 dw_hdmi_qp_i2c_func(struct i2c_adapter *adapter) { return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; } static const struct i2c_algorithm dw_hdmi_qp_algorithm = { .master_xfer = dw_hdmi_qp_i2c_xfer, .functionality = dw_hdmi_qp_i2c_func, }; static struct i2c_adapter *dw_hdmi_qp_i2c_adapter(struct dw_hdmi_qp *hdmi) { struct dw_hdmi_qp_i2c *i2c; struct i2c_adapter *adap; int ret; i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL); if (!i2c) return ERR_PTR(-ENOMEM); mutex_init(&i2c->lock); init_completion(&i2c->cmp); adap = &i2c->adap; adap->owner = THIS_MODULE; adap->dev.parent = hdmi->dev; adap->algo = &dw_hdmi_qp_algorithm; strscpy(adap->name, "DesignWare HDMI QP", sizeof(adap->name)); i2c_set_adapdata(adap, hdmi); ret = devm_i2c_add_adapter(hdmi->dev, adap); if (ret) { dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name); devm_kfree(hdmi->dev, i2c); return ERR_PTR(ret); } hdmi->i2c = i2c; dev_info(hdmi->dev, "registered %s I2C bus driver\n", adap->name); return adap; } static int dw_hdmi_qp_config_avi_infoframe(struct dw_hdmi_qp *hdmi, const u8 *buffer, size_t len) { u32 val, i, j; if (len != HDMI_INFOFRAME_SIZE(AVI)) { dev_err(hdmi->dev, "failed to configure avi infoframe\n"); return -EINVAL; } /* * DW HDMI QP IP uses a different byte format from standard AVI info * frames, though generally the bits are in the correct bytes. */ val = buffer[1] << 8 | buffer[2] << 16; dw_hdmi_qp_write(hdmi, val, PKT_AVI_CONTENTS0); for (i = 0; i < 4; i++) { for (j = 0; j < 4; j++) { if (i * 4 + j >= 14) break; if (!j) val = buffer[i * 4 + j + 3]; val |= buffer[i * 4 + j + 3] << (8 * j); } dw_hdmi_qp_write(hdmi, val, PKT_AVI_CONTENTS1 + i * 4); } dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_AVI_FIELDRATE, PKTSCHED_PKT_CONFIG1); dw_hdmi_qp_mod(hdmi, PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN, PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN, PKTSCHED_PKT_EN); return 0; } static int dw_hdmi_qp_config_drm_infoframe(struct dw_hdmi_qp *hdmi, const u8 *buffer, size_t len) { u32 val, i; if (len != HDMI_INFOFRAME_SIZE(DRM)) { dev_err(hdmi->dev, "failed to configure drm infoframe\n"); return -EINVAL; } dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_DRMI_TX_EN, PKTSCHED_PKT_EN); val = buffer[1] << 8 | buffer[2] << 16; dw_hdmi_qp_write(hdmi, val, PKT_DRMI_CONTENTS0); for (i = 0; i <= buffer[2]; i++) { if (i % 4 == 0) val = buffer[3 + i]; val |= buffer[3 + i] << ((i % 4) * 8); if ((i % 4 == 3) || i == buffer[2]) dw_hdmi_qp_write(hdmi, val, PKT_DRMI_CONTENTS1 + ((i / 4) * 4)); } dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_DRMI_FIELDRATE, PKTSCHED_PKT_CONFIG1); dw_hdmi_qp_mod(hdmi, PKTSCHED_DRMI_TX_EN, PKTSCHED_DRMI_TX_EN, PKTSCHED_PKT_EN); return 0; } /* * Static values documented in the TRM * Different values are only used for debug purposes */ #define DW_HDMI_QP_AUDIO_INFOFRAME_HB1 0x1 #define DW_HDMI_QP_AUDIO_INFOFRAME_HB2 0xa static int dw_hdmi_qp_config_audio_infoframe(struct dw_hdmi_qp *hdmi, const u8 *buffer, size_t len) { /* * AUDI_CONTENTS0: { RSV, HB2, HB1, RSV } * AUDI_CONTENTS1: { PB3, PB2, PB1, PB0 } * AUDI_CONTENTS2: { PB7, PB6, PB5, PB4 } * * PB0: CheckSum * PB1: | CT3 | CT2 | CT1 | CT0 | F13 | CC2 | CC1 | CC0 | * PB2: | F27 | F26 | F25 | SF2 | SF1 | SF0 | SS1 | SS0 | * PB3: | F37 | F36 | F35 | F34 | F33 | F32 | F31 | F30 | * PB4: | CA7 | CA6 | CA5 | CA4 | CA3 | CA2 | CA1 | CA0 | * PB5: | DM_INH | LSV3 | LSV2 | LSV1 | LSV0 | F52 | F51 | F50 | * PB6~PB10: Reserved * * AUDI_CONTENTS0 default value defined by HDMI specification, * and shall only be changed for debug purposes. */ u32 header_bytes = (DW_HDMI_QP_AUDIO_INFOFRAME_HB1 << 8) | (DW_HDMI_QP_AUDIO_INFOFRAME_HB2 << 16); regmap_bulk_write(hdmi->regm, PKT_AUDI_CONTENTS0, &header_bytes, 1); regmap_bulk_write(hdmi->regm, PKT_AUDI_CONTENTS1, &buffer[3], 1); regmap_bulk_write(hdmi->regm, PKT_AUDI_CONTENTS2, &buffer[4], 1); /* Enable ACR, AUDI, AMD */ dw_hdmi_qp_mod(hdmi, PKTSCHED_ACR_TX_EN | PKTSCHED_AUDI_TX_EN | PKTSCHED_AMD_TX_EN, PKTSCHED_ACR_TX_EN | PKTSCHED_AUDI_TX_EN | PKTSCHED_AMD_TX_EN, PKTSCHED_PKT_EN); /* Enable AUDS */ dw_hdmi_qp_mod(hdmi, PKTSCHED_AUDS_TX_EN, PKTSCHED_AUDS_TX_EN, PKTSCHED_PKT_EN); return 0; } static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_atomic_state *state) { struct dw_hdmi_qp *hdmi = bridge->driver_private; struct drm_connector_state *conn_state; struct drm_connector *connector; unsigned int op_mode; connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); if (WARN_ON(!connector)) return; conn_state = drm_atomic_get_new_connector_state(state, connector); if (WARN_ON(!conn_state)) return; if (connector->display_info.is_hdmi) { dev_dbg(hdmi->dev, "%s mode=HDMI rate=%llu\n", __func__, conn_state->hdmi.tmds_char_rate); op_mode = 0; hdmi->tmds_char_rate = conn_state->hdmi.tmds_char_rate; } else { dev_dbg(hdmi->dev, "%s mode=DVI\n", __func__); op_mode = OPMODE_DVI; } hdmi->phy.ops->init(hdmi, hdmi->phy.data); dw_hdmi_qp_mod(hdmi, HDCP2_BYPASS, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0); dw_hdmi_qp_mod(hdmi, op_mode, OPMODE_DVI, LINK_CONFIG0); drm_atomic_helper_connector_hdmi_update_infoframes(connector, state); } static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge, struct drm_atomic_state *state) { struct dw_hdmi_qp *hdmi = bridge->driver_private; hdmi->tmds_char_rate = 0; hdmi->phy.ops->disable(hdmi, hdmi->phy.data); } static enum drm_connector_status dw_hdmi_qp_bridge_detect(struct drm_bridge *bridge) { struct dw_hdmi_qp *hdmi = bridge->driver_private; return hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); } static const struct drm_edid * dw_hdmi_qp_bridge_edid_read(struct drm_bridge *bridge, struct drm_connector *connector) { struct dw_hdmi_qp *hdmi = bridge->driver_private; const struct drm_edid *drm_edid; drm_edid = drm_edid_read_ddc(connector, bridge->ddc); if (!drm_edid) dev_dbg(hdmi->dev, "failed to get edid\n"); return drm_edid; } static enum drm_mode_status dw_hdmi_qp_bridge_tmds_char_rate_valid(const struct drm_bridge *bridge, const struct drm_display_mode *mode, unsigned long long rate) { struct dw_hdmi_qp *hdmi = bridge->driver_private; if (rate > HDMI14_MAX_TMDSCLK) { dev_dbg(hdmi->dev, "Unsupported TMDS char rate: %lld\n", rate); return MODE_CLOCK_HIGH; } return MODE_OK; } static int dw_hdmi_qp_bridge_clear_infoframe(struct drm_bridge *bridge, enum hdmi_infoframe_type type) { struct dw_hdmi_qp *hdmi = bridge->driver_private; switch (type) { case HDMI_INFOFRAME_TYPE_AVI: dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN, PKTSCHED_PKT_EN); break; case HDMI_INFOFRAME_TYPE_DRM: dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_DRMI_TX_EN, PKTSCHED_PKT_EN); break; case HDMI_INFOFRAME_TYPE_AUDIO: dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_ACR_TX_EN | PKTSCHED_AUDS_TX_EN | PKTSCHED_AUDI_TX_EN, PKTSCHED_PKT_EN); break; default: dev_dbg(hdmi->dev, "Unsupported infoframe type %x\n", type); } return 0; } static int dw_hdmi_qp_bridge_write_infoframe(struct drm_bridge *bridge, enum hdmi_infoframe_type type, const u8 *buffer, size_t len) { struct dw_hdmi_qp *hdmi = bridge->driver_private; dw_hdmi_qp_bridge_clear_infoframe(bridge, type); switch (type) { case HDMI_INFOFRAME_TYPE_AVI: return dw_hdmi_qp_config_avi_infoframe(hdmi, buffer, len); case HDMI_INFOFRAME_TYPE_DRM: return dw_hdmi_qp_config_drm_infoframe(hdmi, buffer, len); case HDMI_INFOFRAME_TYPE_AUDIO: return dw_hdmi_qp_config_audio_infoframe(hdmi, buffer, len); default: dev_dbg(hdmi->dev, "Unsupported infoframe type %x\n", type); return 0; } } static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = { .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, .atomic_reset = drm_atomic_helper_bridge_reset, .atomic_enable = dw_hdmi_qp_bridge_atomic_enable, .atomic_disable = dw_hdmi_qp_bridge_atomic_disable, .detect = dw_hdmi_qp_bridge_detect, .edid_read = dw_hdmi_qp_bridge_edid_read, .hdmi_tmds_char_rate_valid = dw_hdmi_qp_bridge_tmds_char_rate_valid, .hdmi_clear_infoframe = dw_hdmi_qp_bridge_clear_infoframe, .hdmi_write_infoframe = dw_hdmi_qp_bridge_write_infoframe, .hdmi_audio_startup = dw_hdmi_qp_audio_enable, .hdmi_audio_shutdown = dw_hdmi_qp_audio_disable, .hdmi_audio_prepare = dw_hdmi_qp_audio_prepare, }; static irqreturn_t dw_hdmi_qp_main_hardirq(int irq, void *dev_id) { struct dw_hdmi_qp *hdmi = dev_id; struct dw_hdmi_qp_i2c *i2c = hdmi->i2c; u32 stat; stat = dw_hdmi_qp_read(hdmi, MAINUNIT_1_INT_STATUS); i2c->stat = stat & (I2CM_OP_DONE_IRQ | I2CM_READ_REQUEST_IRQ | I2CM_NACK_RCVD_IRQ); if (i2c->stat) { dw_hdmi_qp_write(hdmi, i2c->stat, MAINUNIT_1_INT_CLEAR); complete(&i2c->cmp); } if (stat) return IRQ_HANDLED; return IRQ_NONE; } static const struct regmap_config dw_hdmi_qp_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, .max_register = EARCRX_1_INT_FORCE, }; static void dw_hdmi_qp_init_hw(struct dw_hdmi_qp *hdmi) { dw_hdmi_qp_write(hdmi, 0, MAINUNIT_0_INT_MASK_N); dw_hdmi_qp_write(hdmi, 0, MAINUNIT_1_INT_MASK_N); dw_hdmi_qp_write(hdmi, 428571429, TIMER_BASE_CONFIG0); /* Software reset */ dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0); dw_hdmi_qp_write(hdmi, 0x085c085c, I2CM_FM_SCL_CONFIG0); dw_hdmi_qp_mod(hdmi, 0, I2CM_FM_EN, I2CM_INTERFACE_CONTROL0); /* Clear DONE and ERROR interrupts */ dw_hdmi_qp_write(hdmi, I2CM_OP_DONE_CLEAR | I2CM_NACK_RCVD_CLEAR, MAINUNIT_1_INT_CLEAR); if (hdmi->phy.ops->setup_hpd) hdmi->phy.ops->setup_hpd(hdmi, hdmi->phy.data); } struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, struct drm_encoder *encoder, const struct dw_hdmi_qp_plat_data *plat_data) { struct device *dev = &pdev->dev; struct dw_hdmi_qp *hdmi; void __iomem *regs; int ret; if (!plat_data->phy_ops || !plat_data->phy_ops->init || !plat_data->phy_ops->disable || !plat_data->phy_ops->read_hpd) { dev_err(dev, "Missing platform PHY ops\n"); return ERR_PTR(-ENODEV); } hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); if (!hdmi) return ERR_PTR(-ENOMEM); hdmi->dev = dev; regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(regs)) return ERR_CAST(regs); hdmi->regm = devm_regmap_init_mmio(dev, regs, &dw_hdmi_qp_regmap_config); if (IS_ERR(hdmi->regm)) { dev_err(dev, "Failed to configure regmap\n"); return ERR_CAST(hdmi->regm); } hdmi->phy.ops = plat_data->phy_ops; hdmi->phy.data = plat_data->phy_data; dw_hdmi_qp_init_hw(hdmi); ret = devm_request_threaded_irq(dev, plat_data->main_irq, dw_hdmi_qp_main_hardirq, NULL, IRQF_SHARED, dev_name(dev), hdmi); if (ret) return ERR_PTR(ret); hdmi->bridge.driver_private = hdmi; hdmi->bridge.funcs = &dw_hdmi_qp_bridge_funcs; hdmi->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HDMI | DRM_BRIDGE_OP_HPD; hdmi->bridge.of_node = pdev->dev.of_node; hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA; hdmi->bridge.vendor = "Synopsys"; hdmi->bridge.product = "DW HDMI QP TX"; hdmi->bridge.ddc = dw_hdmi_qp_i2c_adapter(hdmi); if (IS_ERR(hdmi->bridge.ddc)) return ERR_CAST(hdmi->bridge.ddc); hdmi->bridge.hdmi_audio_max_i2s_playback_channels = 8; hdmi->bridge.hdmi_audio_dev = dev; hdmi->bridge.hdmi_audio_dai_port = 1; ret = devm_drm_bridge_add(dev, &hdmi->bridge); if (ret) return ERR_PTR(ret); ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); if (ret) return ERR_PTR(ret); return hdmi; } EXPORT_SYMBOL_GPL(dw_hdmi_qp_bind); void dw_hdmi_qp_resume(struct device *dev, struct dw_hdmi_qp *hdmi) { dw_hdmi_qp_init_hw(hdmi); } EXPORT_SYMBOL_GPL(dw_hdmi_qp_resume); MODULE_AUTHOR("Algea Cao "); MODULE_AUTHOR("Cristian Ciocaltea "); MODULE_DESCRIPTION("DW HDMI QP transmitter library"); MODULE_LICENSE("GPL");